[Proposal] Global temporary tables

Started by 曾文旌(义从)over 6 years ago368 messages
#1曾文旌(义从)
wenjing.zwj@alibaba-inc.com

Dear Hackers,

This propose a way to develop global temporary tables in PostgreSQL.

I noticed that there is an "Allow temporary tables to exist as empty by default in all sessions" in the postgresql todolist.
https://wiki.postgresql.org/wiki/Todo <https://wiki.postgresql.org/wiki/Todo&gt;

In recent years, PG community had many discussions about global temp table (GTT) support. Previous discussion covered the following topics:
(1) The main benefit or function: GTT offers features like “persistent schema, ephemeral data”, which avoids catalog bloat and reduces catalog vacuum.
(2) Whether follows ANSI concept of temporary tables
(3) How to deal with statistics, single copy of schema definition, relcache
(4) More can be seen in /messages/by-id/73954ab7-44d3-b37b-81a3-69bdcbb446f7@postgrespro.ru
(5) A recent implementation and design from Konstantin Knizhnik covered many functions of GTT: /messages/by-id/attachment/103265/global_private_temp-1.patch </messages/by-id/attachment/103265/global_private_temp-1.patch&gt;

However, as pointed by Konstantin himself, the implementation still needs functions related to CLOG, vacuum, and MVCC visibility.

We developed GTT based on PG 11 and included most needed features, such as how to deal with concurrent DDL and DML operations, how to handle vacuum and too old relfrozenxids, and how to store and access GTT statistics.

This design followed many suggestions from previous discussion in community. Here are some examples:
“have a separate 'relpersistence' setting for global temp tables…by having the backend id in all filename…. From Andres Freund
Use session memory context to store information related to GTT. From Pavel Stehule
“extend the relfilenode mapper to support a backend-local non-persistent relfilenode map that's used to track temp table and index relfilenodes…” from Craig Ringer

Our implementation creates one record in pg_class for GTT’s schema definition. When rows are first inserted into the GTT in a session, a session specific file is created to store the GTT’s data. Those files are removed when the session ends. We maintain the GTT’s statistics in session local memory. DDL operations, such as DROP table or CREATE INDEX, can be executed on a GTT only by one session, while no other sessions insert any data into the GTT before or it is already truncated. This also avoids the concurrency of DML and DDL operations on GTT. We maintain a session level oldest relfrozenxids for GTT. This way, autovacuum or vacuum can truncate CLOG and increase global relfrozenxids based on all tables’ relfrozenxids, including GTT’s.
The follows summarize the main design and implementation:
Syntax: ON COMMIT PRESERVE ROWS and ON COMMIT DELETE ROWS
Data storage and buffering follows the same way as local temp table with a relfilenode including session id.
A hash table(A) in shared memory is used to track sessions and their usage of GTTs and to serialize DDL and DML operations.
Another hash table(B) in session memory is introduced to record storage files for GTTs and their indexes. When a session ends, those files are removed.
The same hash table(B) in session memory is used to record the relfrozenxids of each GTT. The oldest one is stored in myproc so that autovacuum and vacuum may use it to determine global oldest relfrozenxids and truncate clog.
The same hash table(B) in session memory stores GTT’s session level statistics, It is generated during the operations of vacuum and analyze, and used by SQL optimizer to create execution plan.
Some utility functions are added for DBA to manage GTTs.
TRUNCATE command on a GTT behaves differently from that on a normal table. The command deletes the data immediately but keeps relfilenode using lower level table lock, RowExclusiveLock, instead of AccessExclusiveLock.
Main limits of this version or future improvement: need suggestions from community:
1 VACUUM FULL and CLUSTER are not supported; any operations which may change relfilenode are disabled to GTT.
2 Sequence column is not supported in GTT for now.
3 Users defined statistics is not supported.

Details:

Requirement
The features list about global temp table:
1. global temp table (ON COMMIT clause is omitted, SQL specifies that the default behavior is ON COMMIT DELETE ROWS)
2. support with on commit DELETE ROWS
3. support with on commit PRESERVE ROWS
4. not support ON COMMIT DROP

Feature description
Global temp tables are defined just once and automatically exist (starting with empty contents) in every session that needs them.
Global temp table, each session use local buffer, read or write independent data files.
Use on commit DELETE ROWS for a transaction-specific global temp table. This is the default. database will truncate the table (delete all its rows) after each commit.
Use on commit PRESERVE ROWS Specify PRESERVE ROWS for a session-specific global temp table. databse will truncate the table (delete all its rows) when you terminate the session.

design
Global temp tables are designed based on local temp table(buffer and storage files).
Because the catalog of global temp table is shared between sessions but the data is not shared, we need to build some new mechanisms to manage non-shared data and statistics for those data.

1. catalog
1.1 relpersistence
define RELPERSISTENCEGLOBALTEMP 'g'
Mark global temp table in pg_class relpersistence to 'T'. The relpersistence of the index created on the global temp table is also set to ’T'

1.2 on commit clause
In local temp table on commit DELETE ROWS and on commit PRESERVE ROWS not store in catalog, but GTT need.
Store a bool value oncommitdelete_rows to reloptions only for GTT and share with other session.

2. gram.y
Global temp table already has a syntax tree. jush need to remove the warning message "GLOBAL is deprecated in temporary table creation" and mark relpersistence = RELPERSISTENCEGLOBALTEMP

3. STORAGE
3.1. active_gtt_shared_hash
create a hash table in shared memory to trace the GTT files that are initialized in each session.
Each hash entry contains a bitmap that records the backendid of the initialized GTT file.
With this hash table, we know which backend/session are using this GTT.
It will be used in GTT's DDL.

3.2. gtt_storage_local_hash
In each backend, create a local hashtable gtt_storage_local_hash for tracks GTT storage file and statistics.
1). GTT storage file track
When one session inserts data into a GTT for the first time, record to local hash.
2). normal clean GTT files
Use beforeshmemexit to ensure that all files for the session GTT are deleted when the session exits.
3). abnormal situation file cleanup
When a backend exits abnormally (such as oom kill), the startup process started to recovery before accept connect. startup process check and remove all GTT files before redo wal.

4 DDL
4.1 DROP GTT
One GTT table is allowed to be deleted when only the current session USES it. After get the AccessExclusiveLock of the GTT table, use active_gtt_shared_hash to check and make sure that.

4.2 ALTER GTT
Same as drop GTT.

4.3 CREATE INDEX ON GTT, DROP INDEX ON GTT
Same as drop GTT.

4.4 TRUNCATE GTT
The truncate GTT use RowExclusiveLock, not AccessExclusiveLock, Because truncate only cleans up local data file and local buffers in this session.
Also, truncate immediately deletes the data file without changing the relfilenode of the GTT table. btw, I'm not sure the implementation will be acceptable to the community.

4.5 create index on GTT
Same as drop GTT.

4.6 OTHERS
Any table operations about GTT that need to change relfilenode are disabled, such as vacuum full/cluster.

5. The statistics of GTT
1 relpages reltuples relallvisible frozenxid minmulti from pg_class
2 The statistics for each column from pg_statistic
All the above information will be stored to gtt_storage_local_hash.
When vacuum or analyze GTT's statistic will update, and the planner will use them. Of course, statistics only contain data within the current session.

5.1. View global temp table statistics
Provide pggttattstatistic get column statistics for GTT. Provide pggtt_relstats to rel statistics for GTT.
These functions are implemented in a plug-in, without add system view or function.

6. autovacuum
Autovacuum skips all GTT.

7. vacuum(frozenxid push, clog truncate)
The GTT data file contains transaction information. Queries for GTT data rely on transaction information such as clog. That's can not be vacuumed automatically by vacuum.
7.1 The session level gtt oldest frozenxid
When one GTT been create or remove, record the session level oldest frozenxid and put it into MyProc.

7.1 vacuum
When vacuum push the db's frozenxid(vacupdatedatfrozenxid), need to consider the GTT. It needs to calculate the transactions required for the GTT(search all MyPorc), to avoid the clog required by GTT being cleaned.

8. Parallel query
Planner does not produce parallel query plans for SQL related to global temp table.

9. Operability
Provide pggttattachedpid lists all the pids that are using the GTT. Provide pglistgttrelfrozenxids lists the session level oldest frozenxid of using GTT.
These functions are implemented in a plug-in, without add system view or function.
DBA can use the above function and pgterminatebackend to force the cleanup of "too old" GTT tables and sessions.

10. Limitations and todo list
10.1. alter GTT
10.2. pg_statistic_ext
10.3. remove GTT's relfilenode can not change limit.
cluster/vacuum full, optimize truncate gtt.
10.4. SERIAL column type
The GTT from different sessions share a sequence(SERIAL type).
Need each session use the sequence independently.
10.5. Locking optimization for GTT.
10.6 materialized views is not support on GTT.

What do you thinking about this proposal?
Looking forward to your feedback.

Thanks!

regards

--
Zeng Wenjing
Alibaba Group-Database Products Business Unit

#2Konstantin Knizhnik
k.knizhnik@postgrespro.ru
In reply to: 曾文旌(义从) (#1)
Re: [Proposal] Global temporary tables

On 11.10.2019 15:15, 曾文旌(义从) wrote:

Dear Hackers,

This propose a way to develop global temporary tables in PostgreSQL.

I noticed that there is an "Allow temporary tables to exist as empty
by default in all sessions" in the postgresql todolist.
https://wiki.postgresql.org/wiki/Todo

In recent years, PG community had many discussions about global temp
table (GTT) support. Previous discussion covered the following topics:
(1)The main benefit or function: GTT offers features like “persistent
schema, ephemeral data”, which avoids catalog bloat and reduces
catalog vacuum.
(2)Whether follows ANSI concept of temporary tables
(3)How to deal with statistics, single copy of schema definition, relcache
(4)More can be seen in
/messages/by-id/73954ab7-44d3-b37b-81a3-69bdcbb446f7@postgrespro.ru
(5)A recent implementation and design from Konstantin Knizhnik covered
many functions of GTT:
/messages/by-id/attachment/103265/global_private_temp-1.patch

However, as pointed by Konstantin himself, the implementation still
needs functions related to CLOG, vacuum, and MVCC visibility.

Just to clarify.
I have now proposed several different solutions for GTT:

Shared vs. private buffers for GTT:
1. Private buffers. This is least invasive patch, requiring no changes
in relfilenodes.
2. Shared buffers. Requires changing relfilenode but supports parallel
query execution for GTT.

Access to GTT at replica:
1. Access is prohibited (as for original temp tables). No changes at all.
2. Tuples of temp tables are marked with forzen XID.  Minimal changes,
rollbacks are not possible.
3. Providing special XIDs for GTT at replica. No changes in CLOG are
required, but special MVCC visibility rules are used for GTT. Current
limitation: number of transactions accessing GTT at replica is limited
by 2^32
and bitmap of correspondent size has to be maintained (tuples of GTT are
not proceeded by vacuum and not frozen, so XID horizon never moved).

So except the limitation mentioned above (which I do not consider as
critical) there is only one problem which was not addressed: maintaining
statistics for GTT.
If all of the following conditions are true:

1) GTT are used in joins
2) There are indexes defined for GTT
3) Size and histogram of GTT in different backends can significantly vary.
4) ANALYZE was explicitly called for GTT

then query execution plan built in one backend will be also used for
other backends where it can be inefficient.
I also do not consider this problem as "show stopper" for adding GTT to
Postgres.

I still do not understand the opinion of community which functionality
of GTT is considered to be most important.
But the patch with local buffers and no replica support is small enough
to become good starting point.

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

#3Pavel Stehule
pavel.stehule@gmail.com
In reply to: Konstantin Knizhnik (#2)
Re: [Proposal] Global temporary tables

pá 11. 10. 2019 v 15:50 odesílatel Konstantin Knizhnik <
k.knizhnik@postgrespro.ru> napsal:

On 11.10.2019 15:15, 曾文旌(义从) wrote:

Dear Hackers,

This propose a way to develop global temporary tables in PostgreSQL.

I noticed that there is an "Allow temporary tables to exist as empty by
default in all sessions" in the postgresql todolist.
https://wiki.postgresql.org/wiki/Todo

In recent years, PG community had many discussions about global temp
table (GTT) support. Previous discussion covered the following topics:
(1) The main benefit or function: GTT offers features like “persistent
schema, ephemeral data”, which avoids catalog bloat and reduces catalog
vacuum.
(2) Whether follows ANSI concept of temporary tables
(3) How to deal with statistics, single copy of schema definition,
relcache
(4) More can be seen in
/messages/by-id/73954ab7-44d3-b37b-81a3-69bdcbb446f7@postgrespro.ru
(5) A recent implementation and design from Konstantin Knizhnik covered
many functions of GTT:
/messages/by-id/attachment/103265/global_private_temp-1.patch

However, as pointed by Konstantin himself, the implementation still needs
functions related to CLOG, vacuum, and MVCC visibility.

Just to clarify.
I have now proposed several different solutions for GTT:

Shared vs. private buffers for GTT:
1. Private buffers. This is least invasive patch, requiring no changes in
relfilenodes.
2. Shared buffers. Requires changing relfilenode but supports parallel
query execution for GTT.

This is important argument for using share buffers. Maybe the best is mix
of both - store files in temporal tablespace, but using share buffers.
More, it can be accessible for autovacuum.

Access to GTT at replica:
1. Access is prohibited (as for original temp tables). No changes at all.
2. Tuples of temp tables are marked with forzen XID. Minimal changes,
rollbacks are not possible.
3. Providing special XIDs for GTT at replica. No changes in CLOG are
required, but special MVCC visibility rules are used for GTT. Current
limitation: number of transactions accessing GTT at replica is limited by
2^32
and bitmap of correspondent size has to be maintained (tuples of GTT are
not proceeded by vacuum and not frozen, so XID horizon never moved).

So except the limitation mentioned above (which I do not consider as
critical) there is only one problem which was not addressed: maintaining
statistics for GTT.
If all of the following conditions are true:

1) GTT are used in joins
2) There are indexes defined for GTT
3) Size and histogram of GTT in different backends can significantly vary.
4) ANALYZE was explicitly called for GTT

then query execution plan built in one backend will be also used for other
backends where it can be inefficient.
I also do not consider this problem as "show stopper" for adding GTT to
Postgres.

The last issue is show stopper in my mind. It really depends on usage.
There are situation where shared statistics are ok (and maybe good
solution), and other situation, where shared statistics are just unusable.

Regards

Pavel

Show quoted text

I still do not understand the opinion of community which functionality of
GTT is considered to be most important.
But the patch with local buffers and no replica support is small enough to
become good starting point.

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

#4曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Pavel Stehule (#3)
Re: [Proposal] Global temporary tables

2019年10月12日 下午1:16,Pavel Stehule <pavel.stehule@gmail.com> 写道:

pá 11. 10. 2019 v 15:50 odesílatel Konstantin Knizhnik <k.knizhnik@postgrespro.ru <mailto:k.knizhnik@postgrespro.ru>> napsal:

On 11.10.2019 15:15, 曾文旌(义从) wrote:

Dear Hackers,

This propose a way to develop global temporary tables in PostgreSQL.

I noticed that there is an "Allow temporary tables to exist as empty by default in all sessions" in the postgresql todolist.
https://wiki.postgresql.org/wiki/Todo <https://wiki.postgresql.org/wiki/Todo&gt;

In recent years, PG community had many discussions about global temp table (GTT) support. Previous discussion covered the following topics:
(1) The main benefit or function: GTT offers features like “persistent schema, ephemeral data”, which avoids catalog bloat and reduces catalog vacuum.
(2) Whether follows ANSI concept of temporary tables
(3) How to deal with statistics, single copy of schema definition, relcache
(4) More can be seen in /messages/by-id/73954ab7-44d3-b37b-81a3-69bdcbb446f7@postgrespro.ru </messages/by-id/73954ab7-44d3-b37b-81a3-69bdcbb446f7@postgrespro.ru&gt;
(5) A recent implementation and design from Konstantin Knizhnik covered many functions of GTT: /messages/by-id/attachment/103265/global_private_temp-1.patch </messages/by-id/attachment/103265/global_private_temp-1.patch&gt;

However, as pointed by Konstantin himself, the implementation still needs functions related to CLOG, vacuum, and MVCC visibility.

Just to clarify.
I have now proposed several different solutions for GTT:

Shared vs. private buffers for GTT:
1. Private buffers. This is least invasive patch, requiring no changes in relfilenodes.
2. Shared buffers. Requires changing relfilenode but supports parallel query execution for GTT.

This is important argument for using share buffers. Maybe the best is mix of both - store files in temporal tablespace, but using share buffers. More, it can be accessible for autovacuum.

Access to GTT at replica:
1. Access is prohibited (as for original temp tables). No changes at all.
2. Tuples of temp tables are marked with forzen XID. Minimal changes, rollbacks are not possible.
3. Providing special XIDs for GTT at replica. No changes in CLOG are required, but special MVCC visibility rules are used for GTT. Current limitation: number of transactions accessing GTT at replica is limited by 2^32
and bitmap of correspondent size has to be maintained (tuples of GTT are not proceeded by vacuum and not frozen, so XID horizon never moved).

So except the limitation mentioned above (which I do not consider as critical) there is only one problem which was not addressed: maintaining statistics for GTT.
If all of the following conditions are true:

1) GTT are used in joins
2) There are indexes defined for GTT
3) Size and histogram of GTT in different backends can significantly vary.
4) ANALYZE was explicitly called for GTT

then query execution plan built in one backend will be also used for other backends where it can be inefficient.
I also do not consider this problem as "show stopper" for adding GTT to Postgres.

The last issue is show stopper in my mind. It really depends on usage. There are situation where shared statistics are ok (and maybe good solution), and other situation, where shared statistics are just unusable.

This proposal calculates and stores independent statistics(relpages reltuples and histogram of GTT) for the gtt data within each session, ensuring optimizer can get accurate statistics.

Show quoted text

Regards

Pavel

I still do not understand the opinion of community which functionality of GTT is considered to be most important.
But the patch with local buffers and no replica support is small enough to become good starting point.

--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com <http://www.postgrespro.com/&gt;
The Russian Postgres Company

#5曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Konstantin Knizhnik (#2)
Re: [Proposal] Global temporary tables

2019年10月11日 下午9:50,Konstantin Knizhnik <k.knizhnik@postgrespro.ru> 写道:

On 11.10.2019 15:15, 曾文旌(义从) wrote:

Dear Hackers,

This propose a way to develop global temporary tables in PostgreSQL.

I noticed that there is an "Allow temporary tables to exist as empty by default in all sessions" in the postgresql todolist.
https://wiki.postgresql.org/wiki/Todo <https://wiki.postgresql.org/wiki/Todo&gt;

In recent years, PG community had many discussions about global temp table (GTT) support. Previous discussion covered the following topics:
(1) The main benefit or function: GTT offers features like “persistent schema, ephemeral data”, which avoids catalog bloat and reduces catalog vacuum.
(2) Whether follows ANSI concept of temporary tables
(3) How to deal with statistics, single copy of schema definition, relcache
(4) More can be seen in /messages/by-id/73954ab7-44d3-b37b-81a3-69bdcbb446f7@postgrespro.ru </messages/by-id/73954ab7-44d3-b37b-81a3-69bdcbb446f7@postgrespro.ru&gt;
(5) A recent implementation and design from Konstantin Knizhnik covered many functions of GTT: /messages/by-id/attachment/103265/global_private_temp-1.patch </messages/by-id/attachment/103265/global_private_temp-1.patch&gt;

However, as pointed by Konstantin himself, the implementation still needs functions related to CLOG, vacuum, and MVCC visibility.

Just to clarify.
I have now proposed several different solutions for GTT:

Shared vs. private buffers for GTT:
1. Private buffers. This is least invasive patch, requiring no changes in relfilenodes.
2. Shared buffers. Requires changing relfilenode but supports parallel query execution for GTT.

Access to GTT at replica:
1. Access is prohibited (as for original temp tables). No changes at all.
2. Tuples of temp tables are marked with forzen XID. Minimal changes, rollbacks are not possible.
3. Providing special XIDs for GTT at replica. No changes in CLOG are required, but special MVCC visibility rules are used for GTT. Current limitation: number of transactions accessing GTT at replica is limited by 2^32
and bitmap of correspondent size has to be maintained (tuples of GTT are not proceeded by vacuum and not frozen, so XID horizon never moved).

So except the limitation mentioned above (which I do not consider as critical) there is only one problem which was not addressed: maintaining statistics for GTT.
If all of the following conditions are true:

1) GTT are used in joins
2) There are indexes defined for GTT
3) Size and histogram of GTT in different backends can significantly vary.
4) ANALYZE was explicitly called for GTT

then query execution plan built in one backend will be also used for other backends where it can be inefficient.
I also do not consider this problem as "show stopper" for adding GTT to Postgres.

When session A writes 10000000 rows of data to gtt X, session B also uses X at the same time and it has 100 rows of different data. If B uses analyze to count the statistics of 100000 rows of data and updates it to catalog.
Obviously, session A will get inaccurate query plan based on misaligned statistics when calculating the query plan for X related queries. Session A may think that table X is too small to be worth using index scan, but it is not. Each session needs to get the statistics of the self data to make the query plan.

I still do not understand the opinion of community which functionality of GTT is considered to be most important.
But the patch with local buffers and no replica support is small enough to become good starting point.

Yes ,the first step, we focus on complete basic functions of gtt (dml ddl index on gtt (MVCC visibility rules) storage).
Abnormal statistics can cause problems with index selection on gtt, so index on gtt and accurate statistical information is necessary.

Show quoted text

--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com <http://www.postgrespro.com/&gt;
The Russian Postgres Company

#6Robert Haas
robertmhaas@gmail.com
In reply to: Konstantin Knizhnik (#2)
Re: [Proposal] Global temporary tables

On Fri, Oct 11, 2019 at 9:50 AM Konstantin Knizhnik
<k.knizhnik@postgrespro.ru> wrote:

Just to clarify.
I have now proposed several different solutions for GTT:

Shared vs. private buffers for GTT:
1. Private buffers. This is least invasive patch, requiring no changes in relfilenodes.
2. Shared buffers. Requires changing relfilenode but supports parallel query execution for GTT.

I vote for #1. I think parallel query for temp objects may be a
desirable feature, but I don't think it should be the job of a patch
implementing GTTs to make it happen. In fact, I think it would be an
actively bad idea, because I suspect that if we do eventually support
temp relations for parallel query, we're going to want a solution that
is shared between regular temp tables and global temp tables, not
separate solutions for each.

Access to GTT at replica:
1. Access is prohibited (as for original temp tables). No changes at all.
2. Tuples of temp tables are marked with forzen XID. Minimal changes, rollbacks are not possible.
3. Providing special XIDs for GTT at replica. No changes in CLOG are required, but special MVCC visibility rules are used for GTT. Current limitation: number of transactions accessing GTT at replica is limited by 2^32
and bitmap of correspondent size has to be maintained (tuples of GTT are not proceeded by vacuum and not frozen, so XID horizon never moved).

I again vote for #1. A GTT is defined to allow data to be visible only
within one session -- so what does it even mean for the data to be
accessible on a replica?

So except the limitation mentioned above (which I do not consider as critical) there is only one problem which was not addressed: maintaining statistics for GTT.
If all of the following conditions are true:

1) GTT are used in joins
2) There are indexes defined for GTT
3) Size and histogram of GTT in different backends can significantly vary.
4) ANALYZE was explicitly called for GTT

then query execution plan built in one backend will be also used for other backends where it can be inefficient.
I also do not consider this problem as "show stopper" for adding GTT to Postgres.

I think that's *definitely* a show stopper.

I still do not understand the opinion of community which functionality of GTT is considered to be most important.
But the patch with local buffers and no replica support is small enough to become good starting point.

Well, it seems we now have two patches for this feature. I guess we
need to figure out which one is better, and whether it's possible for
the two efforts to be merged, rather than having two different teams
hacking on separate code bases.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#7Pavel Stehule
pavel.stehule@gmail.com
In reply to: Robert Haas (#6)
Re: [Proposal] Global temporary tables

pá 25. 10. 2019 v 17:01 odesílatel Robert Haas <robertmhaas@gmail.com>
napsal:

On Fri, Oct 11, 2019 at 9:50 AM Konstantin Knizhnik
<k.knizhnik@postgrespro.ru> wrote:

Just to clarify.
I have now proposed several different solutions for GTT:

Shared vs. private buffers for GTT:
1. Private buffers. This is least invasive patch, requiring no changes

in relfilenodes.

2. Shared buffers. Requires changing relfilenode but supports parallel

query execution for GTT.

I vote for #1. I think parallel query for temp objects may be a
desirable feature, but I don't think it should be the job of a patch
implementing GTTs to make it happen. In fact, I think it would be an
actively bad idea, because I suspect that if we do eventually support
temp relations for parallel query, we're going to want a solution that
is shared between regular temp tables and global temp tables, not
separate solutions for each.

Access to GTT at replica:
1. Access is prohibited (as for original temp tables). No changes at all.
2. Tuples of temp tables are marked with forzen XID. Minimal changes,

rollbacks are not possible.

3. Providing special XIDs for GTT at replica. No changes in CLOG are

required, but special MVCC visibility rules are used for GTT. Current
limitation: number of transactions accessing GTT at replica is limited by
2^32

and bitmap of correspondent size has to be maintained (tuples of GTT are

not proceeded by vacuum and not frozen, so XID horizon never moved).

I again vote for #1. A GTT is defined to allow data to be visible only
within one session -- so what does it even mean for the data to be
accessible on a replica?

why not? there are lot of sessions on replica servers. One usage of temp
tables is fixing estimation errors. You can create temp table with partial
query result, run ANALYZE and evaluate other steps. Now this case is not
possible on replica servers.

One motivation for GTT is decreasing port costs from Oracle. But other
motivations, like do more complex calculations on replica are valid and
valuable.

Show quoted text

So except the limitation mentioned above (which I do not consider as

critical) there is only one problem which was not addressed: maintaining
statistics for GTT.

If all of the following conditions are true:

1) GTT are used in joins
2) There are indexes defined for GTT
3) Size and histogram of GTT in different backends can significantly

vary.

4) ANALYZE was explicitly called for GTT

then query execution plan built in one backend will be also used for

other backends where it can be inefficient.

I also do not consider this problem as "show stopper" for adding GTT to

Postgres.

I think that's *definitely* a show stopper.

I still do not understand the opinion of community which functionality

of GTT is considered to be most important.

But the patch with local buffers and no replica support is small enough

to become good starting point.

Well, it seems we now have two patches for this feature. I guess we
need to figure out which one is better, and whether it's possible for
the two efforts to be merged, rather than having two different teams
hacking on separate code bases.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#8Konstantin Knizhnik
k.knizhnik@postgrespro.ru
In reply to: Robert Haas (#6)
Re: [Proposal] Global temporary tables

On 25.10.2019 18:01, Robert Haas wrote:

On Fri, Oct 11, 2019 at 9:50 AM Konstantin Knizhnik
<k.knizhnik@postgrespro.ru> wrote:

Just to clarify.
I have now proposed several different solutions for GTT:

Shared vs. private buffers for GTT:
1. Private buffers. This is least invasive patch, requiring no changes in relfilenodes.
2. Shared buffers. Requires changing relfilenode but supports parallel query execution for GTT.

I vote for #1. I think parallel query for temp objects may be a
desirable feature, but I don't think it should be the job of a patch
implementing GTTs to make it happen. In fact, I think it would be an
actively bad idea, because I suspect that if we do eventually support
temp relations for parallel query, we're going to want a solution that
is shared between regular temp tables and global temp tables, not
separate solutions for each.

Sorry, may be I do not not understand you.
It seems to me that there is only one thing preventing usage of
temporary tables in parallel plans: private buffers.
If global temporary tables are accessed as normal tables though shared
buffers then them can be used in parallel queries
and no extra support is required for it.
At least I have checked that parallel queries are correctly worked for
my implementation of GTT with shared buffers.
So I do not understand about which "separate solutions" you are talking
about.

I can agree that private buffers may be  good starting point for GTT
implementation, because it is less invasive and GTT access speed is
exactly the same as of normal temp tables.
But I do not understand your argument why it is "actively bad idea".

Access to GTT at replica:
1. Access is prohibited (as for original temp tables). No changes at all.
2. Tuples of temp tables are marked with forzen XID. Minimal changes, rollbacks are not possible.
3. Providing special XIDs for GTT at replica. No changes in CLOG are required, but special MVCC visibility rules are used for GTT. Current limitation: number of transactions accessing GTT at replica is limited by 2^32
and bitmap of correspondent size has to be maintained (tuples of GTT are not proceeded by vacuum and not frozen, so XID horizon never moved).

I again vote for #1. A GTT is defined to allow data to be visible only
within one session -- so what does it even mean for the data to be
accessible on a replica?

There are sessions at replica (in case of hot standby), aren't there?

So except the limitation mentioned above (which I do not consider as critical) there is only one problem which was not addressed: maintaining statistics for GTT.
If all of the following conditions are true:

1) GTT are used in joins
2) There are indexes defined for GTT
3) Size and histogram of GTT in different backends can significantly vary.
4) ANALYZE was explicitly called for GTT

then query execution plan built in one backend will be also used for other backends where it can be inefficient.
I also do not consider this problem as "show stopper" for adding GTT to Postgres.

I think that's *definitely* a show stopper.

Well, if both you and Pavel think that it is really "show stopper", then
this problem really has to be addressed.
I slightly confused about this opinion, because Pavel has told me
himself that 99% of users never create indexes for temp tables
or run "analyze" for them. And without it, this problem is not a problem
at all.

I still do not understand the opinion of community which functionality of GTT is considered to be most important.
But the patch with local buffers and no replica support is small enough to become good starting point.

Well, it seems we now have two patches for this feature. I guess we
need to figure out which one is better, and whether it's possible for
the two efforts to be merged, rather than having two different teams
hacking on separate code bases.

I am open for cooperations.
Source code of all my patches is available.

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

#9Pavel Stehule
pavel.stehule@gmail.com
In reply to: Konstantin Knizhnik (#8)
Re: [Proposal] Global temporary tables

So except the limitation mentioned above (which I do not consider as

critical) there is only one problem which was not addressed: maintaining
statistics for GTT.

If all of the following conditions are true:

1) GTT are used in joins
2) There are indexes defined for GTT
3) Size and histogram of GTT in different backends can significantly

vary.

4) ANALYZE was explicitly called for GTT

then query execution plan built in one backend will be also used for

other backends where it can be inefficient.

I also do not consider this problem as "show stopper" for adding GTT to

Postgres.

I think that's *definitely* a show stopper.

Well, if both you and Pavel think that it is really "show stopper", then
this problem really has to be addressed.
I slightly confused about this opinion, because Pavel has told me
himself that 99% of users never create indexes for temp tables
or run "analyze" for them. And without it, this problem is not a problem
at all.

Users doesn't do ANALYZE on temp tables in 99%. It's true. But second fact
is so users has lot of problems. It's very similar to wrong statistics on
persistent tables. When data are small, then it is not problem for users,
although from my perspective it's not optimal. When data are not small,
then the problem can be brutal. Temporary tables are not a exception. And
users and developers are people - we know only about fatal problems. There
are lot of unoptimized queries, but because the problem is not fatal, then
it is not reason for report it. And lot of people has not any idea how fast
the databases can be. The knowledges of users and app developers are sad
book.

Pavel

#10曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Konstantin Knizhnik (#8)
Re: [Proposal] Global temporary tables

2019年10月26日 上午12:22,Konstantin Knizhnik <k.knizhnik@postgrespro.ru> 写道:

On 25.10.2019 18:01, Robert Haas wrote:

On Fri, Oct 11, 2019 at 9:50 AM Konstantin Knizhnik
<k.knizhnik@postgrespro.ru> wrote:

Just to clarify.
I have now proposed several different solutions for GTT:

Shared vs. private buffers for GTT:
1. Private buffers. This is least invasive patch, requiring no changes in relfilenodes.
2. Shared buffers. Requires changing relfilenode but supports parallel query execution for GTT.

I vote for #1. I think parallel query for temp objects may be a
desirable feature, but I don't think it should be the job of a patch
implementing GTTs to make it happen. In fact, I think it would be an
actively bad idea, because I suspect that if we do eventually support
temp relations for parallel query, we're going to want a solution that
is shared between regular temp tables and global temp tables, not
separate solutions for each.

Sorry, may be I do not not understand you.
It seems to me that there is only one thing preventing usage of temporary tables in parallel plans: private buffers.
If global temporary tables are accessed as normal tables though shared buffers then them can be used in parallel queries
and no extra support is required for it.
At least I have checked that parallel queries are correctly worked for my implementation of GTT with shared buffers.
So I do not understand about which "separate solutions" you are talking about.

I can agree that private buffers may be good starting point for GTT implementation, because it is less invasive and GTT access speed is exactly the same as of normal temp tables.
But I do not understand your argument why it is "actively bad idea".

Access to GTT at replica:
1. Access is prohibited (as for original temp tables). No changes at all.
2. Tuples of temp tables are marked with forzen XID. Minimal changes, rollbacks are not possible.
3. Providing special XIDs for GTT at replica. No changes in CLOG are required, but special MVCC visibility rules are used for GTT. Current limitation: number of transactions accessing GTT at replica is limited by 2^32
and bitmap of correspondent size has to be maintained (tuples of GTT are not proceeded by vacuum and not frozen, so XID horizon never moved).

I again vote for #1. A GTT is defined to allow data to be visible only
within one session -- so what does it even mean for the data to be
accessible on a replica?

There are sessions at replica (in case of hot standby), aren't there?

So except the limitation mentioned above (which I do not consider as critical) there is only one problem which was not addressed: maintaining statistics for GTT.
If all of the following conditions are true:

1) GTT are used in joins
2) There are indexes defined for GTT
3) Size and histogram of GTT in different backends can significantly vary.
4) ANALYZE was explicitly called for GTT

then query execution plan built in one backend will be also used for other backends where it can be inefficient.
I also do not consider this problem as "show stopper" for adding GTT to Postgres.

I think that's *definitely* a show stopper.

Well, if both you and Pavel think that it is really "show stopper", then this problem really has to be addressed.
I slightly confused about this opinion, because Pavel has told me himself that 99% of users never create indexes for temp tables
or run "analyze" for them. And without it, this problem is not a problem at all.

I still do not understand the opinion of community which functionality of GTT is considered to be most important.
But the patch with local buffers and no replica support is small enough to become good starting point.

Well, it seems we now have two patches for this feature. I guess we
need to figure out which one is better, and whether it's possible for
the two efforts to be merged, rather than having two different teams
hacking on separate code bases.

I am open for cooperations.
Source code of all my patches is available.

We are also willing to cooperate to complete this feature.
Let me prepare the code(merge code to pg12) and up to community, then see how we work together.

Show quoted text

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

#11Robert Haas
robertmhaas@gmail.com
In reply to: Pavel Stehule (#7)
Re: [Proposal] Global temporary tables

On Fri, Oct 25, 2019 at 11:14 AM Pavel Stehule <pavel.stehule@gmail.com> wrote:

Access to GTT at replica:
1. Access is prohibited (as for original temp tables). No changes at all.
2. Tuples of temp tables are marked with forzen XID. Minimal changes, rollbacks are not possible.
3. Providing special XIDs for GTT at replica. No changes in CLOG are required, but special MVCC visibility rules are used for GTT. Current limitation: number of transactions accessing GTT at replica is limited by 2^32
and bitmap of correspondent size has to be maintained (tuples of GTT are not proceeded by vacuum and not frozen, so XID horizon never moved).

I again vote for #1. A GTT is defined to allow data to be visible only
within one session -- so what does it even mean for the data to be
accessible on a replica?

why not? there are lot of sessions on replica servers. One usage of temp tables is fixing estimation errors. You can create temp table with partial query result, run ANALYZE and evaluate other steps. Now this case is not possible on replica servers.

One motivation for GTT is decreasing port costs from Oracle. But other motivations, like do more complex calculations on replica are valid and valuable.

Hmm, I think I was slightly confused when I wrote my previous
response. I now see that what was under discussion was not making data
from the master visible on the standbys, which really wouldn't make
any sense, but rather allowing standby sessions to also use the GTT,
each with its own local copy of the data. I don't think that's a bad
feature, but look how invasive the required changes are. Not allowing
rollbacks seems dead on arrival; an abort would be able to leave the
table and index mutually inconsistent. A separate XID space would be
a real solution, perhaps, but it would be *extremely* complicated and
invasive to implement.

One thing that I've learned over and over again as a developer is that
you get a lot more done if you tackle one problem at a time. GTTs are
a sufficiently-large problem all by themselves; a major reworking of
the way XIDs work might be a good project to undertake at some point,
but it doesn't make any sense to incorporate that into the GTT
project, which is otherwise about a mostly-separate set of issues.
Let's not try to solve more problems at once than strictly necessary.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#12Robert Haas
robertmhaas@gmail.com
In reply to: Konstantin Knizhnik (#8)
Re: [Proposal] Global temporary tables

On Fri, Oct 25, 2019 at 12:22 PM Konstantin Knizhnik
<k.knizhnik@postgrespro.ru> wrote:

On 25.10.2019 18:01, Robert Haas wrote:

On Fri, Oct 11, 2019 at 9:50 AM Konstantin Knizhnik
<k.knizhnik@postgrespro.ru> wrote:

Just to clarify.
I have now proposed several different solutions for GTT:

Shared vs. private buffers for GTT:
1. Private buffers. This is least invasive patch, requiring no changes in relfilenodes.
2. Shared buffers. Requires changing relfilenode but supports parallel query execution for GTT.

I vote for #1. I think parallel query for temp objects may be a
desirable feature, but I don't think it should be the job of a patch
implementing GTTs to make it happen. In fact, I think it would be an
actively bad idea, because I suspect that if we do eventually support
temp relations for parallel query, we're going to want a solution that
is shared between regular temp tables and global temp tables, not
separate solutions for each.

Sorry, may be I do not not understand you.
It seems to me that there is only one thing preventing usage of
temporary tables in parallel plans: private buffers.
If global temporary tables are accessed as normal tables though shared
buffers then them can be used in parallel queries
and no extra support is required for it.
At least I have checked that parallel queries are correctly worked for
my implementation of GTT with shared buffers.
So I do not understand about which "separate solutions" you are talking
about.

I can agree that private buffers may be good starting point for GTT
implementation, because it is less invasive and GTT access speed is
exactly the same as of normal temp tables.
But I do not understand your argument why it is "actively bad idea".

Well, it sounds like you're talking about ending up in a situation
where local temporary tables are still in private buffers, but global
temporary table data is in shared buffers. I think that would be
inconsistent. And it would mean that when somebody wanted to make
local temporary tables accessible in parallel query, they'd have to
write a patch for that. In other words, I don't support dividing the
patches like this:

Patch #1: Support global temporary tables + allow global temporary
tables to used by parallel query
Patch #2: Allow local temporary tables to be used by parallel query

I support dividing them like this:

Patch #1: Support global temporary tables
Patch #2: Allow (all kinds of) temporary tables to be used by parallel query

The second division looks a lot cleaner to me, although as always I
might be missing something.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#13Konstantin Knizhnik
k.knizhnik@postgrespro.ru
In reply to: Robert Haas (#11)
1 attachment(s)
Re: [Proposal] Global temporary tables

On 28.10.2019 15:07, Robert Haas wrote:

On Fri, Oct 25, 2019 at 11:14 AM Pavel Stehule <pavel.stehule@gmail.com> wrote:

Access to GTT at replica:
1. Access is prohibited (as for original temp tables). No changes at all.
2. Tuples of temp tables are marked with forzen XID. Minimal changes, rollbacks are not possible.
3. Providing special XIDs for GTT at replica. No changes in CLOG are required, but special MVCC visibility rules are used for GTT. Current limitation: number of transactions accessing GTT at replica is limited by 2^32
and bitmap of correspondent size has to be maintained (tuples of GTT are not proceeded by vacuum and not frozen, so XID horizon never moved).

I again vote for #1. A GTT is defined to allow data to be visible only
within one session -- so what does it even mean for the data to be
accessible on a replica?

why not? there are lot of sessions on replica servers. One usage of temp tables is fixing estimation errors. You can create temp table with partial query result, run ANALYZE and evaluate other steps. Now this case is not possible on replica servers.

One motivation for GTT is decreasing port costs from Oracle. But other motivations, like do more complex calculations on replica are valid and valuable.

Hmm, I think I was slightly confused when I wrote my previous
response. I now see that what was under discussion was not making data
from the master visible on the standbys, which really wouldn't make
any sense, but rather allowing standby sessions to also use the GTT,
each with its own local copy of the data. I don't think that's a bad
feature, but look how invasive the required changes are. Not allowing
rollbacks seems dead on arrival; an abort would be able to leave the
table and index mutually inconsistent. A separate XID space would be
a real solution, perhaps, but it would be *extremely* complicated and
invasive to implement.

Sorry, but both statements are not true.
As I mentioned before, I have implemented both solutions.

I am not sure how vital is lack of aborts for transactions working with
GTT at replica.
Some people said that there is no sense in aborts of read-only
transactions at replica (despite to the fact that them are saving
intermediate results in GTT).
Some people said something similar with your's "dead on arrival".
But inconsistency is not possible: if such transaction is really
aborted, then backend is terminated and nobody can see this inconsistency.

Concerning second alternative: you can check yourself that it is not
*extremely* complicated and invasive.
I extracted changes which are related with handling transactions at
replica and attached them to this mail.
It is just 500 lines (including diff contexts). Certainly there are some
limitation of this implementation: number of  transactions working with
GTT at replica is limited by 2^32
and since GTT tuples are not frozen, analog of GTT CLOG kept in memory
is never truncated.

One thing that I've learned over and over again as a developer is that
you get a lot more done if you tackle one problem at a time. GTTs are
a sufficiently-large problem all by themselves; a major reworking of
the way XIDs work might be a good project to undertake at some point,
but it doesn't make any sense to incorporate that into the GTT
project, which is otherwise about a mostly-separate set of issues.
Let's not try to solve more problems at once than strictly necessary.

I agree with it and think that implementation of GTT with private
buffers and no replica access is good starting point.

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

Attachments:

replica-gtt-xact.difftext/x-patch; name=replica-gtt-xact.diffDownload
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index e954482..2f5f5ef 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -444,7 +444,7 @@ heapgetpage(TableScanDesc sscan, BlockNumber page)
 			if (all_visible)
 				valid = true;
 			else
-				valid = HeapTupleSatisfiesVisibility(&loctup, snapshot, buffer);
+				valid = HeapTupleSatisfiesVisibility(scan->rs_base.rs_rd, &loctup, snapshot, buffer);
 
 			CheckForSerializableConflictOut(valid, scan->rs_base.rs_rd,
 											&loctup, buffer, snapshot);
@@ -664,7 +664,8 @@ heapgettup(HeapScanDesc scan,
 				/*
 				 * if current tuple qualifies, return it.
 				 */
-				valid = HeapTupleSatisfiesVisibility(tuple,
+				valid = HeapTupleSatisfiesVisibility(scan->rs_base.rs_rd,
+													 tuple,
 													 snapshot,
 													 scan->rs_cbuf);
 
@@ -1474,7 +1475,7 @@ heap_fetch(Relation relation,
 	/*
 	 * check tuple visibility, then release lock
 	 */
-	valid = HeapTupleSatisfiesVisibility(tuple, snapshot, buffer);
+	valid = HeapTupleSatisfiesVisibility(relation, tuple, snapshot, buffer);
 
 	if (valid)
 		PredicateLockTuple(relation, tuple, snapshot);
@@ -1609,7 +1610,7 @@ heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
 		if (!skip)
 		{
 			/* If it's visible per the snapshot, we must return it */
-			valid = HeapTupleSatisfiesVisibility(heapTuple, snapshot, buffer);
+			valid = HeapTupleSatisfiesVisibility(relation, heapTuple, snapshot, buffer);
 			CheckForSerializableConflictOut(valid, relation, heapTuple,
 											buffer, snapshot);
 
@@ -1749,7 +1750,7 @@ heap_get_latest_tid(TableScanDesc sscan,
 		 * Check tuple visibility; if visible, set it as the new result
 		 * candidate.
 		 */
-		valid = HeapTupleSatisfiesVisibility(&tp, snapshot, buffer);
+		valid = HeapTupleSatisfiesVisibility(relation, &tp, snapshot, buffer);
 		CheckForSerializableConflictOut(valid, relation, &tp, buffer, snapshot);
 		if (valid)
 			*tid = ctid;
@@ -1846,6 +1847,14 @@ ReleaseBulkInsertStatePin(BulkInsertState bistate)
 }
 
 
+static TransactionId
+GetTransactionId(Relation relation)
+{
+	return relation->rd_rel->relpersistence == RELPERSISTENCE_SESSION && RecoveryInProgress()
+		? GetReplicaTransactionId()
+		: GetCurrentTransactionId();
+}
+
 /*
  *	heap_insert		- insert tuple into a heap
  *
@@ -1868,7 +1877,7 @@ void
 heap_insert(Relation relation, HeapTuple tup, CommandId cid,
 			int options, BulkInsertState bistate)
 {
-	TransactionId xid = GetCurrentTransactionId();
+	TransactionId xid = GetTransactionId(relation);
 	HeapTuple	heaptup;
 	Buffer		buffer;
 	Buffer		vmbuffer = InvalidBuffer;
@@ -2105,7 +2114,7 @@ void
 heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
 				  CommandId cid, int options, BulkInsertState bistate)
 {
-	TransactionId xid = GetCurrentTransactionId();
+	TransactionId xid = GetTransactionId(relation);
 	HeapTuple  *heaptuples;
 	int			i;
 	int			ndone;
@@ -2444,7 +2453,7 @@ heap_delete(Relation relation, ItemPointer tid,
 			TM_FailureData *tmfd, bool changingPart)
 {
 	TM_Result	result;
-	TransactionId xid = GetCurrentTransactionId();
+	TransactionId xid = GetTransactionId(relation);
 	ItemId		lp;
 	HeapTupleData tp;
 	Page		page;
@@ -2509,7 +2518,7 @@ heap_delete(Relation relation, ItemPointer tid,
 	tp.t_self = *tid;
 
 l1:
-	result = HeapTupleSatisfiesUpdate(&tp, cid, buffer);
+	result = HeapTupleSatisfiesUpdate(relation, &tp, cid, buffer);
 
 	if (result == TM_Invisible)
 	{
@@ -2628,7 +2637,7 @@ l1:
 	if (crosscheck != InvalidSnapshot && result == TM_Ok)
 	{
 		/* Perform additional check for transaction-snapshot mode RI updates */
-		if (!HeapTupleSatisfiesVisibility(&tp, crosscheck, buffer))
+		if (!HeapTupleSatisfiesVisibility(relation, &tp, crosscheck, buffer))
 			result = TM_Updated;
 	}
 
@@ -2895,7 +2904,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 			TM_FailureData *tmfd, LockTupleMode *lockmode)
 {
 	TM_Result	result;
-	TransactionId xid = GetCurrentTransactionId();
+	TransactionId xid = GetTransactionId(relation);
 	Bitmapset  *hot_attrs;
 	Bitmapset  *key_attrs;
 	Bitmapset  *id_attrs;
@@ -3065,7 +3074,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 l2:
 	checked_lockers = false;
 	locker_remains = false;
-	result = HeapTupleSatisfiesUpdate(&oldtup, cid, buffer);
+	result = HeapTupleSatisfiesUpdate(relation, &oldtup, cid, buffer);
 
 	/* see below about the "no wait" case */
 	Assert(result != TM_BeingModified || wait);
@@ -3258,7 +3267,7 @@ l2:
 	if (crosscheck != InvalidSnapshot && result == TM_Ok)
 	{
 		/* Perform additional check for transaction-snapshot mode RI updates */
-		if (!HeapTupleSatisfiesVisibility(&oldtup, crosscheck, buffer))
+		if (!HeapTupleSatisfiesVisibility(relation, &oldtup, crosscheck, buffer))
 		{
 			result = TM_Updated;
 			Assert(!ItemPointerEquals(&oldtup.t_self, &oldtup.t_data->t_ctid));
@@ -4014,7 +4023,7 @@ heap_lock_tuple(Relation relation, HeapTuple tuple,
 	tuple->t_tableOid = RelationGetRelid(relation);
 
 l3:
-	result = HeapTupleSatisfiesUpdate(tuple, cid, *buffer);
+	result = HeapTupleSatisfiesUpdate(relation, tuple, cid, *buffer);
 
 	if (result == TM_Invisible)
 	{
@@ -4189,7 +4198,7 @@ l3:
 					TM_Result	res;
 
 					res = heap_lock_updated_tuple(relation, tuple, &t_ctid,
-												  GetCurrentTransactionId(),
+												  GetTransactionId(relation),
 												  mode);
 					if (res != TM_Ok)
 					{
@@ -4437,7 +4446,7 @@ l3:
 				TM_Result	res;
 
 				res = heap_lock_updated_tuple(relation, tuple, &t_ctid,
-											  GetCurrentTransactionId(),
+											  GetTransactionId(relation),
 											  mode);
 				if (res != TM_Ok)
 				{
@@ -4546,7 +4555,7 @@ failed:
 	 * state if multixact.c elogs.
 	 */
 	compute_new_xmax_infomask(xmax, old_infomask, tuple->t_data->t_infomask2,
-							  GetCurrentTransactionId(), mode, false,
+							  GetTransactionId(relation), mode, false,
 							  &xid, &new_infomask, &new_infomask2);
 
 	START_CRIT_SECTION();
@@ -5566,7 +5575,7 @@ heap_finish_speculative(Relation relation, ItemPointer tid)
 void
 heap_abort_speculative(Relation relation, ItemPointer tid)
 {
-	TransactionId xid = GetCurrentTransactionId();
+	TransactionId xid = GetTransactionId(relation);
 	ItemId		lp;
 	HeapTupleData tp;
 	Page		page;
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 2dd8821..67a553a 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -226,7 +226,8 @@ heapam_tuple_satisfies_snapshot(Relation rel, TupleTableSlot *slot,
 	 * Caller should be holding pin, but not lock.
 	 */
 	LockBuffer(bslot->buffer, BUFFER_LOCK_SHARE);
-	res = HeapTupleSatisfiesVisibility(bslot->base.tuple, snapshot,
+
+	res = HeapTupleSatisfiesVisibility(rel, bslot->base.tuple, snapshot,
 									   bslot->buffer);
 	LockBuffer(bslot->buffer, BUFFER_LOCK_UNLOCK);
 
@@ -673,6 +674,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 			 * init fork of an unlogged relation.
 			 */
 			if (rel->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT ||
+				rel->rd_rel->relpersistence == RELPERSISTENCE_SESSION ||
 				(rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED &&
 				 forkNum == INIT_FORKNUM))
 				log_smgrcreate(newrnode, forkNum);
@@ -2162,7 +2164,7 @@ heapam_scan_bitmap_next_block(TableScanDesc scan,
 			loctup.t_len = ItemIdGetLength(lp);
 			loctup.t_tableOid = scan->rs_rd->rd_id;
 			ItemPointerSet(&loctup.t_self, page, offnum);
-			valid = HeapTupleSatisfiesVisibility(&loctup, snapshot, buffer);
+			valid = HeapTupleSatisfiesVisibility(scan->rs_rd, &loctup, snapshot, buffer);
 			if (valid)
 			{
 				hscan->rs_vistuples[ntup++] = offnum;
@@ -2482,7 +2484,7 @@ SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 	else
 	{
 		/* Otherwise, we have to check the tuple individually. */
-		return HeapTupleSatisfiesVisibility(tuple, scan->rs_snapshot,
+		return HeapTupleSatisfiesVisibility(scan->rs_rd, tuple, scan->rs_snapshot,
 											buffer);
 	}
 }
diff --git a/src/backend/access/heap/heapam_visibility.c b/src/backend/access/heap/heapam_visibility.c
index 537e681..3076f6a 100644
--- a/src/backend/access/heap/heapam_visibility.c
+++ b/src/backend/access/heap/heapam_visibility.c
@@ -77,6 +77,7 @@
 #include "utils/combocid.h"
 #include "utils/snapmgr.h"
 
+static bool TempTupleSatisfiesVisibility(HeapTuple htup, CommandId curcid, Buffer buffer);
 
 /*
  * SetHintBits()
@@ -454,7 +455,7 @@ HeapTupleSatisfiesToast(HeapTuple htup, Snapshot snapshot,
  *	test for it themselves.)
  */
 TM_Result
-HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid,
+HeapTupleSatisfiesUpdate(Relation relation, HeapTuple htup, CommandId curcid,
 						 Buffer buffer)
 {
 	HeapTupleHeader tuple = htup->t_data;
@@ -462,6 +463,13 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid,
 	Assert(ItemPointerIsValid(&htup->t_self));
 	Assert(htup->t_tableOid != InvalidOid);
 
+	if (relation->rd_rel->relpersistence == RELPERSISTENCE_SESSION && RecoveryInProgress())
+	{
+		AccessTempRelationAtReplica = true;
+		return TempTupleSatisfiesVisibility(htup, curcid, buffer) ? TM_Ok : TM_Invisible;
+	}
+	AccessTempRelationAtReplica = false;
+
 	if (!HeapTupleHeaderXminCommitted(tuple))
 	{
 		if (HeapTupleHeaderXminInvalid(tuple))
@@ -1677,6 +1685,59 @@ HeapTupleSatisfiesHistoricMVCC(HeapTuple htup, Snapshot snapshot,
 }
 
 /*
+ * TempTupleSatisfiesVisibility
+ *		True iff global temp table tuple is visible for the current transaction.
+ *
+ * Temporary tables are visible only for current backend, so there is no need to
+ * handle cases with tuples committed by other backends. We only need to exclude
+ * modifications done by aborted transactions or after start of table scan.
+ *
+ */
+static bool
+TempTupleSatisfiesVisibility(HeapTuple htup, CommandId curcid, Buffer buffer)
+{
+	HeapTupleHeader tuple = htup->t_data;
+	TransactionId xmin;
+	TransactionId xmax;
+
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
+
+	if (HeapTupleHeaderXminInvalid(tuple))
+		return false;
+
+	xmin = HeapTupleHeaderGetRawXmin(tuple);
+
+	if (IsReplicaTransactionAborted(xmin))
+		return false;
+
+	if (IsReplicaCurrentTransactionId(xmin)
+		&& HeapTupleHeaderGetCmin(tuple) >= curcid)
+	{
+		return false;	/* inserted after scan started */
+	}
+
+	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
+		return true;
+
+	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))	/* not deleter */
+		return true;
+
+	xmax = (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+	    ? HeapTupleGetUpdateXid(tuple)
+		: HeapTupleHeaderGetRawXmax(tuple);
+
+	if (IsReplicaTransactionAborted(xmax))
+		return true; /* updating subtransaction aborted */
+
+	if (!IsReplicaCurrentTransactionId(xmax))
+		return false; /* updating transaction committed */
+
+	return (HeapTupleHeaderGetCmax(tuple) >= curcid);	/* updated after scan started */
+}
+
+
+/*
  * HeapTupleSatisfiesVisibility
  *		True iff heap tuple satisfies a time qual.
  *
@@ -1687,8 +1748,15 @@ HeapTupleSatisfiesHistoricMVCC(HeapTuple htup, Snapshot snapshot,
  *	if so, the indicated buffer is marked dirty.
  */
 bool
-HeapTupleSatisfiesVisibility(HeapTuple tup, Snapshot snapshot, Buffer buffer)
+HeapTupleSatisfiesVisibility(Relation relation, HeapTuple tup, Snapshot snapshot, Buffer buffer)
 {
+	if (relation->rd_rel->relpersistence == RELPERSISTENCE_SESSION && RecoveryInProgress())
+	{
+		AccessTempRelationAtReplica = true;
+		return TempTupleSatisfiesVisibility(tup, snapshot->curcid, buffer);
+	}
+	AccessTempRelationAtReplica = false;
+
 	switch (snapshot->snapshot_type)
 	{
 		case SNAPSHOT_MVCC:
diff --git a/src/backend/access/transam/transam.c b/src/backend/access/transam/transam.c
index 365ddfb..bce9c4a 100644
--- a/src/backend/access/transam/transam.c
+++ b/src/backend/access/transam/transam.c
@@ -22,6 +22,7 @@
 #include "access/clog.h"
 #include "access/subtrans.h"
 #include "access/transam.h"
+#include "access/xact.h"
 #include "utils/snapmgr.h"
 
 /*
@@ -126,6 +127,9 @@ TransactionIdDidCommit(TransactionId transactionId)
 {
 	XidStatus	xidstatus;
 
+	if (AccessTempRelationAtReplica)
+		return !IsReplicaCurrentTransactionId(transactionId) && !IsReplicaTransactionAborted(transactionId);
+
 	xidstatus = TransactionLogFetch(transactionId);
 
 	/*
@@ -182,6 +186,9 @@ TransactionIdDidAbort(TransactionId transactionId)
 {
 	XidStatus	xidstatus;
 
+	if (AccessTempRelationAtReplica)
+		return IsReplicaTransactionAborted(transactionId);
+
 	xidstatus = TransactionLogFetch(transactionId);
 
 	/*
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 9162286..cf6bf4e 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -192,6 +192,7 @@ typedef struct TransactionStateData
 	int			parallelModeLevel;	/* Enter/ExitParallelMode counter */
 	bool		chain;			/* start a new block after this one */
 	struct TransactionStateData *parent;	/* back link to parent */
+	TransactionId replicaTransactionId;    /* pseudo XID for inserting data in global temp tables at replica */
 } TransactionStateData;
 
 typedef TransactionStateData *TransactionState;
@@ -286,6 +287,12 @@ typedef struct XactCallbackItem
 
 static XactCallbackItem *Xact_callbacks = NULL;
 
+static TransactionId replicaTransIdCount = FirstNormalTransactionId;
+static TransactionId replicaTopTransId;
+static Bitmapset*    replicaAbortedXids;
+
+bool AccessTempRelationAtReplica;
+
 /*
  * List of add-on start- and end-of-subxact callbacks
  */
@@ -443,6 +450,48 @@ GetCurrentTransactionIdIfAny(void)
 }
 
 /*
+ * Transactions at replica can update only global temporary tables.
+ * Them are assigned backend-local XIDs which are independent from normal XIDs received from primary node.
+ * So tuples of temporary tables at replica requires special visibility rules.
+ *
+ * XIDs for such transactions at replica are created on demand (when tuple of temp table is updated).
+ * XID wrap-around and adjusting XID horizon is not supported. So number of such transactions at replica is
+ * limited by 2^32 and require up to 2^29 in-memory bitmap for marking aborted transactions.
+  */
+TransactionId
+GetReplicaTransactionId(void)
+{
+	TransactionState s = CurrentTransactionState;
+	if (!TransactionIdIsValid(s->replicaTransactionId))
+		s->replicaTransactionId = ++replicaTransIdCount;
+	return s->replicaTransactionId;
+}
+
+/*
+ * At replica transaction can update only temporary tables
+ * and them are assigned special XIDs (not related with normal XIDs received from primary node).
+ * As far as we see only own transaction it is not necessary to mark committed transactions.
+ * Only marking aborted ones is enough. All transactions which are not marked as aborted are treated as
+ * committed or self in-progress transactions.
+ */
+bool
+IsReplicaTransactionAborted(TransactionId xid)
+{
+	return bms_is_member(xid, replicaAbortedXids);
+}
+
+/*
+ * As far as XIDs for transactions at replica are generated individually for each backends,
+ * we can check that XID belongs to the current transaction or any of its subtransactions by
+ * just comparing it with XID of top transaction.
+ */
+bool
+IsReplicaCurrentTransactionId(TransactionId xid)
+{
+	return xid > replicaTopTransId;
+}
+
+/*
  *	GetTopFullTransactionId
  *
  * This will return the FullTransactionId of the main transaction, assigning
@@ -855,6 +904,9 @@ TransactionIdIsCurrentTransactionId(TransactionId xid)
 {
 	TransactionState s;
 
+	if (AccessTempRelationAtReplica)
+		return IsReplicaCurrentTransactionId(xid);
+
 	/*
 	 * We always say that BootstrapTransactionId is "not my transaction ID"
 	 * even when it is (ie, during bootstrap).  Along with the fact that
@@ -1206,7 +1258,7 @@ static TransactionId
 RecordTransactionCommit(void)
 {
 	TransactionId xid = GetTopTransactionIdIfAny();
-	bool		markXidCommitted = TransactionIdIsValid(xid);
+	bool		markXidCommitted = TransactionIdIsNormal(xid);
 	TransactionId latestXid = InvalidTransactionId;
 	int			nrels;
 	RelFileNode *rels;
@@ -1624,7 +1676,7 @@ RecordTransactionAbort(bool isSubXact)
 	 * rels to delete (note that this routine is not responsible for actually
 	 * deleting 'em).  We cannot have any child XIDs, either.
 	 */
-	if (!TransactionIdIsValid(xid))
+	if (!TransactionIdIsNormal(xid))
 	{
 		/* Reset XactLastRecEnd until the next transaction writes something */
 		if (!isSubXact)
@@ -1892,6 +1944,8 @@ StartTransaction(void)
 	s = &TopTransactionStateData;
 	CurrentTransactionState = s;
 
+	replicaTopTransId = replicaTransIdCount;
+
 	Assert(!FullTransactionIdIsValid(XactTopFullTransactionId));
 
 	/* check the current transaction state */
@@ -1905,6 +1959,7 @@ StartTransaction(void)
 	 */
 	s->state = TRANS_START;
 	s->fullTransactionId = InvalidFullTransactionId;	/* until assigned */
+	s->replicaTransactionId = InvalidTransactionId;	/* until assigned */
 
 	/* Determine if statements are logged in this transaction */
 	xact_is_sampled = log_xact_sample_rate != 0 &&
@@ -2570,6 +2625,14 @@ AbortTransaction(void)
 	/* Prevent cancel/die interrupt while cleaning up */
 	HOLD_INTERRUPTS();
 
+	/* Mark transactions involved global temp table at replica as aborted */
+	if (TransactionIdIsValid(s->replicaTransactionId))
+	{
+		MemoryContext ctx = MemoryContextSwitchTo(TopMemoryContext);
+		replicaAbortedXids = bms_add_member(replicaAbortedXids, s->replicaTransactionId);
+		MemoryContextSwitchTo(ctx);
+	}
+
 	/* Make sure we have a valid memory context and resource owner */
 	AtAbort_Memory();
 	AtAbort_ResourceOwner();
@@ -2991,6 +3054,9 @@ CommitTransactionCommand(void)
 			 * and then clean up.
 			 */
 		case TBLOCK_ABORT_PENDING:
+			if (GetCurrentTransactionIdIfAny() == FrozenTransactionId)
+				elog(FATAL, "Transaction is aborted at standby");
+
 			AbortTransaction();
 			CleanupTransaction();
 			s->blockState = TBLOCK_DEFAULT;
@@ -4880,6 +4946,14 @@ AbortSubTransaction(void)
 	/* Prevent cancel/die interrupt while cleaning up */
 	HOLD_INTERRUPTS();
 
+	/* Mark transactions involved global temp table at replica as aborted */
+	if (TransactionIdIsValid(s->replicaTransactionId))
+	{
+		MemoryContext ctx = MemoryContextSwitchTo(TopMemoryContext);
+		replicaAbortedXids = bms_add_member(replicaAbortedXids, s->replicaTransactionId);
+		MemoryContextSwitchTo(ctx);
+	}
+
 	/* Make sure we have a valid memory context and resource owner */
 	AtSubAbort_Memory();
 	AtSubAbort_ResourceOwner();
#14Konstantin Knizhnik
k.knizhnik@postgrespro.ru
In reply to: Robert Haas (#12)
Re: [Proposal] Global temporary tables

On 28.10.2019 15:13, Robert Haas wrote:

On Fri, Oct 25, 2019 at 12:22 PM Konstantin Knizhnik
<k.knizhnik@postgrespro.ru> wrote:

On 25.10.2019 18:01, Robert Haas wrote:

On Fri, Oct 11, 2019 at 9:50 AM Konstantin Knizhnik
<k.knizhnik@postgrespro.ru> wrote:

Just to clarify.
I have now proposed several different solutions for GTT:

Shared vs. private buffers for GTT:
1. Private buffers. This is least invasive patch, requiring no changes in relfilenodes.
2. Shared buffers. Requires changing relfilenode but supports parallel query execution for GTT.

I vote for #1. I think parallel query for temp objects may be a
desirable feature, but I don't think it should be the job of a patch
implementing GTTs to make it happen. In fact, I think it would be an
actively bad idea, because I suspect that if we do eventually support
temp relations for parallel query, we're going to want a solution that
is shared between regular temp tables and global temp tables, not
separate solutions for each.

Sorry, may be I do not not understand you.
It seems to me that there is only one thing preventing usage of
temporary tables in parallel plans: private buffers.
If global temporary tables are accessed as normal tables though shared
buffers then them can be used in parallel queries
and no extra support is required for it.
At least I have checked that parallel queries are correctly worked for
my implementation of GTT with shared buffers.
So I do not understand about which "separate solutions" you are talking
about.

I can agree that private buffers may be good starting point for GTT
implementation, because it is less invasive and GTT access speed is
exactly the same as of normal temp tables.
But I do not understand your argument why it is "actively bad idea".

Well, it sounds like you're talking about ending up in a situation
where local temporary tables are still in private buffers, but global
temporary table data is in shared buffers. I think that would be
inconsistent. And it would mean that when somebody wanted to make
local temporary tables accessible in parallel query, they'd have to
write a patch for that. In other words, I don't support dividing the
patches like this:

Patch #1: Support global temporary tables + allow global temporary
tables to used by parallel query
Patch #2: Allow local temporary tables to be used by parallel query

I support dividing them like this:

Patch #1: Support global temporary tables
Patch #2: Allow (all kinds of) temporary tables to be used by parallel query

The second division looks a lot cleaner to me, although as always I
might be missing something.

Logically it may be good decision. But piratically support of parallel
access to GTT requires just accessing their data through shared buffer.
But in case of local temp tables we need also need to some how share
table's metadata between parallel workers. It seems to be much more
complicated if ever possible.

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

#15Robert Haas
robertmhaas@gmail.com
In reply to: Konstantin Knizhnik (#14)
Re: [Proposal] Global temporary tables

On Mon, Oct 28, 2019 at 9:48 AM Konstantin Knizhnik
<k.knizhnik@postgrespro.ru> wrote:

Logically it may be good decision. But piratically support of parallel
access to GTT requires just accessing their data through shared buffer.
But in case of local temp tables we need also need to some how share
table's metadata between parallel workers. It seems to be much more
complicated if ever possible.

Why? The backends all share a snapshot, and can load whatever they
need from the system catalogs.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#16Robert Haas
robertmhaas@gmail.com
In reply to: Konstantin Knizhnik (#13)
Re: [Proposal] Global temporary tables

On Mon, Oct 28, 2019 at 9:37 AM Konstantin Knizhnik
<k.knizhnik@postgrespro.ru> wrote:

Sorry, but both statements are not true.

Well, I think they are true.

I am not sure how vital is lack of aborts for transactions working with
GTT at replica.
Some people said that there is no sense in aborts of read-only
transactions at replica (despite to the fact that them are saving
intermediate results in GTT).
Some people said something similar with your's "dead on arrival".
But inconsistency is not possible: if such transaction is really
aborted, then backend is terminated and nobody can see this inconsistency.

Aborting the current transaction is a very different thing from
terminating the backend.

Also, the idea that there is no sense in aborts of read-only
transactions on a replica seems totally wrong. Suppose that you insert
a row into the table and then you go to insert a row in each index,
but one of the index inserts fails - duplicate key, out of memory
error, I/O error, whatever. Now the table and the index are
inconsistent. Normally, we're protected against this by MVCC, but if
you use a solution that breaks MVCC by using the same XID for all
transactions, then it can happen.

Concerning second alternative: you can check yourself that it is not
*extremely* complicated and invasive.
I extracted changes which are related with handling transactions at
replica and attached them to this mail.
It is just 500 lines (including diff contexts). Certainly there are some
limitation of this implementation: number of transactions working with
GTT at replica is limited by 2^32
and since GTT tuples are not frozen, analog of GTT CLOG kept in memory
is never truncated.

I admit that this patch is not lengthy, but there remains the question
of whether it is correct. It's possible that the problem isn't as
complicated as I think it is, but I do think there are quite a number
of reasons why this patch wouldn't be considered acceptable...

I agree with it and think that implementation of GTT with private
buffers and no replica access is good starting point.

...but given that we seem to agree on this point, perhaps it isn't
necessary to argue about those things right now.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#17Konstantin Knizhnik
k.knizhnik@postgrespro.ru
In reply to: Robert Haas (#16)
1 attachment(s)
Re: [Proposal] Global temporary tables

On 28.10.2019 19:40, Robert Haas wrote:

Aborting the current transaction is a very different thing from
terminating the backend.

Also, the idea that there is no sense in aborts of read-only
transactions on a replica seems totally wrong. Suppose that you insert
a row into the table and then you go to insert a row in each index,
but one of the index inserts fails - duplicate key, out of memory
error, I/O error, whatever. Now the table and the index are
inconsistent. Normally, we're protected against this by MVCC, but if
you use a solution that breaks MVCC by using the same XID for all
transactions, then it can happen.

Certainly I understand the difference between abort of transaction and
termination of backend.
I do not say that it is good solution. And definitely aborts can happen
for read-only transactions.
I just wanted to express one moment: transaction aborts are caused by
two reasons:
- expected programming errors: deadlocks, conversion errors, unique
constraint violation,...
- unexpected system errors: disk space exhaustion, out of memory, I/O
errors...

Usually at replica with read-only transactions we do not have to deal
with errors of first kind.
So transaction may be aborted, but such abort most likely means that
something is wrong with the system
and restart of backend is not so bad solution in this situation.

In any case, I do not insist on this "frozen XID" approach.
The only advantage of this approach is that it is very simple to
implement: correspondent patch contains just 80 lines of code
and actually it requires just 5 (five) one-line changes.
I didn't agree with your statement just because restart of backend makes
it not possible to observe any inconsistencies in the database.

...but given that we seem to agree on this point, perhaps it isn't
necessary to argue about those things right now.

Ok.
I attached new patch for GTT with local (private) buffer and no replica
access.
It provides GTT for all built-in indexes

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

Attachments:

global_private_temp-3.patchtext/x-patch; name=global_private_temp-3.patchDownload
diff --git a/src/backend/access/brin/brin_revmap.c b/src/backend/access/brin/brin_revmap.c
index 647350c..ca5f22d 100644
--- a/src/backend/access/brin/brin_revmap.c
+++ b/src/backend/access/brin/brin_revmap.c
@@ -25,6 +25,7 @@
 #include "access/brin_revmap.h"
 #include "access/brin_tuple.h"
 #include "access/brin_xlog.h"
+#include "access/brin.h"
 #include "access/rmgr.h"
 #include "access/xloginsert.h"
 #include "miscadmin.h"
@@ -79,6 +80,11 @@ brinRevmapInitialize(Relation idxrel, BlockNumber *pagesPerRange,
 	meta = ReadBuffer(idxrel, BRIN_METAPAGE_BLKNO);
 	LockBuffer(meta, BUFFER_LOCK_SHARE);
 	page = BufferGetPage(meta);
+
+	if (GlobalTempRelationPageIsNotInitialized(idxrel, page))
+		brin_metapage_init(page, BrinGetPagesPerRange(idxrel),
+						   BRIN_CURRENT_VERSION);
+
 	TestForOldSnapshot(snapshot, idxrel, page);
 	metadata = (BrinMetaPageData *) PageGetContents(page);
 
diff --git a/src/backend/access/gin/ginfast.c b/src/backend/access/gin/ginfast.c
index 439a91b..8a6ac71 100644
--- a/src/backend/access/gin/ginfast.c
+++ b/src/backend/access/gin/ginfast.c
@@ -241,6 +241,16 @@ ginHeapTupleFastInsert(GinState *ginstate, GinTupleCollector *collector)
 	metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO);
 	metapage = BufferGetPage(metabuffer);
 
+	if (GlobalTempRelationPageIsNotInitialized(index, metapage))
+	{
+		Buffer rootbuffer = ReadBuffer(index, GIN_ROOT_BLKNO);
+		LockBuffer(rootbuffer, BUFFER_LOCK_EXCLUSIVE);
+		GinInitMetabuffer(metabuffer);
+		GinInitBuffer(rootbuffer, GIN_LEAF);
+		MarkBufferDirty(rootbuffer);
+		UnlockReleaseBuffer(rootbuffer);
+	}
+
 	/*
 	 * An insertion to the pending list could logically belong anywhere in the
 	 * tree, so it conflicts with all serializable scans.  All scans acquire a
diff --git a/src/backend/access/gin/ginget.c b/src/backend/access/gin/ginget.c
index b18ae2b..41bab5d 100644
--- a/src/backend/access/gin/ginget.c
+++ b/src/backend/access/gin/ginget.c
@@ -1750,7 +1750,7 @@ collectMatchesForHeapRow(IndexScanDesc scan, pendingPosition *pos)
 /*
  * Collect all matched rows from pending list into bitmap.
  */
-static void
+static bool
 scanPendingInsert(IndexScanDesc scan, TIDBitmap *tbm, int64 *ntids)
 {
 	GinScanOpaque so = (GinScanOpaque) scan->opaque;
@@ -1774,6 +1774,12 @@ scanPendingInsert(IndexScanDesc scan, TIDBitmap *tbm, int64 *ntids)
 	LockBuffer(metabuffer, GIN_SHARE);
 	page = BufferGetPage(metabuffer);
 	TestForOldSnapshot(scan->xs_snapshot, scan->indexRelation, page);
+
+	if (GlobalTempRelationPageIsNotInitialized(scan->indexRelation, page))
+	{
+		UnlockReleaseBuffer(metabuffer);
+		return false;
+	}
 	blkno = GinPageGetMeta(page)->head;
 
 	/*
@@ -1784,7 +1790,7 @@ scanPendingInsert(IndexScanDesc scan, TIDBitmap *tbm, int64 *ntids)
 	{
 		/* No pending list, so proceed with normal scan */
 		UnlockReleaseBuffer(metabuffer);
-		return;
+		return true;
 	}
 
 	pos.pendingBuffer = ReadBuffer(scan->indexRelation, blkno);
@@ -1840,6 +1846,7 @@ scanPendingInsert(IndexScanDesc scan, TIDBitmap *tbm, int64 *ntids)
 	}
 
 	pfree(pos.hasMatchKey);
+	return true;
 }
 
 
@@ -1875,7 +1882,8 @@ gingetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 	 * to scan the main index before the pending list, since concurrent
 	 * cleanup could then make us miss entries entirely.
 	 */
-	scanPendingInsert(scan, tbm, &ntids);
+	if (!scanPendingInsert(scan, tbm, &ntids))
+		return 0;
 
 	/*
 	 * Now scan the main index.
diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index 0cc8791..3215b6f 100644
--- a/src/backend/access/gist/gist.c
+++ b/src/backend/access/gist/gist.c
@@ -677,7 +677,10 @@ gistdoinsert(Relation r, IndexTuple itup, Size freespace,
 		if (!xlocked)
 		{
 			LockBuffer(stack->buffer, GIST_SHARE);
-			gistcheckpage(state.r, stack->buffer);
+			if (stack->blkno == GIST_ROOT_BLKNO && GlobalTempRelationPageIsNotInitialized(state.r, BufferGetPage(stack->buffer)))
+				GISTInitBuffer(stack->buffer, F_LEAF);
+			else
+				gistcheckpage(state.r, stack->buffer);
 		}
 
 		stack->page = (Page) BufferGetPage(stack->buffer);
diff --git a/src/backend/access/gist/gistget.c b/src/backend/access/gist/gistget.c
index 22d790d..4c52dbe 100644
--- a/src/backend/access/gist/gistget.c
+++ b/src/backend/access/gist/gistget.c
@@ -344,7 +344,10 @@ gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem,
 	buffer = ReadBuffer(scan->indexRelation, pageItem->blkno);
 	LockBuffer(buffer, GIST_SHARE);
 	PredicateLockPage(r, BufferGetBlockNumber(buffer), scan->xs_snapshot);
-	gistcheckpage(scan->indexRelation, buffer);
+	if (pageItem->blkno == GIST_ROOT_BLKNO && GlobalTempRelationPageIsNotInitialized(r, BufferGetPage(buffer)))
+		GISTInitBuffer(buffer, F_LEAF);
+	else
+		gistcheckpage(scan->indexRelation, buffer);
 	page = BufferGetPage(buffer);
 	TestForOldSnapshot(scan->xs_snapshot, r, page);
 	opaque = GistPageGetOpaque(page);
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index 45804d7..50b306a 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1028,7 +1028,7 @@ gistGetFakeLSN(Relation rel)
 {
 	static XLogRecPtr counter = FirstNormalUnloggedLSN;
 
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	if (RelationHasSessionScope(rel))
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hashpage.c b/src/backend/access/hash/hashpage.c
index 838ee68..4f794b3 100644
--- a/src/backend/access/hash/hashpage.c
+++ b/src/backend/access/hash/hashpage.c
@@ -75,13 +75,20 @@ _hash_getbuf(Relation rel, BlockNumber blkno, int access, int flags)
 
 	buf = ReadBuffer(rel, blkno);
 
-	if (access != HASH_NOLOCK)
-		LockBuffer(buf, access);
-
 	/* ref count and lock type are correct */
 
-	_hash_checkpage(rel, buf, flags);
-
+	if (blkno == HASH_METAPAGE && GlobalTempRelationPageIsNotInitialized(rel, BufferGetPage(buf)))
+	{
+		_hash_init(rel, 0, MAIN_FORKNUM);
+		if (access != HASH_NOLOCK)
+			LockBuffer(buf, access);
+	}
+	else
+	{
+		if (access != HASH_NOLOCK)
+			LockBuffer(buf, access);
+		_hash_checkpage(rel, buf, flags);
+	}
 	return buf;
 }
 
@@ -339,7 +346,7 @@ _hash_init(Relation rel, double num_tuples, ForkNumber forkNum)
 	bool		use_wal;
 
 	/* safety check */
-	if (RelationGetNumberOfBlocksInFork(rel, forkNum) != 0)
+	if (rel->rd_rel->relpersistence != RELPERSISTENCE_SESSION && RelationGetNumberOfBlocksInFork(rel, forkNum) != 0)
 		elog(ERROR, "cannot initialize non-empty hash index \"%s\"",
 			 RelationGetRelationName(rel));
 
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 2dd8821..92df373 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -673,6 +673,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 			 * init fork of an unlogged relation.
 			 */
 			if (rel->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT ||
+				rel->rd_rel->relpersistence == RELPERSISTENCE_SESSION ||
 				(rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED &&
 				 forkNum == INIT_FORKNUM))
 				log_smgrcreate(newrnode, forkNum);
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 268f869..ed3ab70 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -763,7 +763,11 @@ _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);
+		/* Session temporary relation may be not yet initialized for this backend. */
+		if (blkno == BTREE_METAPAGE && GlobalTempRelationPageIsNotInitialized(rel, BufferGetPage(buf)))
+			_bt_initmetapage(BufferGetPage(buf), P_NONE, 0);
+		else
+			_bt_checkpage(rel, buf);
 	}
 	else
 	{
diff --git a/src/backend/access/spgist/spgutils.c b/src/backend/access/spgist/spgutils.c
index 45472db..a8497a2 100644
--- a/src/backend/access/spgist/spgutils.c
+++ b/src/backend/access/spgist/spgutils.c
@@ -106,6 +106,7 @@ spgGetCache(Relation index)
 		spgConfigIn in;
 		FmgrInfo   *procinfo;
 		Buffer		metabuffer;
+		Page        metapage;
 		SpGistMetaPageData *metadata;
 
 		cache = MemoryContextAllocZero(index->rd_indexcxt,
@@ -155,12 +156,32 @@ spgGetCache(Relation index)
 		metabuffer = ReadBuffer(index, SPGIST_METAPAGE_BLKNO);
 		LockBuffer(metabuffer, BUFFER_LOCK_SHARE);
 
-		metadata = SpGistPageGetMeta(BufferGetPage(metabuffer));
+		metapage = BufferGetPage(metabuffer);
+		metadata = SpGistPageGetMeta(metapage);
 
 		if (metadata->magicNumber != SPGIST_MAGIC_NUMBER)
-			elog(ERROR, "index \"%s\" is not an SP-GiST index",
-				 RelationGetRelationName(index));
+		{
+			if (GlobalTempRelationPageIsNotInitialized(index, metapage))
+			{
+				Buffer rootbuffer = ReadBuffer(index, SPGIST_ROOT_BLKNO);
+				Buffer nullbuffer = ReadBuffer(index, SPGIST_NULL_BLKNO);
+
+				SpGistInitMetapage(metapage);
+
+				LockBuffer(rootbuffer, BUFFER_LOCK_EXCLUSIVE);
+				SpGistInitPage(BufferGetPage(rootbuffer), SPGIST_LEAF);
+				MarkBufferDirty(rootbuffer);
+				UnlockReleaseBuffer(rootbuffer);
 
+				LockBuffer(nullbuffer, BUFFER_LOCK_EXCLUSIVE);
+				SpGistInitPage(BufferGetPage(nullbuffer), SPGIST_LEAF | SPGIST_NULLS);
+				MarkBufferDirty(nullbuffer);
+				UnlockReleaseBuffer(nullbuffer);
+			}
+			else
+				elog(ERROR, "index \"%s\" is not an SP-GiST index",
+					 RelationGetRelationName(index));
+		}
 		cache->lastUsedPages = metadata->lastUsedPages;
 
 		UnlockReleaseBuffer(metabuffer);
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 1af31c2..e60bdb7 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -402,6 +402,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 		case RELPERSISTENCE_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
+		case RELPERSISTENCE_SESSION:
+			backend = BackendIdForSessionRelations();
+			break;
 		case RELPERSISTENCE_UNLOGGED:
 		case RELPERSISTENCE_PERMANENT:
 			backend = InvalidBackendId;
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index f6c31cc..d943b57 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -3652,7 +3652,7 @@ reindex_relation(Oid relid, int flags, int options)
 		if (flags & REINDEX_REL_FORCE_INDEXES_UNLOGGED)
 			persistence = RELPERSISTENCE_UNLOGGED;
 		else if (flags & REINDEX_REL_FORCE_INDEXES_PERMANENT)
-			persistence = RELPERSISTENCE_PERMANENT;
+			persistence = rel->rd_rel->relpersistence == RELPERSISTENCE_SESSION ? RELPERSISTENCE_SESSION : RELPERSISTENCE_PERMANENT;
 		else
 			persistence = rel->rd_rel->relpersistence;
 
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index 625af8d..1e192fa 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -93,6 +93,10 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 			backend = InvalidBackendId;
 			needs_wal = false;
 			break;
+		case RELPERSISTENCE_SESSION:
+			backend = BackendIdForSessionRelations();
+			needs_wal = false;
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			backend = InvalidBackendId;
 			needs_wal = true;
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index a23128d..5d131a7 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -1400,7 +1400,7 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 	 */
 	if (newrelpersistence == RELPERSISTENCE_UNLOGGED)
 		reindex_flags |= REINDEX_REL_FORCE_INDEXES_UNLOGGED;
-	else if (newrelpersistence == RELPERSISTENCE_PERMANENT)
+	else if (newrelpersistence != RELPERSISTENCE_TEMP)
 		reindex_flags |= REINDEX_REL_FORCE_INDEXES_PERMANENT;
 
 	/* Report that we are now reindexing relations */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index a13322b..be661a4 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -94,7 +94,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -222,7 +222,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* now initialize the sequence's data */
 	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -327,7 +327,7 @@ ResetSequence(Oid seq_relid)
 	/*
 	 * Insert the modified tuple into the new storage file.
 	 */
-	fill_seq_with_data(seq_rel, tuple);
+	fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -340,18 +340,21 @@ ResetSequence(Oid seq_relid)
  * Initialize a sequence's relation with the specified tuple as content
  */
 static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 	sequence_magic *sm;
 	OffsetNumber offnum;
+	bool lockBuffer = false;
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ReadBuffer(rel, P_NEW);
-	Assert(BufferGetBlockNumber(buf) == 0);
-
+	if (buf == InvalidBuffer)
+	{
+		buf = ReadBuffer(rel, P_NEW);
+		Assert(BufferGetBlockNumber(buf) == 0);
+		lockBuffer = true;
+	}
 	page = BufferGetPage(buf);
 
 	PageInit(page, BufferGetPageSize(buf), sizeof(sequence_magic));
@@ -360,7 +363,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* Now insert sequence tuple */
 
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+	if (lockBuffer)
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +414,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	END_CRIT_SECTION();
 
-	UnlockReleaseBuffer(buf);
+	if (lockBuffer)
+		UnlockReleaseBuffer(buf);
 }
 
 /*
@@ -502,7 +507,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * Insert the modified tuple into the new storage file.
 		 */
-		fill_seq_with_data(seqrel, newdatatuple);
+		fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
 	}
 
 	/* process OWNED BY if given */
@@ -1178,6 +1183,17 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
 	LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
 
 	page = BufferGetPage(*buf);
+	if (GlobalTempRelationPageIsNotInitialized(rel, page))
+	{
+		/* Initialize sequence for global temporary tables */
+		Datum		value[SEQ_COL_LASTCOL] = {0};
+		bool		null[SEQ_COL_LASTCOL] = {false};
+		HeapTuple tuple;
+		value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(1); /* start sequence with 1 */
+		tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+		fill_seq_with_data(rel, tuple, *buf);
+	}
+
 	sm = (sequence_magic *) PageGetSpecialPointer(page);
 
 	if (sm->magic != SEQ_MAGIC)
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8d25d14..50d0402 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -587,7 +587,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * Check consistency of arguments
 	 */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !IsLocalRelpersistence(stmt->relation->relpersistence))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -1772,7 +1772,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 ||
+			rel->rd_rel->relpersistence == RELPERSISTENCE_SESSION)
 		{
 			/* Immediate, non-rollbackable truncation is OK */
 			heap_truncate_one_rel(rel);
@@ -7708,6 +7709,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on unlogged tables may reference only permanent or unlogged tables")));
 			break;
+		case RELPERSISTENCE_SESSION:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_SESSION)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on session tables may reference only session tables")));
+			break;
 		case RELPERSISTENCE_TEMP:
 			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
 				ereport(ERROR,
@@ -14140,6 +14147,13 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
 							RelationGetRelationName(rel)),
 					 errtable(rel)));
 			break;
+		case RELPERSISTENCE_SESSION:
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot change logged status of session table \"%s\"",
+							RelationGetRelationName(rel)),
+					 errtable(rel)));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (toLogged)
 				/* nothing to do */
@@ -14627,14 +14641,7 @@ PreCommit_on_commit_actions(void)
 				/* Do nothing (there shouldn't be such entries, actually) */
 				break;
 			case ONCOMMIT_DELETE_ROWS:
-
-				/*
-				 * If this transaction hasn't accessed any temporary
-				 * relations, we can skip truncating ON COMMIT DELETE ROWS
-				 * tables, as they must still be empty.
-				 */
-				if ((MyXactFlags & XACT_FLAGS_ACCESSEDTEMPNAMESPACE))
-					oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid);
+				oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid);
 				break;
 			case ONCOMMIT_DROP:
 				oids_to_drop = lappend_oid(oids_to_drop, oc->relid);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3f67aaf..565c868 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3266,20 +3266,11 @@ OptTemp:	TEMPORARY					{ $$ = RELPERSISTENCE_TEMP; }
 			| TEMP						{ $$ = RELPERSISTENCE_TEMP; }
 			| LOCAL TEMPORARY			{ $$ = RELPERSISTENCE_TEMP; }
 			| LOCAL TEMP				{ $$ = RELPERSISTENCE_TEMP; }
-			| GLOBAL TEMPORARY
-				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
-					$$ = RELPERSISTENCE_TEMP;
-				}
-			| GLOBAL TEMP
-				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
-					$$ = RELPERSISTENCE_TEMP;
-				}
+			| GLOBAL TEMPORARY          { $$ = RELPERSISTENCE_SESSION; }
+			| GLOBAL TEMP               { $$ = RELPERSISTENCE_SESSION; }
+			| SESSION                   { $$ = RELPERSISTENCE_SESSION; }
+			| SESSION TEMPORARY         { $$ = RELPERSISTENCE_SESSION; }
+			| SESSION TEMP              { $$ = RELPERSISTENCE_SESSION; }
 			| UNLOGGED					{ $$ = RELPERSISTENCE_UNLOGGED; }
 			| /*EMPTY*/					{ $$ = RELPERSISTENCE_PERMANENT; }
 		;
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index ee47547..ea7fe4c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,14 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->options = seqoptions;
 
 	/*
+	 * Why we should not always use persistence of parent table?
+	 * Although it is prohibited to have unlogged sequences,
+	 * unlogged tables with SERIAL fields are accepted!
+	 */
+	if (cxt->relation->relpersistence != RELPERSISTENCE_UNLOGGED)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
+	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
 	 * clause, the "redundant options" error will point to their occurrence,
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index c1dd816..dcfc134 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2157,7 +2157,7 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		if (IsLocalRelpersistence(classForm->relpersistence))
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c
index 07f3c93..5db79ec 100644
--- a/src/backend/storage/smgr/md.c
+++ b/src/backend/storage/smgr/md.c
@@ -33,6 +33,7 @@
 #include "postmaster/bgwriter.h"
 #include "storage/fd.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/md.h"
 #include "storage/relfilenode.h"
 #include "storage/smgr.h"
@@ -87,6 +88,18 @@ typedef struct _MdfdVec
 
 static MemoryContext MdCxt;		/* context for all MdfdVec objects */
 
+/*
+ * Structure used to collect information created by this backend.
+ * Data of this related should be deleted on backend exit.
+ */
+typedef struct SessionRelation
+{
+	RelFileNodeBackend rnode;
+	struct SessionRelation* next;
+} SessionRelation;
+
+
+static SessionRelation* SessionRelations;
 
 /* Populate a file tag describing an md.c segment file. */
 #define INIT_MD_FILETAG(a,xx_rnode,xx_forknum,xx_segno) \
@@ -152,6 +165,45 @@ mdinit(void)
 								  ALLOCSET_DEFAULT_SIZES);
 }
 
+
+/*
+ * Delete all data of session relations and remove their pages from shared buffers.
+ * This function is called on backend exit.
+ */
+static void
+TruncateSessionRelations(int code, Datum arg)
+{
+	SessionRelation* rel;
+	for (rel = SessionRelations; rel != NULL; rel = rel->next)
+	{
+		/* Delete relation files */
+		mdunlink(rel->rnode, InvalidForkNumber, false);
+	}
+}
+
+/*
+ * Maintain information about session relations accessed by this backend.
+ * This list is needed to perform cleanup on backend exit.
+ * Session relation is linked in this list when this relation is created or opened and file doesn't exist.
+ * Such procedure guarantee that each relation is linked into list only once.
+ */
+static void
+RegisterSessionRelation(SMgrRelation reln)
+{
+	SessionRelation* rel = (SessionRelation*)MemoryContextAlloc(TopMemoryContext, sizeof(SessionRelation));
+
+	/*
+	 * Perform session relation cleanup on backend exit. We are using shared memory hook, because
+	 * cleanup should be performed before backend is disconnected from shared memory.
+	 */
+	if (SessionRelations == NULL)
+		on_shmem_exit(TruncateSessionRelations, 0);
+
+	rel->rnode = reln->smgr_rnode;
+	rel->next = SessionRelations;
+	SessionRelations = rel;
+}
+
 /*
  *	mdexists() -- Does the physical file exist?
  *
@@ -218,6 +270,8 @@ mdcreate(SMgrRelation reln, ForkNumber forkNum, bool isRedo)
 					 errmsg("could not create file \"%s\": %m", path)));
 		}
 	}
+	if (RelFileNodeBackendIsGlobalTemp(reln->smgr_rnode))
+		RegisterSessionRelation(reln);
 
 	pfree(path);
 
@@ -465,6 +519,19 @@ mdopenfork(SMgrRelation reln, ForkNumber forknum, int behavior)
 
 	if (fd < 0)
 	{
+		/*
+		 * In case of session relation access, there may be no yet files of this relation for this backend.
+		 * If so, then create file and register session relation for truncation on backend exit.
+		 */
+		if (RelFileNodeBackendIsGlobalTemp(reln->smgr_rnode))
+		{
+			fd = PathNameOpenFile(path, O_RDWR | PG_BINARY | O_CREAT);
+			if (fd >= 0)
+			{
+				RegisterSessionRelation(reln);
+				goto NewSegment;
+			}
+		}
 		if ((behavior & EXTENSION_RETURN_NULL) &&
 			FILE_POSSIBLY_DELETED(errno))
 		{
@@ -476,6 +543,7 @@ mdopenfork(SMgrRelation reln, ForkNumber forknum, int behavior)
 				 errmsg("could not open file \"%s\": %m", path)));
 	}
 
+  NewSegment:
 	pfree(path);
 
 	_fdvec_resize(reln, forknum, 1);
@@ -652,8 +720,13 @@ mdread(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
 		 * complaining.  This allows, for example, the case of trying to
 		 * update a block that was later truncated away.
 		 */
-		if (zero_damaged_pages || InRecovery)
+		if (zero_damaged_pages || InRecovery || RelFileNodeBackendIsGlobalTemp(reln->smgr_rnode))
+		{
 			MemSet(buffer, 0, BLCKSZ);
+			/* In case of session relation we need to write zero page to provide correct result of subsequent mdnblocks */
+			if (RelFileNodeBackendIsGlobalTemp(reln->smgr_rnode))
+				mdwrite(reln, forknum, blocknum, buffer, true);
+		}
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_DATA_CORRUPTED),
@@ -738,12 +811,18 @@ mdwrite(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
 BlockNumber
 mdnblocks(SMgrRelation reln, ForkNumber forknum)
 {
-	MdfdVec    *v = mdopenfork(reln, forknum, EXTENSION_FAIL);
+	/*
+	 * If we access session relation, there may be no files yet of this relation for this backend.
+	 * Pass EXTENSION_RETURN_NULL to make mdopen return NULL in this case instead of reporting error.
+	 */
+	MdfdVec    *v = mdopenfork(reln, forknum, RelFileNodeBackendIsGlobalTemp(reln->smgr_rnode)
+							   ? EXTENSION_RETURN_NULL : EXTENSION_FAIL);
 	BlockNumber nblocks;
 	BlockNumber segno = 0;
 
 	/* mdopen has opened the first segment */
-	Assert(reln->md_num_open_segs[forknum] > 0);
+	if (reln->md_num_open_segs[forknum] == 0)
+		return 0;
 
 	/*
 	 * Start from the last open segments, to avoid redundant seeks.  We have
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index a87e721..2401361 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -994,6 +994,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 	/* Determine owning backend. */
 	switch (relform->relpersistence)
 	{
+		case RELPERSISTENCE_SESSION:
+			backend = BackendIdForSessionRelations();
+			break;
 		case RELPERSISTENCE_UNLOGGED:
 		case RELPERSISTENCE_PERMANENT:
 			backend = InvalidBackendId;
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 585dcee..ce8852c 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1098,6 +1098,10 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	relation->rd_newRelfilenodeSubid = InvalidSubTransactionId;
 	switch (relation->rd_rel->relpersistence)
 	{
+		case RELPERSISTENCE_SESSION:
+			relation->rd_backend = BackendIdForSessionRelations();
+			relation->rd_islocaltemp = false;
+			break;
 		case RELPERSISTENCE_UNLOGGED:
 		case RELPERSISTENCE_PERMANENT:
 			relation->rd_backend = InvalidBackendId;
@@ -3301,6 +3305,10 @@ RelationBuildLocalRelation(const char *relname,
 	rel->rd_rel->relpersistence = relpersistence;
 	switch (relpersistence)
 	{
+		case RELPERSISTENCE_SESSION:
+			rel->rd_backend = BackendIdForSessionRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		case RELPERSISTENCE_UNLOGGED:
 		case RELPERSISTENCE_PERMANENT:
 			rel->rd_backend = InvalidBackendId;
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index bf69adc..fa7479c 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15637,8 +15637,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 											 tbinfo->dobj.catId.oid, false);
 
 		appendPQExpBuffer(q, "CREATE %s%s %s",
-						  tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ?
-						  "UNLOGGED " : "",
+						  tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ? "UNLOGGED "
+						  : tbinfo->relpersistence == RELPERSISTENCE_SESSION ? "SESSION " : "",
 						  reltypename,
 						  qualrelname);
 
diff --git a/src/common/relpath.c b/src/common/relpath.c
index 62b9553..cef99d2 100644
--- a/src/common/relpath.c
+++ b/src/common/relpath.c
@@ -166,7 +166,18 @@ GetRelationPath(Oid dbNode, Oid spcNode, Oid relNode,
 		}
 		else
 		{
-			if (forkNumber != MAIN_FORKNUM)
+			/*
+			 * Session relations are distinguished from local temp relations by adding
+			 * SessionRelFirstBackendId offset to backendId.
+			 * These is no need to separate them at file system level, so just subtract SessionRelFirstBackendId
+			 * to avoid too long file names.
+			 * Segments of session relations have the same prefix (t%d_) as local temporary relations
+			 * to make it possible to cleanup them in the same way as local temporary relation files.
+			 */
+			 if (backendId >= SessionRelFirstBackendId)
+				 backendId -= SessionRelFirstBackendId;
+
+			 if (forkNumber != MAIN_FORKNUM)
 				path = psprintf("base/%u/t%d_%u_%s",
 								dbNode, backendId, relNode,
 								forkNames[forkNumber]);
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 090b6ba..6a39663 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_SESSION	's' /* session table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/storage/backendid.h b/src/include/storage/backendid.h
index 70ef8eb..f226e7c 100644
--- a/src/include/storage/backendid.h
+++ b/src/include/storage/backendid.h
@@ -22,6 +22,13 @@ typedef int BackendId;			/* unique currently active backend identifier */
 
 #define InvalidBackendId		(-1)
 
+/*
+ * We need to distinguish local and global temporary relations by RelFileNodeBackend.
+ * The least invasive change is to add some special bias value to backend id (since 
+ * maximal number of backed is limited by MaxBackends).
+ */
+#define SessionRelFirstBackendId (0x40000000)
+
 extern PGDLLIMPORT BackendId MyBackendId;	/* backend id of this backend */
 
 /* backend id of our parallel session leader, or InvalidBackendId if none */
@@ -34,4 +41,10 @@ extern PGDLLIMPORT BackendId ParallelMasterBackendId;
 #define BackendIdForTempRelations() \
 	(ParallelMasterBackendId == InvalidBackendId ? MyBackendId : ParallelMasterBackendId)
 
+
+#define BackendIdForSessionRelations() \
+	(BackendIdForTempRelations() + SessionRelFirstBackendId)
+
+#define IsSessionRelationBackendId(id) ((id) >= SessionRelFirstBackendId)
+
 #endif							/* BACKENDID_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 4ef6d8d..bac7a31 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -229,6 +229,13 @@ typedef PageHeaderData *PageHeader;
 #define PageIsNew(page) (((PageHeader) (page))->pd_upper == 0)
 
 /*
+ * Page of temporary relation is not initialized
+ */
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_SESSION && PageIsNew(page))
+
+
+/*
  * PageGetItemId
  *		Returns an item identifier of a page.
  */
diff --git a/src/include/storage/relfilenode.h b/src/include/storage/relfilenode.h
index 586500a..20aec72 100644
--- a/src/include/storage/relfilenode.h
+++ b/src/include/storage/relfilenode.h
@@ -75,10 +75,25 @@ typedef struct RelFileNodeBackend
 	BackendId	backend;
 } RelFileNodeBackend;
 
+/*
+ * Check whether it is local or global temporary relation, which data belongs only to one backend.
+ */
 #define RelFileNodeBackendIsTemp(rnode) \
 	((rnode).backend != InvalidBackendId)
 
 /*
+ * Check whether it is global temporary relation which metadata is shared by all sessions,
+ * but data is private for the current session.
+ */
+#define RelFileNodeBackendIsGlobalTemp(rnode) IsSessionRelationBackendId((rnode).backend)
+
+/*
+ * Check whether it is local temporary relation which exists only in this backend.
+ */
+#define RelFileNodeBackendIsLocalTemp(rnode) \
+	(RelFileNodeBackendIsTemp(rnode) && !RelFileNodeBackendIsGlobalTemp(rnode))
+
+/*
  * Note: RelFileNodeEquals and RelFileNodeBackendEquals compare relNode first
  * since that is most likely to be different in two unequal RelFileNodes.  It
  * is probably redundant to compare spcNode if the other fields are found equal,
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index a5cf804..d42830f 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -327,6 +327,18 @@ typedef struct StdRdOptions
 	((relation)->rd_options ? \
 	 ((StdRdOptions *) (relation)->rd_options)->parallel_workers : (defaultpw))
 
+/*
+ * Relation persistence is either TEMP either SESSION
+ */
+#define IsLocalRelpersistence(relpersistence) \
+	((relpersistence) == RELPERSISTENCE_TEMP || (relpersistence) == RELPERSISTENCE_SESSION)
+
+/*
+ * Relation is either global either local temp table
+ */
+#define RelationHasSessionScope(relation) \
+	IsLocalRelpersistence(((relation)->rd_rel->relpersistence))
+
 /* ViewOptions->check_option values */
 typedef enum ViewOptCheckOption
 {
@@ -335,6 +347,7 @@ typedef enum ViewOptCheckOption
 	VIEW_OPTION_CHECK_OPTION_CASCADED
 } ViewOptCheckOption;
 
+
 /*
  * ViewOptions
  *		Contents of rd_options for views
@@ -526,7 +539,7 @@ typedef struct ViewOptions
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	RelationHasSessionScope(relation)
 
 /*
  * RELATION_IS_LOCAL
diff --git a/src/test/isolation/expected/inherit-global-temp.out b/src/test/isolation/expected/inherit-global-temp.out
new file mode 100644
index 0000000..6114f8c
--- /dev/null
+++ b/src/test/isolation/expected/inherit-global-temp.out
@@ -0,0 +1,218 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1_insert_p s1_insert_c s2_insert_c s1_select_p s1_select_c s2_select_p s2_select_c
+step s1_insert_p: INSERT INTO inh_global_parent VALUES (1), (2);
+step s1_insert_c: INSERT INTO inh_global_temp_child_s1 VALUES (3), (4);
+step s2_insert_c: INSERT INTO inh_global_temp_child_s2 VALUES (5), (6);
+step s1_select_p: SELECT a FROM inh_global_parent;
+a              
+
+1              
+2              
+3              
+4              
+step s1_select_c: SELECT a FROM inh_global_temp_child_s1;
+a              
+
+3              
+4              
+step s2_select_p: SELECT a FROM inh_global_parent;
+a              
+
+1              
+2              
+5              
+6              
+step s2_select_c: SELECT a FROM inh_global_temp_child_s2;
+a              
+
+5              
+6              
+
+starting permutation: s1_insert_p s1_insert_c s2_insert_c s1_update_p s1_update_c s1_select_p s1_select_c s2_select_p s2_select_c
+step s1_insert_p: INSERT INTO inh_global_parent VALUES (1), (2);
+step s1_insert_c: INSERT INTO inh_global_temp_child_s1 VALUES (3), (4);
+step s2_insert_c: INSERT INTO inh_global_temp_child_s2 VALUES (5), (6);
+step s1_update_p: UPDATE inh_global_parent SET a = 11 WHERE a = 1;
+step s1_update_c: UPDATE inh_global_parent SET a = 13 WHERE a IN (3, 5);
+step s1_select_p: SELECT a FROM inh_global_parent;
+a              
+
+2              
+11             
+4              
+13             
+step s1_select_c: SELECT a FROM inh_global_temp_child_s1;
+a              
+
+4              
+13             
+step s2_select_p: SELECT a FROM inh_global_parent;
+a              
+
+2              
+11             
+5              
+6              
+step s2_select_c: SELECT a FROM inh_global_temp_child_s2;
+a              
+
+5              
+6              
+
+starting permutation: s1_insert_p s1_insert_c s2_insert_c s2_update_c s1_select_p s1_select_c s2_select_p s2_select_c
+step s1_insert_p: INSERT INTO inh_global_parent VALUES (1), (2);
+step s1_insert_c: INSERT INTO inh_global_temp_child_s1 VALUES (3), (4);
+step s2_insert_c: INSERT INTO inh_global_temp_child_s2 VALUES (5), (6);
+step s2_update_c: UPDATE inh_global_parent SET a = 15 WHERE a IN (3, 5);
+step s1_select_p: SELECT a FROM inh_global_parent;
+a              
+
+1              
+2              
+3              
+4              
+step s1_select_c: SELECT a FROM inh_global_temp_child_s1;
+a              
+
+3              
+4              
+step s2_select_p: SELECT a FROM inh_global_parent;
+a              
+
+1              
+2              
+6              
+15             
+step s2_select_c: SELECT a FROM inh_global_temp_child_s2;
+a              
+
+6              
+15             
+
+starting permutation: s1_insert_p s1_insert_c s2_insert_c s1_delete_p s1_delete_c s1_select_p s1_select_c s2_select_p s2_select_c
+step s1_insert_p: INSERT INTO inh_global_parent VALUES (1), (2);
+step s1_insert_c: INSERT INTO inh_global_temp_child_s1 VALUES (3), (4);
+step s2_insert_c: INSERT INTO inh_global_temp_child_s2 VALUES (5), (6);
+step s1_delete_p: DELETE FROM inh_global_parent WHERE a = 2;
+step s1_delete_c: DELETE FROM inh_global_parent WHERE a IN (4, 6);
+step s1_select_p: SELECT a FROM inh_global_parent;
+a              
+
+1              
+3              
+step s1_select_c: SELECT a FROM inh_global_temp_child_s1;
+a              
+
+3              
+step s2_select_p: SELECT a FROM inh_global_parent;
+a              
+
+1              
+5              
+6              
+step s2_select_c: SELECT a FROM inh_global_temp_child_s2;
+a              
+
+5              
+6              
+
+starting permutation: s1_insert_p s1_insert_c s2_insert_c s2_delete_c s1_select_p s1_select_c s2_select_p s2_select_c
+step s1_insert_p: INSERT INTO inh_global_parent VALUES (1), (2);
+step s1_insert_c: INSERT INTO inh_global_temp_child_s1 VALUES (3), (4);
+step s2_insert_c: INSERT INTO inh_global_temp_child_s2 VALUES (5), (6);
+step s2_delete_c: DELETE FROM inh_global_parent WHERE a IN (4, 6);
+step s1_select_p: SELECT a FROM inh_global_parent;
+a              
+
+1              
+2              
+3              
+4              
+step s1_select_c: SELECT a FROM inh_global_temp_child_s1;
+a              
+
+3              
+4              
+step s2_select_p: SELECT a FROM inh_global_parent;
+a              
+
+1              
+2              
+5              
+step s2_select_c: SELECT a FROM inh_global_temp_child_s2;
+a              
+
+5              
+
+starting permutation: s1_insert_p s1_insert_c s2_insert_c s1_truncate_p s1_select_p s1_select_c s2_select_p s2_select_c
+step s1_insert_p: INSERT INTO inh_global_parent VALUES (1), (2);
+step s1_insert_c: INSERT INTO inh_global_temp_child_s1 VALUES (3), (4);
+step s2_insert_c: INSERT INTO inh_global_temp_child_s2 VALUES (5), (6);
+step s1_truncate_p: TRUNCATE inh_global_parent;
+step s1_select_p: SELECT a FROM inh_global_parent;
+a              
+
+step s1_select_c: SELECT a FROM inh_global_temp_child_s1;
+a              
+
+step s2_select_p: SELECT a FROM inh_global_parent;
+a              
+
+5              
+6              
+step s2_select_c: SELECT a FROM inh_global_temp_child_s2;
+a              
+
+5              
+6              
+
+starting permutation: s1_insert_p s1_insert_c s2_insert_c s2_truncate_p s1_select_p s1_select_c s2_select_p s2_select_c
+step s1_insert_p: INSERT INTO inh_global_parent VALUES (1), (2);
+step s1_insert_c: INSERT INTO inh_global_temp_child_s1 VALUES (3), (4);
+step s2_insert_c: INSERT INTO inh_global_temp_child_s2 VALUES (5), (6);
+step s2_truncate_p: TRUNCATE inh_global_parent;
+step s1_select_p: SELECT a FROM inh_global_parent;
+a              
+
+3              
+4              
+step s1_select_c: SELECT a FROM inh_global_temp_child_s1;
+a              
+
+3              
+4              
+step s2_select_p: SELECT a FROM inh_global_parent;
+a              
+
+step s2_select_c: SELECT a FROM inh_global_temp_child_s2;
+a              
+
+
+starting permutation: s1_insert_p s1_insert_c s2_insert_c s1_begin s1_truncate_p s2_select_p s1_commit
+step s1_insert_p: INSERT INTO inh_global_parent VALUES (1), (2);
+step s1_insert_c: INSERT INTO inh_global_temp_child_s1 VALUES (3), (4);
+step s2_insert_c: INSERT INTO inh_global_temp_child_s2 VALUES (5), (6);
+step s1_begin: BEGIN;
+step s1_truncate_p: TRUNCATE inh_global_parent;
+step s2_select_p: SELECT a FROM inh_global_parent; <waiting ...>
+step s1_commit: COMMIT;
+step s2_select_p: <... completed>
+a              
+
+5              
+6              
+
+starting permutation: s1_insert_p s1_insert_c s2_insert_c s1_begin s1_truncate_p s2_select_c s1_commit
+step s1_insert_p: INSERT INTO inh_global_parent VALUES (1), (2);
+step s1_insert_c: INSERT INTO inh_global_temp_child_s1 VALUES (3), (4);
+step s2_insert_c: INSERT INTO inh_global_temp_child_s2 VALUES (5), (6);
+step s1_begin: BEGIN;
+step s1_truncate_p: TRUNCATE inh_global_parent;
+step s2_select_c: SELECT a FROM inh_global_temp_child_s2; <waiting ...>
+step s1_commit: COMMIT;
+step s2_select_c: <... completed>
+a              
+
+5              
+6              
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index a2fa192..ef7aa85 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -88,3 +88,4 @@ test: plpgsql-toast
 test: truncate-conflict
 test: serializable-parallel
 test: serializable-parallel-2
+test: inherit-global-temp
diff --git a/src/test/isolation/specs/inherit-global-temp.spec b/src/test/isolation/specs/inherit-global-temp.spec
new file mode 100644
index 0000000..5e95dd6
--- /dev/null
+++ b/src/test/isolation/specs/inherit-global-temp.spec
@@ -0,0 +1,73 @@
+# This is a copy of the inherit-temp test with little changes for global temporary tables.
+#
+
+setup
+{
+  CREATE TABLE inh_global_parent (a int);
+}
+
+teardown
+{
+  DROP TABLE inh_global_parent;
+}
+
+# Session 1 executes actions which act directly on both the parent and
+# its child.  Abbreviation "c" is used for queries working on the child
+# and "p" on the parent.
+session "s1"
+setup
+{
+  CREATE GLOBAL TEMPORARY TABLE inh_global_temp_child_s1 () INHERITS (inh_global_parent);
+}
+step "s1_begin" { BEGIN; }
+step "s1_truncate_p" { TRUNCATE inh_global_parent; }
+step "s1_select_p" { SELECT a FROM inh_global_parent; }
+step "s1_select_c" { SELECT a FROM inh_global_temp_child_s1; }
+step "s1_insert_p" { INSERT INTO inh_global_parent VALUES (1), (2); }
+step "s1_insert_c" { INSERT INTO inh_global_temp_child_s1 VALUES (3), (4); }
+step "s1_update_p" { UPDATE inh_global_parent SET a = 11 WHERE a = 1; }
+step "s1_update_c" { UPDATE inh_global_parent SET a = 13 WHERE a IN (3, 5); }
+step "s1_delete_p" { DELETE FROM inh_global_parent WHERE a = 2; }
+step "s1_delete_c" { DELETE FROM inh_global_parent WHERE a IN (4, 6); }
+step "s1_commit" { COMMIT; }
+teardown
+{
+  DROP TABLE inh_global_temp_child_s1;
+}
+
+# Session 2 executes actions on the parent which act only on the child.
+session "s2"
+setup
+{
+  CREATE GLOBAL TEMPORARY TABLE inh_global_temp_child_s2 () INHERITS (inh_global_parent);
+}
+step "s2_truncate_p" { TRUNCATE inh_global_parent; }
+step "s2_select_p" { SELECT a FROM inh_global_parent; }
+step "s2_select_c" { SELECT a FROM inh_global_temp_child_s2; }
+step "s2_insert_c" { INSERT INTO inh_global_temp_child_s2 VALUES (5), (6); }
+step "s2_update_c" { UPDATE inh_global_parent SET a = 15 WHERE a IN (3, 5); }
+step "s2_delete_c" { DELETE FROM inh_global_parent WHERE a IN (4, 6); }
+teardown
+{
+  DROP TABLE inh_global_temp_child_s2;
+}
+
+# Check INSERT behavior across sessions
+permutation "s1_insert_p" "s1_insert_c" "s2_insert_c" "s1_select_p" "s1_select_c" "s2_select_p" "s2_select_c"
+
+# Check UPDATE behavior across sessions
+permutation "s1_insert_p" "s1_insert_c" "s2_insert_c" "s1_update_p" "s1_update_c" "s1_select_p" "s1_select_c" "s2_select_p" "s2_select_c"
+permutation "s1_insert_p" "s1_insert_c" "s2_insert_c" "s2_update_c" "s1_select_p" "s1_select_c" "s2_select_p" "s2_select_c"
+
+# Check DELETE behavior across sessions
+permutation "s1_insert_p" "s1_insert_c" "s2_insert_c" "s1_delete_p" "s1_delete_c" "s1_select_p" "s1_select_c" "s2_select_p" "s2_select_c"
+permutation "s1_insert_p" "s1_insert_c" "s2_insert_c" "s2_delete_c" "s1_select_p" "s1_select_c" "s2_select_p" "s2_select_c"
+
+# Check TRUNCATE behavior across sessions
+permutation "s1_insert_p" "s1_insert_c" "s2_insert_c" "s1_truncate_p" "s1_select_p" "s1_select_c" "s2_select_p" "s2_select_c"
+permutation "s1_insert_p" "s1_insert_c" "s2_insert_c" "s2_truncate_p" "s1_select_p" "s1_select_c" "s2_select_p" "s2_select_c"
+
+# TRUNCATE on a parent tree does not block access to temporary child relation
+# of another session, and blocks when scanning the parent.
+permutation "s1_insert_p" "s1_insert_c" "s2_insert_c" "s1_begin" "s1_truncate_p" "s2_select_p" "s1_commit"
+permutation "s1_insert_p" "s1_insert_c" "s2_insert_c" "s1_begin" "s1_truncate_p" "s2_select_c" "s1_commit"
diff --git a/src/test/regress/expected/global_temp.out b/src/test/regress/expected/global_temp.out
new file mode 100644
index 0000000..ae1adb6
--- /dev/null
+++ b/src/test/regress/expected/global_temp.out
@@ -0,0 +1,247 @@
+--
+-- GLOBAL TEMP
+-- Test global temp relations
+--
+-- Test ON COMMIT DELETE ROWS
+CREATE GLOBAL TEMP TABLE global_temptest(col int) ON COMMIT DELETE ROWS;
+BEGIN;
+INSERT INTO global_temptest VALUES (1);
+INSERT INTO global_temptest VALUES (2);
+SELECT * FROM global_temptest;
+ col 
+-----
+   1
+   2
+(2 rows)
+
+COMMIT;
+SELECT * FROM global_temptest;
+ col 
+-----
+(0 rows)
+
+DROP TABLE global_temptest;
+BEGIN;
+CREATE GLOBAL TEMP TABLE global_temptest(col) ON COMMIT DELETE ROWS AS SELECT 1;
+SELECT * FROM global_temptest;
+ col 
+-----
+   1
+(1 row)
+
+COMMIT;
+SELECT * FROM global_temptest;
+ col 
+-----
+(0 rows)
+
+DROP TABLE global_temptest;
+-- Test foreign keys
+BEGIN;
+CREATE GLOBAL TEMP TABLE global_temptest1(col int PRIMARY KEY);
+CREATE GLOBAL TEMP TABLE global_temptest2(col int REFERENCES global_temptest1)
+  ON COMMIT DELETE ROWS;
+INSERT INTO global_temptest1 VALUES (1);
+INSERT INTO global_temptest2 VALUES (1);
+COMMIT;
+SELECT * FROM global_temptest1;
+ col 
+-----
+   1
+(1 row)
+
+SELECT * FROM global_temptest2;
+ col 
+-----
+(0 rows)
+
+BEGIN;
+CREATE GLOBAL TEMP TABLE global_temptest3(col int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMP TABLE global_temptest4(col int REFERENCES global_temptest3);
+COMMIT;
+ERROR:  unsupported ON COMMIT and foreign key combination
+DETAIL:  Table "global_temptest4" references "global_temptest3", but they do not have the same ON COMMIT setting.
+-- For partitioned temp tables, ON COMMIT actions ignore storage-less
+-- partitioned tables.
+BEGIN;
+CREATE GLOBAL TEMP TABLE temp_parted_oncommit (a int)
+  PARTITION BY LIST (a) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMP TABLE temp_parted_oncommit_1
+  PARTITION OF temp_parted_oncommit
+  FOR VALUES IN (1) ON COMMIT DELETE ROWS;
+INSERT INTO temp_parted_oncommit VALUES (1);
+COMMIT;
+-- partitions are emptied by the previous commit
+SELECT * FROM temp_parted_oncommit;
+ a 
+---
+(0 rows)
+
+DROP TABLE temp_parted_oncommit;
+-- Using ON COMMIT DELETE on a partitioned table does not remove
+-- all rows if partitions preserve their data.
+BEGIN;
+CREATE GLOBAL TEMP TABLE global_temp_parted_oncommit_test (a int)
+  PARTITION BY LIST (a) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMP TABLE global_temp_parted_oncommit_test1
+  PARTITION OF global_temp_parted_oncommit_test
+  FOR VALUES IN (1) ON COMMIT PRESERVE ROWS;
+INSERT INTO global_temp_parted_oncommit_test VALUES (1);
+COMMIT;
+-- Data from the remaining partition is still here as its rows are
+-- preserved.
+SELECT * FROM global_temp_parted_oncommit_test;
+ a 
+---
+ 1
+(1 row)
+
+-- two relations remain in this case.
+SELECT relname FROM pg_class WHERE relname LIKE 'global_temp_parted_oncommit_test%';
+              relname              
+-----------------------------------
+ global_temp_parted_oncommit_test
+ global_temp_parted_oncommit_test1
+(2 rows)
+
+DROP TABLE global_temp_parted_oncommit_test;
+-- Check dependencies between ON COMMIT actions with inheritance trees.
+-- Data on the parent is removed, and the child goes away.
+BEGIN;
+CREATE GLOBAL TEMP TABLE global_temp_inh_oncommit_test (a int) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMP TABLE global_temp_inh_oncommit_test1 ()
+  INHERITS(global_temp_inh_oncommit_test) ON COMMIT PRESERVE ROWS;
+INSERT INTO global_temp_inh_oncommit_test1 VALUES (1);
+INSERT INTO global_temp_inh_oncommit_test VALUES (1);
+COMMIT;
+SELECT * FROM global_temp_inh_oncommit_test;
+ a 
+---
+ 1
+(1 row)
+
+-- two relations remain
+SELECT relname FROM pg_class WHERE relname LIKE 'global_temp_inh_oncommit_test%';
+            relname             
+--------------------------------
+ global_temp_inh_oncommit_test
+ global_temp_inh_oncommit_test1
+(2 rows)
+
+DROP TABLE global_temp_inh_oncommit_test1;
+DROP TABLE global_temp_inh_oncommit_test;
+-- Global temp table cannot inherit from temporary relation
+BEGIN;
+CREATE TEMP TABLE global_temp_table (a int) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMP TABLE global_temp_table1 ()
+  INHERITS(global_temp_table) ON COMMIT PRESERVE ROWS;
+ERROR:  cannot inherit from temporary relation "global_temp_table"
+ROLLBACK;
+-- Temp table can inherit from global temporary relation
+BEGIN;
+CREATE GLOBAL TEMP TABLE global_temp_table (a int) ON COMMIT DELETE ROWS;
+CREATE TEMP TABLE temp_table1 ()
+  INHERITS(global_temp_table) ON COMMIT PRESERVE ROWS;
+CREATE TEMP TABLE temp_table2 ()
+  INHERITS(global_temp_table) ON COMMIT DELETE ROWS;
+INSERT INTO temp_table2 VALUES (2);
+INSERT INTO temp_table1 VALUES (1);
+INSERT INTO global_temp_table VALUES (0);
+SELECT * FROM global_temp_table;
+ a 
+---
+ 0
+ 1
+ 2
+(3 rows)
+
+COMMIT;
+SELECT * FROM global_temp_table;
+ a 
+---
+ 1
+(1 row)
+
+DROP TABLE temp_table2;
+DROP TABLE temp_table1;
+DROP TABLE global_temp_table;
+-- Global temp table can inherit from normal relation
+BEGIN;
+CREATE TABLE normal_table (a int);
+CREATE GLOBAL TEMP TABLE temp_table1 ()
+  INHERITS(normal_table) ON COMMIT PRESERVE ROWS;
+CREATE GLOBAL TEMP TABLE temp_table2 ()
+  INHERITS(normal_table) ON COMMIT DELETE ROWS;
+INSERT INTO temp_table2 VALUES (2);
+INSERT INTO temp_table1 VALUES (1);
+INSERT INTO normal_table VALUES (0);
+SELECT * FROM normal_table;
+ a 
+---
+ 0
+ 1
+ 2
+(3 rows)
+
+COMMIT;
+SELECT * FROM normal_table;
+ a 
+---
+ 0
+ 1
+(2 rows)
+
+DROP TABLE temp_table2;
+DROP TABLE temp_table1;
+DROP TABLE normal_table;
+-- Check SERIAL and BIGSERIAL pseudo-types
+CREATE GLOBAL TEMP TABLE global_temp_table ( aid BIGSERIAL, bid SERIAL );
+CREATE SEQUENCE test_sequence;
+INSERT INTO global_temp_table DEFAULT VALUES;
+INSERT INTO global_temp_table DEFAULT VALUES;
+INSERT INTO global_temp_table DEFAULT VALUES;
+SELECT * FROM global_temp_table;
+ aid | bid 
+-----+-----
+   1 |   1
+   2 |   2
+   3 |   3
+(3 rows)
+
+SELECT NEXTVAL( 'test_sequence' );
+ nextval 
+---------
+       1
+(1 row)
+
+\c
+SELECT * FROM global_temp_table;
+ aid | bid 
+-----+-----
+(0 rows)
+
+SELECT NEXTVAL( 'test_sequence' );
+ nextval 
+---------
+       2
+(1 row)
+
+INSERT INTO global_temp_table DEFAULT VALUES;
+INSERT INTO global_temp_table DEFAULT VALUES;
+INSERT INTO global_temp_table DEFAULT VALUES;
+SELECT * FROM global_temp_table;
+ aid | bid 
+-----+-----
+   1 |   1
+   2 |   2
+   3 |   3
+(3 rows)
+
+SELECT NEXTVAL( 'test_sequence' );
+ nextval 
+---------
+       3
+(1 row)
+
+DROP TABLE global_temp_table;
+DROP SEQUENCE test_sequence;
diff --git a/src/test/regress/expected/session_table.out b/src/test/regress/expected/session_table.out
new file mode 100644
index 0000000..1b9b3f4
--- /dev/null
+++ b/src/test/regress/expected/session_table.out
@@ -0,0 +1,64 @@
+create session table my_private_table(x integer primary key, y integer);
+insert into my_private_table values (generate_series(1,10000), generate_series(1,10000));
+select count(*) from my_private_table;
+ count 
+-------
+ 10000
+(1 row)
+
+\c
+select count(*) from my_private_table;
+ count 
+-------
+     0
+(1 row)
+
+select * from my_private_table where x=10001;
+ x | y 
+---+---
+(0 rows)
+
+insert into my_private_table values (generate_series(1,100000), generate_series(1,100000));
+create index on my_private_table(y);
+select * from my_private_table where x=10001;
+   x   |   y   
+-------+-------
+ 10001 | 10001
+(1 row)
+
+select * from my_private_table where y=10001;
+   x   |   y   
+-------+-------
+ 10001 | 10001
+(1 row)
+
+select count(*) from my_private_table;
+ count  
+--------
+ 100000
+(1 row)
+
+\c
+select * from my_private_table where x=100001;
+ x | y 
+---+---
+(0 rows)
+
+select * from my_private_table order by y desc limit 1;
+ x | y 
+---+---
+(0 rows)
+
+insert into my_private_table values (generate_series(1,100000), generate_series(1,100000));
+select * from my_private_table where x=100001;
+ x | y 
+---+---
+(0 rows)
+
+select * from my_private_table order by y desc limit 1;
+   x    |   y    
+--------+--------
+ 100000 | 100000
+(1 row)
+
+drop table  my_private_table;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index fc0f141..507cf7d 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -107,7 +107,7 @@ test: json jsonb json_encoding jsonpath jsonpath_encoding jsonb_jsonpath
 # NB: temp.sql does a reconnect which transiently uses 2 connections,
 # so keep this parallel group to at most 19 tests
 # ----------
-test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml
+test: plancache limit plpgsql copy2 temp global_temp session_table domain rangefuncs prepare conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml
 
 # ----------
 # Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 68ac56a..3890777 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -172,6 +172,8 @@ test: limit
 test: plpgsql
 test: copy2
 test: temp
+test: global_temp
+test: session_table
 test: domain
 test: rangefuncs
 test: prepare
diff --git a/src/test/regress/sql/global_temp.sql b/src/test/regress/sql/global_temp.sql
new file mode 100644
index 0000000..3058b9b
--- /dev/null
+++ b/src/test/regress/sql/global_temp.sql
@@ -0,0 +1,151 @@
+--
+-- GLOBAL TEMP
+-- Test global temp relations
+--
+
+-- Test ON COMMIT DELETE ROWS
+
+CREATE GLOBAL TEMP TABLE global_temptest(col int) ON COMMIT DELETE ROWS;
+
+BEGIN;
+INSERT INTO global_temptest VALUES (1);
+INSERT INTO global_temptest VALUES (2);
+
+SELECT * FROM global_temptest;
+COMMIT;
+
+SELECT * FROM global_temptest;
+
+DROP TABLE global_temptest;
+
+BEGIN;
+CREATE GLOBAL TEMP TABLE global_temptest(col) ON COMMIT DELETE ROWS AS SELECT 1;
+
+SELECT * FROM global_temptest;
+COMMIT;
+
+SELECT * FROM global_temptest;
+
+DROP TABLE global_temptest;
+
+-- Test foreign keys
+BEGIN;
+CREATE GLOBAL TEMP TABLE global_temptest1(col int PRIMARY KEY);
+CREATE GLOBAL TEMP TABLE global_temptest2(col int REFERENCES global_temptest1)
+  ON COMMIT DELETE ROWS;
+INSERT INTO global_temptest1 VALUES (1);
+INSERT INTO global_temptest2 VALUES (1);
+COMMIT;
+SELECT * FROM global_temptest1;
+SELECT * FROM global_temptest2;
+
+BEGIN;
+CREATE GLOBAL TEMP TABLE global_temptest3(col int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMP TABLE global_temptest4(col int REFERENCES global_temptest3);
+COMMIT;
+
+-- For partitioned temp tables, ON COMMIT actions ignore storage-less
+-- partitioned tables.
+BEGIN;
+CREATE GLOBAL TEMP TABLE temp_parted_oncommit (a int)
+  PARTITION BY LIST (a) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMP TABLE temp_parted_oncommit_1
+  PARTITION OF temp_parted_oncommit
+  FOR VALUES IN (1) ON COMMIT DELETE ROWS;
+INSERT INTO temp_parted_oncommit VALUES (1);
+COMMIT;
+-- partitions are emptied by the previous commit
+SELECT * FROM temp_parted_oncommit;
+DROP TABLE temp_parted_oncommit;
+
+-- Using ON COMMIT DELETE on a partitioned table does not remove
+-- all rows if partitions preserve their data.
+BEGIN;
+CREATE GLOBAL TEMP TABLE global_temp_parted_oncommit_test (a int)
+  PARTITION BY LIST (a) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMP TABLE global_temp_parted_oncommit_test1
+  PARTITION OF global_temp_parted_oncommit_test
+  FOR VALUES IN (1) ON COMMIT PRESERVE ROWS;
+INSERT INTO global_temp_parted_oncommit_test VALUES (1);
+COMMIT;
+-- Data from the remaining partition is still here as its rows are
+-- preserved.
+SELECT * FROM global_temp_parted_oncommit_test;
+-- two relations remain in this case.
+SELECT relname FROM pg_class WHERE relname LIKE 'global_temp_parted_oncommit_test%';
+DROP TABLE global_temp_parted_oncommit_test;
+
+-- Check dependencies between ON COMMIT actions with inheritance trees.
+-- Data on the parent is removed, and the child goes away.
+BEGIN;
+CREATE GLOBAL TEMP TABLE global_temp_inh_oncommit_test (a int) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMP TABLE global_temp_inh_oncommit_test1 ()
+  INHERITS(global_temp_inh_oncommit_test) ON COMMIT PRESERVE ROWS;
+INSERT INTO global_temp_inh_oncommit_test1 VALUES (1);
+INSERT INTO global_temp_inh_oncommit_test VALUES (1);
+COMMIT;
+SELECT * FROM global_temp_inh_oncommit_test;
+-- two relations remain
+SELECT relname FROM pg_class WHERE relname LIKE 'global_temp_inh_oncommit_test%';
+DROP TABLE global_temp_inh_oncommit_test1;
+DROP TABLE global_temp_inh_oncommit_test;
+
+-- Global temp table cannot inherit from temporary relation
+BEGIN;
+CREATE TEMP TABLE global_temp_table (a int) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMP TABLE global_temp_table1 ()
+  INHERITS(global_temp_table) ON COMMIT PRESERVE ROWS;
+ROLLBACK;
+
+-- Temp table can inherit from global temporary relation
+BEGIN;
+CREATE GLOBAL TEMP TABLE global_temp_table (a int) ON COMMIT DELETE ROWS;
+CREATE TEMP TABLE temp_table1 ()
+  INHERITS(global_temp_table) ON COMMIT PRESERVE ROWS;
+CREATE TEMP TABLE temp_table2 ()
+  INHERITS(global_temp_table) ON COMMIT DELETE ROWS;
+INSERT INTO temp_table2 VALUES (2);
+INSERT INTO temp_table1 VALUES (1);
+INSERT INTO global_temp_table VALUES (0);
+SELECT * FROM global_temp_table;
+COMMIT;
+SELECT * FROM global_temp_table;
+DROP TABLE temp_table2;
+DROP TABLE temp_table1;
+DROP TABLE global_temp_table;
+
+-- Global temp table can inherit from normal relation
+BEGIN;
+CREATE TABLE normal_table (a int);
+CREATE GLOBAL TEMP TABLE temp_table1 ()
+  INHERITS(normal_table) ON COMMIT PRESERVE ROWS;
+CREATE GLOBAL TEMP TABLE temp_table2 ()
+  INHERITS(normal_table) ON COMMIT DELETE ROWS;
+INSERT INTO temp_table2 VALUES (2);
+INSERT INTO temp_table1 VALUES (1);
+INSERT INTO normal_table VALUES (0);
+SELECT * FROM normal_table;
+COMMIT;
+SELECT * FROM normal_table;
+DROP TABLE temp_table2;
+DROP TABLE temp_table1;
+DROP TABLE normal_table;
+
+-- Check SERIAL and BIGSERIAL pseudo-types
+CREATE GLOBAL TEMP TABLE global_temp_table ( aid BIGSERIAL, bid SERIAL );
+CREATE SEQUENCE test_sequence;
+INSERT INTO global_temp_table DEFAULT VALUES;
+INSERT INTO global_temp_table DEFAULT VALUES;
+INSERT INTO global_temp_table DEFAULT VALUES;
+SELECT * FROM global_temp_table;
+SELECT NEXTVAL( 'test_sequence' );
+\c
+SELECT * FROM global_temp_table;
+SELECT NEXTVAL( 'test_sequence' );
+INSERT INTO global_temp_table DEFAULT VALUES;
+INSERT INTO global_temp_table DEFAULT VALUES;
+INSERT INTO global_temp_table DEFAULT VALUES;
+SELECT * FROM global_temp_table;
+SELECT NEXTVAL( 'test_sequence' );
+DROP TABLE global_temp_table;
+DROP SEQUENCE test_sequence;
diff --git a/src/test/regress/sql/session_table.sql b/src/test/regress/sql/session_table.sql
new file mode 100644
index 0000000..c6663dc
--- /dev/null
+++ b/src/test/regress/sql/session_table.sql
@@ -0,0 +1,18 @@
+create session table my_private_table(x integer primary key, y integer);
+insert into my_private_table values (generate_series(1,10000), generate_series(1,10000));
+select count(*) from my_private_table;
+\c
+select count(*) from my_private_table;
+select * from my_private_table where x=10001;
+insert into my_private_table values (generate_series(1,100000), generate_series(1,100000));
+create index on my_private_table(y);
+select * from my_private_table where x=10001;
+select * from my_private_table where y=10001;
+select count(*) from my_private_table;
+\c
+select * from my_private_table where x=100001;
+select * from my_private_table order by y desc limit 1;
+insert into my_private_table values (generate_series(1,100000), generate_series(1,100000));
+select * from my_private_table where x=100001;
+select * from my_private_table order by y desc limit 1;
+drop table  my_private_table;
#18Konstantin Knizhnik
k.knizhnik@postgrespro.ru
In reply to: Pavel Stehule (#9)
1 attachment(s)
Re: [Proposal] Global temporary tables

On 25.10.2019 20:00, Pavel Stehule wrote:

So except the limitation mentioned above (which I do not

consider as critical) there is only one problem which was not
addressed: maintaining statistics for GTT.

If all of the following conditions are true:

1) GTT are used in joins
2) There are indexes defined for GTT
3) Size and histogram of GTT in different backends can

significantly vary.

4) ANALYZE was explicitly called for GTT

then query execution plan built in one backend will be also

used for other backends where it can be inefficient.

I also do not consider this problem as "show stopper" for

adding GTT to Postgres.

I think that's *definitely* a show stopper.

Well, if both you and Pavel think that it is really "show
stopper", then
this problem really has to be addressed.
I slightly confused about this opinion, because Pavel has told me
himself that 99% of users never create indexes for temp tables
or run "analyze" for them. And without it, this problem is not a
problem
at all.

Users doesn't do ANALYZE on temp tables in 99%. It's true. But second
fact is so users has lot of problems. It's very similar to wrong
statistics on persistent tables. When data are small, then it is not
problem for users, although from my perspective it's not optimal. When
data are not small, then the problem can be brutal. Temporary tables
are not a exception. And users and developers are people - we know
only about fatal problems. There are lot of unoptimized queries, but
because the problem is not fatal, then it is not reason for report it.
And lot of people has not any idea how fast the databases can be. The
knowledges of  users and app developers are sad book.

Pavel

It seems to me that I have found quite elegant solution for per-backend
statistic for GTT: I just inserting it in backend's catalog cache, but
not in pg_statistic table itself.
To do it I have to add InsertSysCache/InsertCatCache functions which
insert pinned entry in the correspondent cache.
I wonder if there are some pitfalls of such approach?

New patch for GTT is attached.

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

Attachments:

global_private_temp-4.patchtext/x-patch; name=global_private_temp-4.patchDownload
diff --git a/src/backend/access/brin/brin_revmap.c b/src/backend/access/brin/brin_revmap.c
index 647350c..ca5f22d 100644
--- a/src/backend/access/brin/brin_revmap.c
+++ b/src/backend/access/brin/brin_revmap.c
@@ -25,6 +25,7 @@
 #include "access/brin_revmap.h"
 #include "access/brin_tuple.h"
 #include "access/brin_xlog.h"
+#include "access/brin.h"
 #include "access/rmgr.h"
 #include "access/xloginsert.h"
 #include "miscadmin.h"
@@ -79,6 +80,11 @@ brinRevmapInitialize(Relation idxrel, BlockNumber *pagesPerRange,
 	meta = ReadBuffer(idxrel, BRIN_METAPAGE_BLKNO);
 	LockBuffer(meta, BUFFER_LOCK_SHARE);
 	page = BufferGetPage(meta);
+
+	if (GlobalTempRelationPageIsNotInitialized(idxrel, page))
+		brin_metapage_init(page, BrinGetPagesPerRange(idxrel),
+						   BRIN_CURRENT_VERSION);
+
 	TestForOldSnapshot(snapshot, idxrel, page);
 	metadata = (BrinMetaPageData *) PageGetContents(page);
 
diff --git a/src/backend/access/gin/ginfast.c b/src/backend/access/gin/ginfast.c
index 439a91b..8a6ac71 100644
--- a/src/backend/access/gin/ginfast.c
+++ b/src/backend/access/gin/ginfast.c
@@ -241,6 +241,16 @@ ginHeapTupleFastInsert(GinState *ginstate, GinTupleCollector *collector)
 	metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO);
 	metapage = BufferGetPage(metabuffer);
 
+	if (GlobalTempRelationPageIsNotInitialized(index, metapage))
+	{
+		Buffer rootbuffer = ReadBuffer(index, GIN_ROOT_BLKNO);
+		LockBuffer(rootbuffer, BUFFER_LOCK_EXCLUSIVE);
+		GinInitMetabuffer(metabuffer);
+		GinInitBuffer(rootbuffer, GIN_LEAF);
+		MarkBufferDirty(rootbuffer);
+		UnlockReleaseBuffer(rootbuffer);
+	}
+
 	/*
 	 * An insertion to the pending list could logically belong anywhere in the
 	 * tree, so it conflicts with all serializable scans.  All scans acquire a
diff --git a/src/backend/access/gin/ginget.c b/src/backend/access/gin/ginget.c
index b18ae2b..41bab5d 100644
--- a/src/backend/access/gin/ginget.c
+++ b/src/backend/access/gin/ginget.c
@@ -1750,7 +1750,7 @@ collectMatchesForHeapRow(IndexScanDesc scan, pendingPosition *pos)
 /*
  * Collect all matched rows from pending list into bitmap.
  */
-static void
+static bool
 scanPendingInsert(IndexScanDesc scan, TIDBitmap *tbm, int64 *ntids)
 {
 	GinScanOpaque so = (GinScanOpaque) scan->opaque;
@@ -1774,6 +1774,12 @@ scanPendingInsert(IndexScanDesc scan, TIDBitmap *tbm, int64 *ntids)
 	LockBuffer(metabuffer, GIN_SHARE);
 	page = BufferGetPage(metabuffer);
 	TestForOldSnapshot(scan->xs_snapshot, scan->indexRelation, page);
+
+	if (GlobalTempRelationPageIsNotInitialized(scan->indexRelation, page))
+	{
+		UnlockReleaseBuffer(metabuffer);
+		return false;
+	}
 	blkno = GinPageGetMeta(page)->head;
 
 	/*
@@ -1784,7 +1790,7 @@ scanPendingInsert(IndexScanDesc scan, TIDBitmap *tbm, int64 *ntids)
 	{
 		/* No pending list, so proceed with normal scan */
 		UnlockReleaseBuffer(metabuffer);
-		return;
+		return true;
 	}
 
 	pos.pendingBuffer = ReadBuffer(scan->indexRelation, blkno);
@@ -1840,6 +1846,7 @@ scanPendingInsert(IndexScanDesc scan, TIDBitmap *tbm, int64 *ntids)
 	}
 
 	pfree(pos.hasMatchKey);
+	return true;
 }
 
 
@@ -1875,7 +1882,8 @@ gingetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 	 * to scan the main index before the pending list, since concurrent
 	 * cleanup could then make us miss entries entirely.
 	 */
-	scanPendingInsert(scan, tbm, &ntids);
+	if (!scanPendingInsert(scan, tbm, &ntids))
+		return 0;
 
 	/*
 	 * Now scan the main index.
diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index 0cc8791..3215b6f 100644
--- a/src/backend/access/gist/gist.c
+++ b/src/backend/access/gist/gist.c
@@ -677,7 +677,10 @@ gistdoinsert(Relation r, IndexTuple itup, Size freespace,
 		if (!xlocked)
 		{
 			LockBuffer(stack->buffer, GIST_SHARE);
-			gistcheckpage(state.r, stack->buffer);
+			if (stack->blkno == GIST_ROOT_BLKNO && GlobalTempRelationPageIsNotInitialized(state.r, BufferGetPage(stack->buffer)))
+				GISTInitBuffer(stack->buffer, F_LEAF);
+			else
+				gistcheckpage(state.r, stack->buffer);
 		}
 
 		stack->page = (Page) BufferGetPage(stack->buffer);
diff --git a/src/backend/access/gist/gistget.c b/src/backend/access/gist/gistget.c
index 22d790d..4c52dbe 100644
--- a/src/backend/access/gist/gistget.c
+++ b/src/backend/access/gist/gistget.c
@@ -344,7 +344,10 @@ gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem,
 	buffer = ReadBuffer(scan->indexRelation, pageItem->blkno);
 	LockBuffer(buffer, GIST_SHARE);
 	PredicateLockPage(r, BufferGetBlockNumber(buffer), scan->xs_snapshot);
-	gistcheckpage(scan->indexRelation, buffer);
+	if (pageItem->blkno == GIST_ROOT_BLKNO && GlobalTempRelationPageIsNotInitialized(r, BufferGetPage(buffer)))
+		GISTInitBuffer(buffer, F_LEAF);
+	else
+		gistcheckpage(scan->indexRelation, buffer);
 	page = BufferGetPage(buffer);
 	TestForOldSnapshot(scan->xs_snapshot, r, page);
 	opaque = GistPageGetOpaque(page);
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index 45804d7..50b306a 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1028,7 +1028,7 @@ gistGetFakeLSN(Relation rel)
 {
 	static XLogRecPtr counter = FirstNormalUnloggedLSN;
 
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	if (RelationHasSessionScope(rel))
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hashpage.c b/src/backend/access/hash/hashpage.c
index 838ee68..4f794b3 100644
--- a/src/backend/access/hash/hashpage.c
+++ b/src/backend/access/hash/hashpage.c
@@ -75,13 +75,20 @@ _hash_getbuf(Relation rel, BlockNumber blkno, int access, int flags)
 
 	buf = ReadBuffer(rel, blkno);
 
-	if (access != HASH_NOLOCK)
-		LockBuffer(buf, access);
-
 	/* ref count and lock type are correct */
 
-	_hash_checkpage(rel, buf, flags);
-
+	if (blkno == HASH_METAPAGE && GlobalTempRelationPageIsNotInitialized(rel, BufferGetPage(buf)))
+	{
+		_hash_init(rel, 0, MAIN_FORKNUM);
+		if (access != HASH_NOLOCK)
+			LockBuffer(buf, access);
+	}
+	else
+	{
+		if (access != HASH_NOLOCK)
+			LockBuffer(buf, access);
+		_hash_checkpage(rel, buf, flags);
+	}
 	return buf;
 }
 
@@ -339,7 +346,7 @@ _hash_init(Relation rel, double num_tuples, ForkNumber forkNum)
 	bool		use_wal;
 
 	/* safety check */
-	if (RelationGetNumberOfBlocksInFork(rel, forkNum) != 0)
+	if (rel->rd_rel->relpersistence != RELPERSISTENCE_SESSION && RelationGetNumberOfBlocksInFork(rel, forkNum) != 0)
 		elog(ERROR, "cannot initialize non-empty hash index \"%s\"",
 			 RelationGetRelationName(rel));
 
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 2dd8821..92df373 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -673,6 +673,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 			 * init fork of an unlogged relation.
 			 */
 			if (rel->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT ||
+				rel->rd_rel->relpersistence == RELPERSISTENCE_SESSION ||
 				(rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED &&
 				 forkNum == INIT_FORKNUM))
 				log_smgrcreate(newrnode, forkNum);
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 268f869..ed3ab70 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -763,7 +763,11 @@ _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);
+		/* Session temporary relation may be not yet initialized for this backend. */
+		if (blkno == BTREE_METAPAGE && GlobalTempRelationPageIsNotInitialized(rel, BufferGetPage(buf)))
+			_bt_initmetapage(BufferGetPage(buf), P_NONE, 0);
+		else
+			_bt_checkpage(rel, buf);
 	}
 	else
 	{
diff --git a/src/backend/access/spgist/spgutils.c b/src/backend/access/spgist/spgutils.c
index 45472db..a8497a2 100644
--- a/src/backend/access/spgist/spgutils.c
+++ b/src/backend/access/spgist/spgutils.c
@@ -106,6 +106,7 @@ spgGetCache(Relation index)
 		spgConfigIn in;
 		FmgrInfo   *procinfo;
 		Buffer		metabuffer;
+		Page        metapage;
 		SpGistMetaPageData *metadata;
 
 		cache = MemoryContextAllocZero(index->rd_indexcxt,
@@ -155,12 +156,32 @@ spgGetCache(Relation index)
 		metabuffer = ReadBuffer(index, SPGIST_METAPAGE_BLKNO);
 		LockBuffer(metabuffer, BUFFER_LOCK_SHARE);
 
-		metadata = SpGistPageGetMeta(BufferGetPage(metabuffer));
+		metapage = BufferGetPage(metabuffer);
+		metadata = SpGistPageGetMeta(metapage);
 
 		if (metadata->magicNumber != SPGIST_MAGIC_NUMBER)
-			elog(ERROR, "index \"%s\" is not an SP-GiST index",
-				 RelationGetRelationName(index));
+		{
+			if (GlobalTempRelationPageIsNotInitialized(index, metapage))
+			{
+				Buffer rootbuffer = ReadBuffer(index, SPGIST_ROOT_BLKNO);
+				Buffer nullbuffer = ReadBuffer(index, SPGIST_NULL_BLKNO);
+
+				SpGistInitMetapage(metapage);
+
+				LockBuffer(rootbuffer, BUFFER_LOCK_EXCLUSIVE);
+				SpGistInitPage(BufferGetPage(rootbuffer), SPGIST_LEAF);
+				MarkBufferDirty(rootbuffer);
+				UnlockReleaseBuffer(rootbuffer);
 
+				LockBuffer(nullbuffer, BUFFER_LOCK_EXCLUSIVE);
+				SpGistInitPage(BufferGetPage(nullbuffer), SPGIST_LEAF | SPGIST_NULLS);
+				MarkBufferDirty(nullbuffer);
+				UnlockReleaseBuffer(nullbuffer);
+			}
+			else
+				elog(ERROR, "index \"%s\" is not an SP-GiST index",
+					 RelationGetRelationName(index));
+		}
 		cache->lastUsedPages = metadata->lastUsedPages;
 
 		UnlockReleaseBuffer(metabuffer);
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 1af31c2..e60bdb7 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -402,6 +402,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 		case RELPERSISTENCE_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
+		case RELPERSISTENCE_SESSION:
+			backend = BackendIdForSessionRelations();
+			break;
 		case RELPERSISTENCE_UNLOGGED:
 		case RELPERSISTENCE_PERMANENT:
 			backend = InvalidBackendId;
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index f6c31cc..d943b57 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -3652,7 +3652,7 @@ reindex_relation(Oid relid, int flags, int options)
 		if (flags & REINDEX_REL_FORCE_INDEXES_UNLOGGED)
 			persistence = RELPERSISTENCE_UNLOGGED;
 		else if (flags & REINDEX_REL_FORCE_INDEXES_PERMANENT)
-			persistence = RELPERSISTENCE_PERMANENT;
+			persistence = rel->rd_rel->relpersistence == RELPERSISTENCE_SESSION ? RELPERSISTENCE_SESSION : RELPERSISTENCE_PERMANENT;
 		else
 			persistence = rel->rd_rel->relpersistence;
 
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index 625af8d..1e192fa 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -93,6 +93,10 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 			backend = InvalidBackendId;
 			needs_wal = false;
 			break;
+		case RELPERSISTENCE_SESSION:
+			backend = BackendIdForSessionRelations();
+			needs_wal = false;
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			backend = InvalidBackendId;
 			needs_wal = true;
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 7accb95..9f2ea48 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -102,7 +102,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, bool is_global_temp);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -318,6 +318,7 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
+	bool        is_global_temp = onerel->rd_rel->relpersistence == RELPERSISTENCE_SESSION;
 
 	if (inh)
 		ereport(elevel,
@@ -575,14 +576,14 @@ 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, is_global_temp);
 
 		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, is_global_temp);
 		}
 
 		/*
@@ -1425,7 +1426,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, bool is_global_temp)
 {
 	Relation	sd;
 	int			attno;
@@ -1527,30 +1528,42 @@ 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))
+		if (is_global_temp)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+			InsertSysCache(STATRELATTINH,
+						   ObjectIdGetDatum(relid),
+						   Int16GetDatum(stats->attr->attnum),
+						   BoolGetDatum(inh),
+						   0,
+						   stup);
 		}
 		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);
 	}
 
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index a23128d..5d131a7 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -1400,7 +1400,7 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 	 */
 	if (newrelpersistence == RELPERSISTENCE_UNLOGGED)
 		reindex_flags |= REINDEX_REL_FORCE_INDEXES_UNLOGGED;
-	else if (newrelpersistence == RELPERSISTENCE_PERMANENT)
+	else if (newrelpersistence != RELPERSISTENCE_TEMP)
 		reindex_flags |= REINDEX_REL_FORCE_INDEXES_PERMANENT;
 
 	/* Report that we are now reindexing relations */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index a13322b..be661a4 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -94,7 +94,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -222,7 +222,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* now initialize the sequence's data */
 	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -327,7 +327,7 @@ ResetSequence(Oid seq_relid)
 	/*
 	 * Insert the modified tuple into the new storage file.
 	 */
-	fill_seq_with_data(seq_rel, tuple);
+	fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -340,18 +340,21 @@ ResetSequence(Oid seq_relid)
  * Initialize a sequence's relation with the specified tuple as content
  */
 static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 	sequence_magic *sm;
 	OffsetNumber offnum;
+	bool lockBuffer = false;
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ReadBuffer(rel, P_NEW);
-	Assert(BufferGetBlockNumber(buf) == 0);
-
+	if (buf == InvalidBuffer)
+	{
+		buf = ReadBuffer(rel, P_NEW);
+		Assert(BufferGetBlockNumber(buf) == 0);
+		lockBuffer = true;
+	}
 	page = BufferGetPage(buf);
 
 	PageInit(page, BufferGetPageSize(buf), sizeof(sequence_magic));
@@ -360,7 +363,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* Now insert sequence tuple */
 
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+	if (lockBuffer)
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +414,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	END_CRIT_SECTION();
 
-	UnlockReleaseBuffer(buf);
+	if (lockBuffer)
+		UnlockReleaseBuffer(buf);
 }
 
 /*
@@ -502,7 +507,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * Insert the modified tuple into the new storage file.
 		 */
-		fill_seq_with_data(seqrel, newdatatuple);
+		fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
 	}
 
 	/* process OWNED BY if given */
@@ -1178,6 +1183,17 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
 	LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
 
 	page = BufferGetPage(*buf);
+	if (GlobalTempRelationPageIsNotInitialized(rel, page))
+	{
+		/* Initialize sequence for global temporary tables */
+		Datum		value[SEQ_COL_LASTCOL] = {0};
+		bool		null[SEQ_COL_LASTCOL] = {false};
+		HeapTuple tuple;
+		value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(1); /* start sequence with 1 */
+		tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+		fill_seq_with_data(rel, tuple, *buf);
+	}
+
 	sm = (sequence_magic *) PageGetSpecialPointer(page);
 
 	if (sm->magic != SEQ_MAGIC)
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8d25d14..50d0402 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -587,7 +587,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * Check consistency of arguments
 	 */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !IsLocalRelpersistence(stmt->relation->relpersistence))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -1772,7 +1772,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 ||
+			rel->rd_rel->relpersistence == RELPERSISTENCE_SESSION)
 		{
 			/* Immediate, non-rollbackable truncation is OK */
 			heap_truncate_one_rel(rel);
@@ -7708,6 +7709,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on unlogged tables may reference only permanent or unlogged tables")));
 			break;
+		case RELPERSISTENCE_SESSION:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_SESSION)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on session tables may reference only session tables")));
+			break;
 		case RELPERSISTENCE_TEMP:
 			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
 				ereport(ERROR,
@@ -14140,6 +14147,13 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
 							RelationGetRelationName(rel)),
 					 errtable(rel)));
 			break;
+		case RELPERSISTENCE_SESSION:
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot change logged status of session table \"%s\"",
+							RelationGetRelationName(rel)),
+					 errtable(rel)));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (toLogged)
 				/* nothing to do */
@@ -14627,14 +14641,7 @@ PreCommit_on_commit_actions(void)
 				/* Do nothing (there shouldn't be such entries, actually) */
 				break;
 			case ONCOMMIT_DELETE_ROWS:
-
-				/*
-				 * If this transaction hasn't accessed any temporary
-				 * relations, we can skip truncating ON COMMIT DELETE ROWS
-				 * tables, as they must still be empty.
-				 */
-				if ((MyXactFlags & XACT_FLAGS_ACCESSEDTEMPNAMESPACE))
-					oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid);
+				oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid);
 				break;
 			case ONCOMMIT_DROP:
 				oids_to_drop = lappend_oid(oids_to_drop, oc->relid);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3f67aaf..565c868 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3266,20 +3266,11 @@ OptTemp:	TEMPORARY					{ $$ = RELPERSISTENCE_TEMP; }
 			| TEMP						{ $$ = RELPERSISTENCE_TEMP; }
 			| LOCAL TEMPORARY			{ $$ = RELPERSISTENCE_TEMP; }
 			| LOCAL TEMP				{ $$ = RELPERSISTENCE_TEMP; }
-			| GLOBAL TEMPORARY
-				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
-					$$ = RELPERSISTENCE_TEMP;
-				}
-			| GLOBAL TEMP
-				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
-					$$ = RELPERSISTENCE_TEMP;
-				}
+			| GLOBAL TEMPORARY          { $$ = RELPERSISTENCE_SESSION; }
+			| GLOBAL TEMP               { $$ = RELPERSISTENCE_SESSION; }
+			| SESSION                   { $$ = RELPERSISTENCE_SESSION; }
+			| SESSION TEMPORARY         { $$ = RELPERSISTENCE_SESSION; }
+			| SESSION TEMP              { $$ = RELPERSISTENCE_SESSION; }
 			| UNLOGGED					{ $$ = RELPERSISTENCE_UNLOGGED; }
 			| /*EMPTY*/					{ $$ = RELPERSISTENCE_PERMANENT; }
 		;
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index ee47547..ea7fe4c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,14 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->options = seqoptions;
 
 	/*
+	 * Why we should not always use persistence of parent table?
+	 * Although it is prohibited to have unlogged sequences,
+	 * unlogged tables with SERIAL fields are accepted!
+	 */
+	if (cxt->relation->relpersistence != RELPERSISTENCE_UNLOGGED)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
+	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
 	 * clause, the "redundant options" error will point to their occurrence,
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index c1dd816..dcfc134 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2157,7 +2157,7 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		if (IsLocalRelpersistence(classForm->relpersistence))
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c
index 07f3c93..5db79ec 100644
--- a/src/backend/storage/smgr/md.c
+++ b/src/backend/storage/smgr/md.c
@@ -33,6 +33,7 @@
 #include "postmaster/bgwriter.h"
 #include "storage/fd.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/md.h"
 #include "storage/relfilenode.h"
 #include "storage/smgr.h"
@@ -87,6 +88,18 @@ typedef struct _MdfdVec
 
 static MemoryContext MdCxt;		/* context for all MdfdVec objects */
 
+/*
+ * Structure used to collect information created by this backend.
+ * Data of this related should be deleted on backend exit.
+ */
+typedef struct SessionRelation
+{
+	RelFileNodeBackend rnode;
+	struct SessionRelation* next;
+} SessionRelation;
+
+
+static SessionRelation* SessionRelations;
 
 /* Populate a file tag describing an md.c segment file. */
 #define INIT_MD_FILETAG(a,xx_rnode,xx_forknum,xx_segno) \
@@ -152,6 +165,45 @@ mdinit(void)
 								  ALLOCSET_DEFAULT_SIZES);
 }
 
+
+/*
+ * Delete all data of session relations and remove their pages from shared buffers.
+ * This function is called on backend exit.
+ */
+static void
+TruncateSessionRelations(int code, Datum arg)
+{
+	SessionRelation* rel;
+	for (rel = SessionRelations; rel != NULL; rel = rel->next)
+	{
+		/* Delete relation files */
+		mdunlink(rel->rnode, InvalidForkNumber, false);
+	}
+}
+
+/*
+ * Maintain information about session relations accessed by this backend.
+ * This list is needed to perform cleanup on backend exit.
+ * Session relation is linked in this list when this relation is created or opened and file doesn't exist.
+ * Such procedure guarantee that each relation is linked into list only once.
+ */
+static void
+RegisterSessionRelation(SMgrRelation reln)
+{
+	SessionRelation* rel = (SessionRelation*)MemoryContextAlloc(TopMemoryContext, sizeof(SessionRelation));
+
+	/*
+	 * Perform session relation cleanup on backend exit. We are using shared memory hook, because
+	 * cleanup should be performed before backend is disconnected from shared memory.
+	 */
+	if (SessionRelations == NULL)
+		on_shmem_exit(TruncateSessionRelations, 0);
+
+	rel->rnode = reln->smgr_rnode;
+	rel->next = SessionRelations;
+	SessionRelations = rel;
+}
+
 /*
  *	mdexists() -- Does the physical file exist?
  *
@@ -218,6 +270,8 @@ mdcreate(SMgrRelation reln, ForkNumber forkNum, bool isRedo)
 					 errmsg("could not create file \"%s\": %m", path)));
 		}
 	}
+	if (RelFileNodeBackendIsGlobalTemp(reln->smgr_rnode))
+		RegisterSessionRelation(reln);
 
 	pfree(path);
 
@@ -465,6 +519,19 @@ mdopenfork(SMgrRelation reln, ForkNumber forknum, int behavior)
 
 	if (fd < 0)
 	{
+		/*
+		 * In case of session relation access, there may be no yet files of this relation for this backend.
+		 * If so, then create file and register session relation for truncation on backend exit.
+		 */
+		if (RelFileNodeBackendIsGlobalTemp(reln->smgr_rnode))
+		{
+			fd = PathNameOpenFile(path, O_RDWR | PG_BINARY | O_CREAT);
+			if (fd >= 0)
+			{
+				RegisterSessionRelation(reln);
+				goto NewSegment;
+			}
+		}
 		if ((behavior & EXTENSION_RETURN_NULL) &&
 			FILE_POSSIBLY_DELETED(errno))
 		{
@@ -476,6 +543,7 @@ mdopenfork(SMgrRelation reln, ForkNumber forknum, int behavior)
 				 errmsg("could not open file \"%s\": %m", path)));
 	}
 
+  NewSegment:
 	pfree(path);
 
 	_fdvec_resize(reln, forknum, 1);
@@ -652,8 +720,13 @@ mdread(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
 		 * complaining.  This allows, for example, the case of trying to
 		 * update a block that was later truncated away.
 		 */
-		if (zero_damaged_pages || InRecovery)
+		if (zero_damaged_pages || InRecovery || RelFileNodeBackendIsGlobalTemp(reln->smgr_rnode))
+		{
 			MemSet(buffer, 0, BLCKSZ);
+			/* In case of session relation we need to write zero page to provide correct result of subsequent mdnblocks */
+			if (RelFileNodeBackendIsGlobalTemp(reln->smgr_rnode))
+				mdwrite(reln, forknum, blocknum, buffer, true);
+		}
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_DATA_CORRUPTED),
@@ -738,12 +811,18 @@ mdwrite(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
 BlockNumber
 mdnblocks(SMgrRelation reln, ForkNumber forknum)
 {
-	MdfdVec    *v = mdopenfork(reln, forknum, EXTENSION_FAIL);
+	/*
+	 * If we access session relation, there may be no files yet of this relation for this backend.
+	 * Pass EXTENSION_RETURN_NULL to make mdopen return NULL in this case instead of reporting error.
+	 */
+	MdfdVec    *v = mdopenfork(reln, forknum, RelFileNodeBackendIsGlobalTemp(reln->smgr_rnode)
+							   ? EXTENSION_RETURN_NULL : EXTENSION_FAIL);
 	BlockNumber nblocks;
 	BlockNumber segno = 0;
 
 	/* mdopen has opened the first segment */
-	Assert(reln->md_num_open_segs[forknum] > 0);
+	if (reln->md_num_open_segs[forknum] == 0)
+		return 0;
 
 	/*
 	 * Start from the last open segments, to avoid redundant seeks.  We have
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index a87e721..2401361 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -994,6 +994,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 	/* Determine owning backend. */
 	switch (relform->relpersistence)
 	{
+		case RELPERSISTENCE_SESSION:
+			backend = BackendIdForSessionRelations();
+			break;
 		case RELPERSISTENCE_UNLOGGED:
 		case RELPERSISTENCE_PERMANENT:
 			backend = InvalidBackendId;
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index c3e7d94..6d86c28 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -1191,6 +1191,111 @@ SearchCatCache4(CatCache *cache,
 	return SearchCatCacheInternal(cache, 4, v1, v2, v3, v4);
 }
 
+
+void InsertCatCache(CatCache *cache,
+					Datum v1, Datum v2, Datum v3, Datum v4,
+					HeapTuple tuple)
+{
+	Datum		arguments[CATCACHE_MAXKEYS];
+	uint32		hashValue;
+	Index		hashIndex;
+	CatCTup    *ct;
+	dlist_iter	iter;
+	dlist_head *bucket;
+	int         nkeys = cache->cc_nkeys;
+	MemoryContext oldcxt;
+	int         i;
+
+	/*
+	 * one-time startup overhead for each cache
+	 */
+	if (unlikely(cache->cc_tupdesc == NULL))
+		CatalogCacheInitializeCache(cache);
+
+	/* Initialize local parameter array */
+	arguments[0] = v1;
+	arguments[1] = v2;
+	arguments[2] = v3;
+	arguments[3] = v4;
+	/*
+	 * find the hash bucket in which to look for the tuple
+	 */
+	hashValue = CatalogCacheComputeHashValue(cache, nkeys, v1, v2, v3, v4);
+	hashIndex = HASH_INDEX(hashValue, cache->cc_nbuckets);
+
+	/*
+	 * scan the hash bucket until we find a match or exhaust our tuples
+	 *
+	 * Note: it's okay to use dlist_foreach here, even though we modify the
+	 * dlist within the loop, because we don't continue the loop afterwards.
+	 */
+	bucket = &cache->cc_bucket[hashIndex];
+	dlist_foreach(iter, bucket)
+	{
+		ct = dlist_container(CatCTup, cache_elem, iter.cur);
+
+		if (ct->dead)
+			continue;			/* ignore dead entries */
+
+		if (ct->hash_value != hashValue)
+			continue;			/* quickly skip entry if wrong hash val */
+
+		if (!CatalogCacheCompareTuple(cache, nkeys, ct->keys, arguments))
+			continue;
+
+		/*
+		 * If it's a positive entry, bump its refcount and return it. If it's
+		 * negative, we can report failure to the caller.
+		 */
+		if (ct->tuple.t_len == tuple->t_len)
+		{
+			memcpy((char *) ct->tuple.t_data,
+				   (const char *) tuple->t_data,
+				   tuple->t_len);
+			return;
+		}
+		dlist_delete(&ct->cache_elem);
+		pfree(ct);
+		cache->cc_ntup -= 1;
+		CacheHdr->ch_ntup -= 1;
+		break;
+	}
+	/* Allocate memory for CatCTup and the cached tuple in one go */
+	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+
+	ct = (CatCTup *) palloc(sizeof(CatCTup) +
+							MAXIMUM_ALIGNOF + tuple->t_len);
+	ct->tuple.t_len = tuple->t_len;
+	ct->tuple.t_self = tuple->t_self;
+	ct->tuple.t_tableOid = tuple->t_tableOid;
+	ct->tuple.t_data = (HeapTupleHeader)
+		MAXALIGN(((char *) ct) + sizeof(CatCTup));
+	/* copy tuple contents */
+	memcpy((char *) ct->tuple.t_data,
+		   (const char *) tuple->t_data,
+		   tuple->t_len);
+	ct->ct_magic = CT_MAGIC;
+	ct->my_cache = cache;
+	ct->c_list = NULL;
+	ct->refcount = 1;			/* pinned*/
+	ct->dead = false;
+	ct->negative = false;
+	ct->hash_value = hashValue;
+	dlist_push_head(&cache->cc_bucket[hashIndex], &ct->cache_elem);
+	memcpy(ct->keys, arguments, nkeys*sizeof(Datum));
+
+	cache->cc_ntup++;
+	CacheHdr->ch_ntup++;
+	MemoryContextSwitchTo(oldcxt);
+
+	/*
+	 * If the hash table has become too full, enlarge the buckets array. Quite
+	 * arbitrarily, we enlarge when fill factor > 2.
+	 */
+	if (cache->cc_ntup > cache->cc_nbuckets * 2)
+		RehashCatCache(cache);
+}
+
 /*
  * Work-horse for SearchCatCache/SearchCatCacheN.
  */
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 585dcee..ce8852c 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1098,6 +1098,10 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	relation->rd_newRelfilenodeSubid = InvalidSubTransactionId;
 	switch (relation->rd_rel->relpersistence)
 	{
+		case RELPERSISTENCE_SESSION:
+			relation->rd_backend = BackendIdForSessionRelations();
+			relation->rd_islocaltemp = false;
+			break;
 		case RELPERSISTENCE_UNLOGGED:
 		case RELPERSISTENCE_PERMANENT:
 			relation->rd_backend = InvalidBackendId;
@@ -3301,6 +3305,10 @@ RelationBuildLocalRelation(const char *relname,
 	rel->rd_rel->relpersistence = relpersistence;
 	switch (relpersistence)
 	{
+		case RELPERSISTENCE_SESSION:
+			rel->rd_backend = BackendIdForSessionRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		case RELPERSISTENCE_UNLOGGED:
 		case RELPERSISTENCE_PERMANENT:
 			rel->rd_backend = InvalidBackendId;
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 16297a5..e7a4d3c 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -1164,6 +1164,16 @@ SearchSysCache4(int cacheId,
 	return SearchCatCache4(SysCache[cacheId], key1, key2, key3, key4);
 }
 
+void
+InsertSysCache(int cacheId,
+			   Datum key1, Datum key2, Datum key3, Datum key4,
+			   HeapTuple value)
+{
+	Assert(cacheId >= 0 && cacheId < SysCacheSize &&
+		   PointerIsValid(SysCache[cacheId]));
+	InsertCatCache(SysCache[cacheId], key1, key2, key3, key4, value);
+}
+
 /*
  * ReleaseSysCache
  *		Release previously grabbed reference count on a tuple
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index bf69adc..fa7479c 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15637,8 +15637,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 											 tbinfo->dobj.catId.oid, false);
 
 		appendPQExpBuffer(q, "CREATE %s%s %s",
-						  tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ?
-						  "UNLOGGED " : "",
+						  tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ? "UNLOGGED "
+						  : tbinfo->relpersistence == RELPERSISTENCE_SESSION ? "SESSION " : "",
 						  reltypename,
 						  qualrelname);
 
diff --git a/src/common/relpath.c b/src/common/relpath.c
index 62b9553..cef99d2 100644
--- a/src/common/relpath.c
+++ b/src/common/relpath.c
@@ -166,7 +166,18 @@ GetRelationPath(Oid dbNode, Oid spcNode, Oid relNode,
 		}
 		else
 		{
-			if (forkNumber != MAIN_FORKNUM)
+			/*
+			 * Session relations are distinguished from local temp relations by adding
+			 * SessionRelFirstBackendId offset to backendId.
+			 * These is no need to separate them at file system level, so just subtract SessionRelFirstBackendId
+			 * to avoid too long file names.
+			 * Segments of session relations have the same prefix (t%d_) as local temporary relations
+			 * to make it possible to cleanup them in the same way as local temporary relation files.
+			 */
+			 if (backendId >= SessionRelFirstBackendId)
+				 backendId -= SessionRelFirstBackendId;
+
+			 if (forkNumber != MAIN_FORKNUM)
 				path = psprintf("base/%u/t%d_%u_%s",
 								dbNode, backendId, relNode,
 								forkNames[forkNumber]);
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 090b6ba..6a39663 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_SESSION	's' /* session table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/storage/backendid.h b/src/include/storage/backendid.h
index 70ef8eb..f226e7c 100644
--- a/src/include/storage/backendid.h
+++ b/src/include/storage/backendid.h
@@ -22,6 +22,13 @@ typedef int BackendId;			/* unique currently active backend identifier */
 
 #define InvalidBackendId		(-1)
 
+/*
+ * We need to distinguish local and global temporary relations by RelFileNodeBackend.
+ * The least invasive change is to add some special bias value to backend id (since 
+ * maximal number of backed is limited by MaxBackends).
+ */
+#define SessionRelFirstBackendId (0x40000000)
+
 extern PGDLLIMPORT BackendId MyBackendId;	/* backend id of this backend */
 
 /* backend id of our parallel session leader, or InvalidBackendId if none */
@@ -34,4 +41,10 @@ extern PGDLLIMPORT BackendId ParallelMasterBackendId;
 #define BackendIdForTempRelations() \
 	(ParallelMasterBackendId == InvalidBackendId ? MyBackendId : ParallelMasterBackendId)
 
+
+#define BackendIdForSessionRelations() \
+	(BackendIdForTempRelations() + SessionRelFirstBackendId)
+
+#define IsSessionRelationBackendId(id) ((id) >= SessionRelFirstBackendId)
+
 #endif							/* BACKENDID_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 4ef6d8d..bac7a31 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -229,6 +229,13 @@ typedef PageHeaderData *PageHeader;
 #define PageIsNew(page) (((PageHeader) (page))->pd_upper == 0)
 
 /*
+ * Page of temporary relation is not initialized
+ */
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_SESSION && PageIsNew(page))
+
+
+/*
  * PageGetItemId
  *		Returns an item identifier of a page.
  */
diff --git a/src/include/storage/relfilenode.h b/src/include/storage/relfilenode.h
index 586500a..20aec72 100644
--- a/src/include/storage/relfilenode.h
+++ b/src/include/storage/relfilenode.h
@@ -75,10 +75,25 @@ typedef struct RelFileNodeBackend
 	BackendId	backend;
 } RelFileNodeBackend;
 
+/*
+ * Check whether it is local or global temporary relation, which data belongs only to one backend.
+ */
 #define RelFileNodeBackendIsTemp(rnode) \
 	((rnode).backend != InvalidBackendId)
 
 /*
+ * Check whether it is global temporary relation which metadata is shared by all sessions,
+ * but data is private for the current session.
+ */
+#define RelFileNodeBackendIsGlobalTemp(rnode) IsSessionRelationBackendId((rnode).backend)
+
+/*
+ * Check whether it is local temporary relation which exists only in this backend.
+ */
+#define RelFileNodeBackendIsLocalTemp(rnode) \
+	(RelFileNodeBackendIsTemp(rnode) && !RelFileNodeBackendIsGlobalTemp(rnode))
+
+/*
  * Note: RelFileNodeEquals and RelFileNodeBackendEquals compare relNode first
  * since that is most likely to be different in two unequal RelFileNodes.  It
  * is probably redundant to compare spcNode if the other fields are found equal,
diff --git a/src/include/utils/catcache.h b/src/include/utils/catcache.h
index ff1faba..31f615d 100644
--- a/src/include/utils/catcache.h
+++ b/src/include/utils/catcache.h
@@ -228,4 +228,8 @@ extern void PrepareToInvalidateCacheTuple(Relation relation,
 extern void PrintCatCacheLeakWarning(HeapTuple tuple);
 extern void PrintCatCacheListLeakWarning(CatCList *list);
 
+extern void InsertCatCache(CatCache *cache,
+						   Datum v1, Datum v2, Datum v3, Datum v4,
+						   HeapTuple tuple);
+
 #endif							/* CATCACHE_H */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index a5cf804..d42830f 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -327,6 +327,18 @@ typedef struct StdRdOptions
 	((relation)->rd_options ? \
 	 ((StdRdOptions *) (relation)->rd_options)->parallel_workers : (defaultpw))
 
+/*
+ * Relation persistence is either TEMP either SESSION
+ */
+#define IsLocalRelpersistence(relpersistence) \
+	((relpersistence) == RELPERSISTENCE_TEMP || (relpersistence) == RELPERSISTENCE_SESSION)
+
+/*
+ * Relation is either global either local temp table
+ */
+#define RelationHasSessionScope(relation) \
+	IsLocalRelpersistence(((relation)->rd_rel->relpersistence))
+
 /* ViewOptions->check_option values */
 typedef enum ViewOptCheckOption
 {
@@ -335,6 +347,7 @@ typedef enum ViewOptCheckOption
 	VIEW_OPTION_CHECK_OPTION_CASCADED
 } ViewOptCheckOption;
 
+
 /*
  * ViewOptions
  *		Contents of rd_options for views
@@ -526,7 +539,7 @@ typedef struct ViewOptions
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	RelationHasSessionScope(relation)
 
 /*
  * RELATION_IS_LOCAL
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 918765c..5b1598b 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -216,4 +216,8 @@ extern bool RelationSupportsSysCache(Oid relid);
 
 #define ReleaseSysCacheList(x)	ReleaseCatCacheList(x)
 
+
+extern void InsertSysCache(int cacheId, 
+						   Datum v1, Datum v2, Datum v3, Datum v4,
+						   HeapTuple tuple);
 #endif							/* SYSCACHE_H */
diff --git a/src/test/isolation/expected/inherit-global-temp.out b/src/test/isolation/expected/inherit-global-temp.out
new file mode 100644
index 0000000..6114f8c
--- /dev/null
+++ b/src/test/isolation/expected/inherit-global-temp.out
@@ -0,0 +1,218 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1_insert_p s1_insert_c s2_insert_c s1_select_p s1_select_c s2_select_p s2_select_c
+step s1_insert_p: INSERT INTO inh_global_parent VALUES (1), (2);
+step s1_insert_c: INSERT INTO inh_global_temp_child_s1 VALUES (3), (4);
+step s2_insert_c: INSERT INTO inh_global_temp_child_s2 VALUES (5), (6);
+step s1_select_p: SELECT a FROM inh_global_parent;
+a              
+
+1              
+2              
+3              
+4              
+step s1_select_c: SELECT a FROM inh_global_temp_child_s1;
+a              
+
+3              
+4              
+step s2_select_p: SELECT a FROM inh_global_parent;
+a              
+
+1              
+2              
+5              
+6              
+step s2_select_c: SELECT a FROM inh_global_temp_child_s2;
+a              
+
+5              
+6              
+
+starting permutation: s1_insert_p s1_insert_c s2_insert_c s1_update_p s1_update_c s1_select_p s1_select_c s2_select_p s2_select_c
+step s1_insert_p: INSERT INTO inh_global_parent VALUES (1), (2);
+step s1_insert_c: INSERT INTO inh_global_temp_child_s1 VALUES (3), (4);
+step s2_insert_c: INSERT INTO inh_global_temp_child_s2 VALUES (5), (6);
+step s1_update_p: UPDATE inh_global_parent SET a = 11 WHERE a = 1;
+step s1_update_c: UPDATE inh_global_parent SET a = 13 WHERE a IN (3, 5);
+step s1_select_p: SELECT a FROM inh_global_parent;
+a              
+
+2              
+11             
+4              
+13             
+step s1_select_c: SELECT a FROM inh_global_temp_child_s1;
+a              
+
+4              
+13             
+step s2_select_p: SELECT a FROM inh_global_parent;
+a              
+
+2              
+11             
+5              
+6              
+step s2_select_c: SELECT a FROM inh_global_temp_child_s2;
+a              
+
+5              
+6              
+
+starting permutation: s1_insert_p s1_insert_c s2_insert_c s2_update_c s1_select_p s1_select_c s2_select_p s2_select_c
+step s1_insert_p: INSERT INTO inh_global_parent VALUES (1), (2);
+step s1_insert_c: INSERT INTO inh_global_temp_child_s1 VALUES (3), (4);
+step s2_insert_c: INSERT INTO inh_global_temp_child_s2 VALUES (5), (6);
+step s2_update_c: UPDATE inh_global_parent SET a = 15 WHERE a IN (3, 5);
+step s1_select_p: SELECT a FROM inh_global_parent;
+a              
+
+1              
+2              
+3              
+4              
+step s1_select_c: SELECT a FROM inh_global_temp_child_s1;
+a              
+
+3              
+4              
+step s2_select_p: SELECT a FROM inh_global_parent;
+a              
+
+1              
+2              
+6              
+15             
+step s2_select_c: SELECT a FROM inh_global_temp_child_s2;
+a              
+
+6              
+15             
+
+starting permutation: s1_insert_p s1_insert_c s2_insert_c s1_delete_p s1_delete_c s1_select_p s1_select_c s2_select_p s2_select_c
+step s1_insert_p: INSERT INTO inh_global_parent VALUES (1), (2);
+step s1_insert_c: INSERT INTO inh_global_temp_child_s1 VALUES (3), (4);
+step s2_insert_c: INSERT INTO inh_global_temp_child_s2 VALUES (5), (6);
+step s1_delete_p: DELETE FROM inh_global_parent WHERE a = 2;
+step s1_delete_c: DELETE FROM inh_global_parent WHERE a IN (4, 6);
+step s1_select_p: SELECT a FROM inh_global_parent;
+a              
+
+1              
+3              
+step s1_select_c: SELECT a FROM inh_global_temp_child_s1;
+a              
+
+3              
+step s2_select_p: SELECT a FROM inh_global_parent;
+a              
+
+1              
+5              
+6              
+step s2_select_c: SELECT a FROM inh_global_temp_child_s2;
+a              
+
+5              
+6              
+
+starting permutation: s1_insert_p s1_insert_c s2_insert_c s2_delete_c s1_select_p s1_select_c s2_select_p s2_select_c
+step s1_insert_p: INSERT INTO inh_global_parent VALUES (1), (2);
+step s1_insert_c: INSERT INTO inh_global_temp_child_s1 VALUES (3), (4);
+step s2_insert_c: INSERT INTO inh_global_temp_child_s2 VALUES (5), (6);
+step s2_delete_c: DELETE FROM inh_global_parent WHERE a IN (4, 6);
+step s1_select_p: SELECT a FROM inh_global_parent;
+a              
+
+1              
+2              
+3              
+4              
+step s1_select_c: SELECT a FROM inh_global_temp_child_s1;
+a              
+
+3              
+4              
+step s2_select_p: SELECT a FROM inh_global_parent;
+a              
+
+1              
+2              
+5              
+step s2_select_c: SELECT a FROM inh_global_temp_child_s2;
+a              
+
+5              
+
+starting permutation: s1_insert_p s1_insert_c s2_insert_c s1_truncate_p s1_select_p s1_select_c s2_select_p s2_select_c
+step s1_insert_p: INSERT INTO inh_global_parent VALUES (1), (2);
+step s1_insert_c: INSERT INTO inh_global_temp_child_s1 VALUES (3), (4);
+step s2_insert_c: INSERT INTO inh_global_temp_child_s2 VALUES (5), (6);
+step s1_truncate_p: TRUNCATE inh_global_parent;
+step s1_select_p: SELECT a FROM inh_global_parent;
+a              
+
+step s1_select_c: SELECT a FROM inh_global_temp_child_s1;
+a              
+
+step s2_select_p: SELECT a FROM inh_global_parent;
+a              
+
+5              
+6              
+step s2_select_c: SELECT a FROM inh_global_temp_child_s2;
+a              
+
+5              
+6              
+
+starting permutation: s1_insert_p s1_insert_c s2_insert_c s2_truncate_p s1_select_p s1_select_c s2_select_p s2_select_c
+step s1_insert_p: INSERT INTO inh_global_parent VALUES (1), (2);
+step s1_insert_c: INSERT INTO inh_global_temp_child_s1 VALUES (3), (4);
+step s2_insert_c: INSERT INTO inh_global_temp_child_s2 VALUES (5), (6);
+step s2_truncate_p: TRUNCATE inh_global_parent;
+step s1_select_p: SELECT a FROM inh_global_parent;
+a              
+
+3              
+4              
+step s1_select_c: SELECT a FROM inh_global_temp_child_s1;
+a              
+
+3              
+4              
+step s2_select_p: SELECT a FROM inh_global_parent;
+a              
+
+step s2_select_c: SELECT a FROM inh_global_temp_child_s2;
+a              
+
+
+starting permutation: s1_insert_p s1_insert_c s2_insert_c s1_begin s1_truncate_p s2_select_p s1_commit
+step s1_insert_p: INSERT INTO inh_global_parent VALUES (1), (2);
+step s1_insert_c: INSERT INTO inh_global_temp_child_s1 VALUES (3), (4);
+step s2_insert_c: INSERT INTO inh_global_temp_child_s2 VALUES (5), (6);
+step s1_begin: BEGIN;
+step s1_truncate_p: TRUNCATE inh_global_parent;
+step s2_select_p: SELECT a FROM inh_global_parent; <waiting ...>
+step s1_commit: COMMIT;
+step s2_select_p: <... completed>
+a              
+
+5              
+6              
+
+starting permutation: s1_insert_p s1_insert_c s2_insert_c s1_begin s1_truncate_p s2_select_c s1_commit
+step s1_insert_p: INSERT INTO inh_global_parent VALUES (1), (2);
+step s1_insert_c: INSERT INTO inh_global_temp_child_s1 VALUES (3), (4);
+step s2_insert_c: INSERT INTO inh_global_temp_child_s2 VALUES (5), (6);
+step s1_begin: BEGIN;
+step s1_truncate_p: TRUNCATE inh_global_parent;
+step s2_select_c: SELECT a FROM inh_global_temp_child_s2; <waiting ...>
+step s1_commit: COMMIT;
+step s2_select_c: <... completed>
+a              
+
+5              
+6              
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index a2fa192..ef7aa85 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -88,3 +88,4 @@ test: plpgsql-toast
 test: truncate-conflict
 test: serializable-parallel
 test: serializable-parallel-2
+test: inherit-global-temp
diff --git a/src/test/isolation/specs/inherit-global-temp.spec b/src/test/isolation/specs/inherit-global-temp.spec
new file mode 100644
index 0000000..5e95dd6
--- /dev/null
+++ b/src/test/isolation/specs/inherit-global-temp.spec
@@ -0,0 +1,73 @@
+# This is a copy of the inherit-temp test with little changes for global temporary tables.
+#
+
+setup
+{
+  CREATE TABLE inh_global_parent (a int);
+}
+
+teardown
+{
+  DROP TABLE inh_global_parent;
+}
+
+# Session 1 executes actions which act directly on both the parent and
+# its child.  Abbreviation "c" is used for queries working on the child
+# and "p" on the parent.
+session "s1"
+setup
+{
+  CREATE GLOBAL TEMPORARY TABLE inh_global_temp_child_s1 () INHERITS (inh_global_parent);
+}
+step "s1_begin" { BEGIN; }
+step "s1_truncate_p" { TRUNCATE inh_global_parent; }
+step "s1_select_p" { SELECT a FROM inh_global_parent; }
+step "s1_select_c" { SELECT a FROM inh_global_temp_child_s1; }
+step "s1_insert_p" { INSERT INTO inh_global_parent VALUES (1), (2); }
+step "s1_insert_c" { INSERT INTO inh_global_temp_child_s1 VALUES (3), (4); }
+step "s1_update_p" { UPDATE inh_global_parent SET a = 11 WHERE a = 1; }
+step "s1_update_c" { UPDATE inh_global_parent SET a = 13 WHERE a IN (3, 5); }
+step "s1_delete_p" { DELETE FROM inh_global_parent WHERE a = 2; }
+step "s1_delete_c" { DELETE FROM inh_global_parent WHERE a IN (4, 6); }
+step "s1_commit" { COMMIT; }
+teardown
+{
+  DROP TABLE inh_global_temp_child_s1;
+}
+
+# Session 2 executes actions on the parent which act only on the child.
+session "s2"
+setup
+{
+  CREATE GLOBAL TEMPORARY TABLE inh_global_temp_child_s2 () INHERITS (inh_global_parent);
+}
+step "s2_truncate_p" { TRUNCATE inh_global_parent; }
+step "s2_select_p" { SELECT a FROM inh_global_parent; }
+step "s2_select_c" { SELECT a FROM inh_global_temp_child_s2; }
+step "s2_insert_c" { INSERT INTO inh_global_temp_child_s2 VALUES (5), (6); }
+step "s2_update_c" { UPDATE inh_global_parent SET a = 15 WHERE a IN (3, 5); }
+step "s2_delete_c" { DELETE FROM inh_global_parent WHERE a IN (4, 6); }
+teardown
+{
+  DROP TABLE inh_global_temp_child_s2;
+}
+
+# Check INSERT behavior across sessions
+permutation "s1_insert_p" "s1_insert_c" "s2_insert_c" "s1_select_p" "s1_select_c" "s2_select_p" "s2_select_c"
+
+# Check UPDATE behavior across sessions
+permutation "s1_insert_p" "s1_insert_c" "s2_insert_c" "s1_update_p" "s1_update_c" "s1_select_p" "s1_select_c" "s2_select_p" "s2_select_c"
+permutation "s1_insert_p" "s1_insert_c" "s2_insert_c" "s2_update_c" "s1_select_p" "s1_select_c" "s2_select_p" "s2_select_c"
+
+# Check DELETE behavior across sessions
+permutation "s1_insert_p" "s1_insert_c" "s2_insert_c" "s1_delete_p" "s1_delete_c" "s1_select_p" "s1_select_c" "s2_select_p" "s2_select_c"
+permutation "s1_insert_p" "s1_insert_c" "s2_insert_c" "s2_delete_c" "s1_select_p" "s1_select_c" "s2_select_p" "s2_select_c"
+
+# Check TRUNCATE behavior across sessions
+permutation "s1_insert_p" "s1_insert_c" "s2_insert_c" "s1_truncate_p" "s1_select_p" "s1_select_c" "s2_select_p" "s2_select_c"
+permutation "s1_insert_p" "s1_insert_c" "s2_insert_c" "s2_truncate_p" "s1_select_p" "s1_select_c" "s2_select_p" "s2_select_c"
+
+# TRUNCATE on a parent tree does not block access to temporary child relation
+# of another session, and blocks when scanning the parent.
+permutation "s1_insert_p" "s1_insert_c" "s2_insert_c" "s1_begin" "s1_truncate_p" "s2_select_p" "s1_commit"
+permutation "s1_insert_p" "s1_insert_c" "s2_insert_c" "s1_begin" "s1_truncate_p" "s2_select_c" "s1_commit"
diff --git a/src/test/regress/expected/global_temp.out b/src/test/regress/expected/global_temp.out
new file mode 100644
index 0000000..ae1adb6
--- /dev/null
+++ b/src/test/regress/expected/global_temp.out
@@ -0,0 +1,247 @@
+--
+-- GLOBAL TEMP
+-- Test global temp relations
+--
+-- Test ON COMMIT DELETE ROWS
+CREATE GLOBAL TEMP TABLE global_temptest(col int) ON COMMIT DELETE ROWS;
+BEGIN;
+INSERT INTO global_temptest VALUES (1);
+INSERT INTO global_temptest VALUES (2);
+SELECT * FROM global_temptest;
+ col 
+-----
+   1
+   2
+(2 rows)
+
+COMMIT;
+SELECT * FROM global_temptest;
+ col 
+-----
+(0 rows)
+
+DROP TABLE global_temptest;
+BEGIN;
+CREATE GLOBAL TEMP TABLE global_temptest(col) ON COMMIT DELETE ROWS AS SELECT 1;
+SELECT * FROM global_temptest;
+ col 
+-----
+   1
+(1 row)
+
+COMMIT;
+SELECT * FROM global_temptest;
+ col 
+-----
+(0 rows)
+
+DROP TABLE global_temptest;
+-- Test foreign keys
+BEGIN;
+CREATE GLOBAL TEMP TABLE global_temptest1(col int PRIMARY KEY);
+CREATE GLOBAL TEMP TABLE global_temptest2(col int REFERENCES global_temptest1)
+  ON COMMIT DELETE ROWS;
+INSERT INTO global_temptest1 VALUES (1);
+INSERT INTO global_temptest2 VALUES (1);
+COMMIT;
+SELECT * FROM global_temptest1;
+ col 
+-----
+   1
+(1 row)
+
+SELECT * FROM global_temptest2;
+ col 
+-----
+(0 rows)
+
+BEGIN;
+CREATE GLOBAL TEMP TABLE global_temptest3(col int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMP TABLE global_temptest4(col int REFERENCES global_temptest3);
+COMMIT;
+ERROR:  unsupported ON COMMIT and foreign key combination
+DETAIL:  Table "global_temptest4" references "global_temptest3", but they do not have the same ON COMMIT setting.
+-- For partitioned temp tables, ON COMMIT actions ignore storage-less
+-- partitioned tables.
+BEGIN;
+CREATE GLOBAL TEMP TABLE temp_parted_oncommit (a int)
+  PARTITION BY LIST (a) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMP TABLE temp_parted_oncommit_1
+  PARTITION OF temp_parted_oncommit
+  FOR VALUES IN (1) ON COMMIT DELETE ROWS;
+INSERT INTO temp_parted_oncommit VALUES (1);
+COMMIT;
+-- partitions are emptied by the previous commit
+SELECT * FROM temp_parted_oncommit;
+ a 
+---
+(0 rows)
+
+DROP TABLE temp_parted_oncommit;
+-- Using ON COMMIT DELETE on a partitioned table does not remove
+-- all rows if partitions preserve their data.
+BEGIN;
+CREATE GLOBAL TEMP TABLE global_temp_parted_oncommit_test (a int)
+  PARTITION BY LIST (a) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMP TABLE global_temp_parted_oncommit_test1
+  PARTITION OF global_temp_parted_oncommit_test
+  FOR VALUES IN (1) ON COMMIT PRESERVE ROWS;
+INSERT INTO global_temp_parted_oncommit_test VALUES (1);
+COMMIT;
+-- Data from the remaining partition is still here as its rows are
+-- preserved.
+SELECT * FROM global_temp_parted_oncommit_test;
+ a 
+---
+ 1
+(1 row)
+
+-- two relations remain in this case.
+SELECT relname FROM pg_class WHERE relname LIKE 'global_temp_parted_oncommit_test%';
+              relname              
+-----------------------------------
+ global_temp_parted_oncommit_test
+ global_temp_parted_oncommit_test1
+(2 rows)
+
+DROP TABLE global_temp_parted_oncommit_test;
+-- Check dependencies between ON COMMIT actions with inheritance trees.
+-- Data on the parent is removed, and the child goes away.
+BEGIN;
+CREATE GLOBAL TEMP TABLE global_temp_inh_oncommit_test (a int) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMP TABLE global_temp_inh_oncommit_test1 ()
+  INHERITS(global_temp_inh_oncommit_test) ON COMMIT PRESERVE ROWS;
+INSERT INTO global_temp_inh_oncommit_test1 VALUES (1);
+INSERT INTO global_temp_inh_oncommit_test VALUES (1);
+COMMIT;
+SELECT * FROM global_temp_inh_oncommit_test;
+ a 
+---
+ 1
+(1 row)
+
+-- two relations remain
+SELECT relname FROM pg_class WHERE relname LIKE 'global_temp_inh_oncommit_test%';
+            relname             
+--------------------------------
+ global_temp_inh_oncommit_test
+ global_temp_inh_oncommit_test1
+(2 rows)
+
+DROP TABLE global_temp_inh_oncommit_test1;
+DROP TABLE global_temp_inh_oncommit_test;
+-- Global temp table cannot inherit from temporary relation
+BEGIN;
+CREATE TEMP TABLE global_temp_table (a int) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMP TABLE global_temp_table1 ()
+  INHERITS(global_temp_table) ON COMMIT PRESERVE ROWS;
+ERROR:  cannot inherit from temporary relation "global_temp_table"
+ROLLBACK;
+-- Temp table can inherit from global temporary relation
+BEGIN;
+CREATE GLOBAL TEMP TABLE global_temp_table (a int) ON COMMIT DELETE ROWS;
+CREATE TEMP TABLE temp_table1 ()
+  INHERITS(global_temp_table) ON COMMIT PRESERVE ROWS;
+CREATE TEMP TABLE temp_table2 ()
+  INHERITS(global_temp_table) ON COMMIT DELETE ROWS;
+INSERT INTO temp_table2 VALUES (2);
+INSERT INTO temp_table1 VALUES (1);
+INSERT INTO global_temp_table VALUES (0);
+SELECT * FROM global_temp_table;
+ a 
+---
+ 0
+ 1
+ 2
+(3 rows)
+
+COMMIT;
+SELECT * FROM global_temp_table;
+ a 
+---
+ 1
+(1 row)
+
+DROP TABLE temp_table2;
+DROP TABLE temp_table1;
+DROP TABLE global_temp_table;
+-- Global temp table can inherit from normal relation
+BEGIN;
+CREATE TABLE normal_table (a int);
+CREATE GLOBAL TEMP TABLE temp_table1 ()
+  INHERITS(normal_table) ON COMMIT PRESERVE ROWS;
+CREATE GLOBAL TEMP TABLE temp_table2 ()
+  INHERITS(normal_table) ON COMMIT DELETE ROWS;
+INSERT INTO temp_table2 VALUES (2);
+INSERT INTO temp_table1 VALUES (1);
+INSERT INTO normal_table VALUES (0);
+SELECT * FROM normal_table;
+ a 
+---
+ 0
+ 1
+ 2
+(3 rows)
+
+COMMIT;
+SELECT * FROM normal_table;
+ a 
+---
+ 0
+ 1
+(2 rows)
+
+DROP TABLE temp_table2;
+DROP TABLE temp_table1;
+DROP TABLE normal_table;
+-- Check SERIAL and BIGSERIAL pseudo-types
+CREATE GLOBAL TEMP TABLE global_temp_table ( aid BIGSERIAL, bid SERIAL );
+CREATE SEQUENCE test_sequence;
+INSERT INTO global_temp_table DEFAULT VALUES;
+INSERT INTO global_temp_table DEFAULT VALUES;
+INSERT INTO global_temp_table DEFAULT VALUES;
+SELECT * FROM global_temp_table;
+ aid | bid 
+-----+-----
+   1 |   1
+   2 |   2
+   3 |   3
+(3 rows)
+
+SELECT NEXTVAL( 'test_sequence' );
+ nextval 
+---------
+       1
+(1 row)
+
+\c
+SELECT * FROM global_temp_table;
+ aid | bid 
+-----+-----
+(0 rows)
+
+SELECT NEXTVAL( 'test_sequence' );
+ nextval 
+---------
+       2
+(1 row)
+
+INSERT INTO global_temp_table DEFAULT VALUES;
+INSERT INTO global_temp_table DEFAULT VALUES;
+INSERT INTO global_temp_table DEFAULT VALUES;
+SELECT * FROM global_temp_table;
+ aid | bid 
+-----+-----
+   1 |   1
+   2 |   2
+   3 |   3
+(3 rows)
+
+SELECT NEXTVAL( 'test_sequence' );
+ nextval 
+---------
+       3
+(1 row)
+
+DROP TABLE global_temp_table;
+DROP SEQUENCE test_sequence;
diff --git a/src/test/regress/expected/session_table.out b/src/test/regress/expected/session_table.out
new file mode 100644
index 0000000..1b9b3f4
--- /dev/null
+++ b/src/test/regress/expected/session_table.out
@@ -0,0 +1,64 @@
+create session table my_private_table(x integer primary key, y integer);
+insert into my_private_table values (generate_series(1,10000), generate_series(1,10000));
+select count(*) from my_private_table;
+ count 
+-------
+ 10000
+(1 row)
+
+\c
+select count(*) from my_private_table;
+ count 
+-------
+     0
+(1 row)
+
+select * from my_private_table where x=10001;
+ x | y 
+---+---
+(0 rows)
+
+insert into my_private_table values (generate_series(1,100000), generate_series(1,100000));
+create index on my_private_table(y);
+select * from my_private_table where x=10001;
+   x   |   y   
+-------+-------
+ 10001 | 10001
+(1 row)
+
+select * from my_private_table where y=10001;
+   x   |   y   
+-------+-------
+ 10001 | 10001
+(1 row)
+
+select count(*) from my_private_table;
+ count  
+--------
+ 100000
+(1 row)
+
+\c
+select * from my_private_table where x=100001;
+ x | y 
+---+---
+(0 rows)
+
+select * from my_private_table order by y desc limit 1;
+ x | y 
+---+---
+(0 rows)
+
+insert into my_private_table values (generate_series(1,100000), generate_series(1,100000));
+select * from my_private_table where x=100001;
+ x | y 
+---+---
+(0 rows)
+
+select * from my_private_table order by y desc limit 1;
+   x    |   y    
+--------+--------
+ 100000 | 100000
+(1 row)
+
+drop table  my_private_table;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index fc0f141..507cf7d 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -107,7 +107,7 @@ test: json jsonb json_encoding jsonpath jsonpath_encoding jsonb_jsonpath
 # NB: temp.sql does a reconnect which transiently uses 2 connections,
 # so keep this parallel group to at most 19 tests
 # ----------
-test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml
+test: plancache limit plpgsql copy2 temp global_temp session_table domain rangefuncs prepare conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml
 
 # ----------
 # Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 68ac56a..3890777 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -172,6 +172,8 @@ test: limit
 test: plpgsql
 test: copy2
 test: temp
+test: global_temp
+test: session_table
 test: domain
 test: rangefuncs
 test: prepare
diff --git a/src/test/regress/sql/global_temp.sql b/src/test/regress/sql/global_temp.sql
new file mode 100644
index 0000000..3058b9b
--- /dev/null
+++ b/src/test/regress/sql/global_temp.sql
@@ -0,0 +1,151 @@
+--
+-- GLOBAL TEMP
+-- Test global temp relations
+--
+
+-- Test ON COMMIT DELETE ROWS
+
+CREATE GLOBAL TEMP TABLE global_temptest(col int) ON COMMIT DELETE ROWS;
+
+BEGIN;
+INSERT INTO global_temptest VALUES (1);
+INSERT INTO global_temptest VALUES (2);
+
+SELECT * FROM global_temptest;
+COMMIT;
+
+SELECT * FROM global_temptest;
+
+DROP TABLE global_temptest;
+
+BEGIN;
+CREATE GLOBAL TEMP TABLE global_temptest(col) ON COMMIT DELETE ROWS AS SELECT 1;
+
+SELECT * FROM global_temptest;
+COMMIT;
+
+SELECT * FROM global_temptest;
+
+DROP TABLE global_temptest;
+
+-- Test foreign keys
+BEGIN;
+CREATE GLOBAL TEMP TABLE global_temptest1(col int PRIMARY KEY);
+CREATE GLOBAL TEMP TABLE global_temptest2(col int REFERENCES global_temptest1)
+  ON COMMIT DELETE ROWS;
+INSERT INTO global_temptest1 VALUES (1);
+INSERT INTO global_temptest2 VALUES (1);
+COMMIT;
+SELECT * FROM global_temptest1;
+SELECT * FROM global_temptest2;
+
+BEGIN;
+CREATE GLOBAL TEMP TABLE global_temptest3(col int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMP TABLE global_temptest4(col int REFERENCES global_temptest3);
+COMMIT;
+
+-- For partitioned temp tables, ON COMMIT actions ignore storage-less
+-- partitioned tables.
+BEGIN;
+CREATE GLOBAL TEMP TABLE temp_parted_oncommit (a int)
+  PARTITION BY LIST (a) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMP TABLE temp_parted_oncommit_1
+  PARTITION OF temp_parted_oncommit
+  FOR VALUES IN (1) ON COMMIT DELETE ROWS;
+INSERT INTO temp_parted_oncommit VALUES (1);
+COMMIT;
+-- partitions are emptied by the previous commit
+SELECT * FROM temp_parted_oncommit;
+DROP TABLE temp_parted_oncommit;
+
+-- Using ON COMMIT DELETE on a partitioned table does not remove
+-- all rows if partitions preserve their data.
+BEGIN;
+CREATE GLOBAL TEMP TABLE global_temp_parted_oncommit_test (a int)
+  PARTITION BY LIST (a) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMP TABLE global_temp_parted_oncommit_test1
+  PARTITION OF global_temp_parted_oncommit_test
+  FOR VALUES IN (1) ON COMMIT PRESERVE ROWS;
+INSERT INTO global_temp_parted_oncommit_test VALUES (1);
+COMMIT;
+-- Data from the remaining partition is still here as its rows are
+-- preserved.
+SELECT * FROM global_temp_parted_oncommit_test;
+-- two relations remain in this case.
+SELECT relname FROM pg_class WHERE relname LIKE 'global_temp_parted_oncommit_test%';
+DROP TABLE global_temp_parted_oncommit_test;
+
+-- Check dependencies between ON COMMIT actions with inheritance trees.
+-- Data on the parent is removed, and the child goes away.
+BEGIN;
+CREATE GLOBAL TEMP TABLE global_temp_inh_oncommit_test (a int) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMP TABLE global_temp_inh_oncommit_test1 ()
+  INHERITS(global_temp_inh_oncommit_test) ON COMMIT PRESERVE ROWS;
+INSERT INTO global_temp_inh_oncommit_test1 VALUES (1);
+INSERT INTO global_temp_inh_oncommit_test VALUES (1);
+COMMIT;
+SELECT * FROM global_temp_inh_oncommit_test;
+-- two relations remain
+SELECT relname FROM pg_class WHERE relname LIKE 'global_temp_inh_oncommit_test%';
+DROP TABLE global_temp_inh_oncommit_test1;
+DROP TABLE global_temp_inh_oncommit_test;
+
+-- Global temp table cannot inherit from temporary relation
+BEGIN;
+CREATE TEMP TABLE global_temp_table (a int) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMP TABLE global_temp_table1 ()
+  INHERITS(global_temp_table) ON COMMIT PRESERVE ROWS;
+ROLLBACK;
+
+-- Temp table can inherit from global temporary relation
+BEGIN;
+CREATE GLOBAL TEMP TABLE global_temp_table (a int) ON COMMIT DELETE ROWS;
+CREATE TEMP TABLE temp_table1 ()
+  INHERITS(global_temp_table) ON COMMIT PRESERVE ROWS;
+CREATE TEMP TABLE temp_table2 ()
+  INHERITS(global_temp_table) ON COMMIT DELETE ROWS;
+INSERT INTO temp_table2 VALUES (2);
+INSERT INTO temp_table1 VALUES (1);
+INSERT INTO global_temp_table VALUES (0);
+SELECT * FROM global_temp_table;
+COMMIT;
+SELECT * FROM global_temp_table;
+DROP TABLE temp_table2;
+DROP TABLE temp_table1;
+DROP TABLE global_temp_table;
+
+-- Global temp table can inherit from normal relation
+BEGIN;
+CREATE TABLE normal_table (a int);
+CREATE GLOBAL TEMP TABLE temp_table1 ()
+  INHERITS(normal_table) ON COMMIT PRESERVE ROWS;
+CREATE GLOBAL TEMP TABLE temp_table2 ()
+  INHERITS(normal_table) ON COMMIT DELETE ROWS;
+INSERT INTO temp_table2 VALUES (2);
+INSERT INTO temp_table1 VALUES (1);
+INSERT INTO normal_table VALUES (0);
+SELECT * FROM normal_table;
+COMMIT;
+SELECT * FROM normal_table;
+DROP TABLE temp_table2;
+DROP TABLE temp_table1;
+DROP TABLE normal_table;
+
+-- Check SERIAL and BIGSERIAL pseudo-types
+CREATE GLOBAL TEMP TABLE global_temp_table ( aid BIGSERIAL, bid SERIAL );
+CREATE SEQUENCE test_sequence;
+INSERT INTO global_temp_table DEFAULT VALUES;
+INSERT INTO global_temp_table DEFAULT VALUES;
+INSERT INTO global_temp_table DEFAULT VALUES;
+SELECT * FROM global_temp_table;
+SELECT NEXTVAL( 'test_sequence' );
+\c
+SELECT * FROM global_temp_table;
+SELECT NEXTVAL( 'test_sequence' );
+INSERT INTO global_temp_table DEFAULT VALUES;
+INSERT INTO global_temp_table DEFAULT VALUES;
+INSERT INTO global_temp_table DEFAULT VALUES;
+SELECT * FROM global_temp_table;
+SELECT NEXTVAL( 'test_sequence' );
+DROP TABLE global_temp_table;
+DROP SEQUENCE test_sequence;
diff --git a/src/test/regress/sql/session_table.sql b/src/test/regress/sql/session_table.sql
new file mode 100644
index 0000000..c6663dc
--- /dev/null
+++ b/src/test/regress/sql/session_table.sql
@@ -0,0 +1,18 @@
+create session table my_private_table(x integer primary key, y integer);
+insert into my_private_table values (generate_series(1,10000), generate_series(1,10000));
+select count(*) from my_private_table;
+\c
+select count(*) from my_private_table;
+select * from my_private_table where x=10001;
+insert into my_private_table values (generate_series(1,100000), generate_series(1,100000));
+create index on my_private_table(y);
+select * from my_private_table where x=10001;
+select * from my_private_table where y=10001;
+select count(*) from my_private_table;
+\c
+select * from my_private_table where x=100001;
+select * from my_private_table order by y desc limit 1;
+insert into my_private_table values (generate_series(1,100000), generate_series(1,100000));
+select * from my_private_table where x=100001;
+select * from my_private_table order by y desc limit 1;
+drop table  my_private_table;
#19Robert Haas
robertmhaas@gmail.com
In reply to: Konstantin Knizhnik (#18)
Re: [Proposal] Global temporary tables

On Fri, Nov 1, 2019 at 11:15 AM Konstantin Knizhnik
<k.knizhnik@postgrespro.ru> wrote:

It seems to me that I have found quite elegant solution for per-backend statistic for GTT: I just inserting it in backend's catalog cache, but not in pg_statistic table itself.
To do it I have to add InsertSysCache/InsertCatCache functions which insert pinned entry in the correspondent cache.
I wonder if there are some pitfalls of such approach?

That sounds pretty hackish. You'd have to be very careful, for
example, that if the tables were dropped or re-analyzed, all of the
old entries got removed -- and then it would still fail if any code
tried to access the statistics directly from the table, rather than
via the caches. My assumption is that the statistics ought to be
stored in some backend-private data structure designed for that
purpose, and that the code that needs the data should be taught to
look for it there when the table is a GTT.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#20Konstantin Knizhnik
k.knizhnik@postgrespro.ru
In reply to: Robert Haas (#19)
Re: [Proposal] Global temporary tables

On 01.11.2019 18:26, Robert Haas wrote:

On Fri, Nov 1, 2019 at 11:15 AM Konstantin Knizhnik
<k.knizhnik@postgrespro.ru> wrote:

It seems to me that I have found quite elegant solution for per-backend statistic for GTT: I just inserting it in backend's catalog cache, but not in pg_statistic table itself.
To do it I have to add InsertSysCache/InsertCatCache functions which insert pinned entry in the correspondent cache.
I wonder if there are some pitfalls of such approach?

That sounds pretty hackish. You'd have to be very careful, for
example, that if the tables were dropped or re-analyzed, all of the
old entries got removed --

I have checked it:
- when table is reanalyzed, then cache entries are replaced.
- when table is dropped, then cache entries are removed.

and then it would still fail if any code
tried to access the statistics directly from the table, rather than
via the caches. My assumption is that the statistics ought to be
stored in some backend-private data structure designed for that
purpose, and that the code that needs the data should be taught to
look for it there when the table is a GTT.

Yes, if you do "select * from pg_statistic" then you will not see
statistic for GTT in this case.
But I do not think that it is so critical. I do not believe that anybody
is trying to manually interpret values in this table.
And optimizer is retrieving statistic through sys-cache mechanism and so
is able to build correct plan in this case.

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

#21Pavel Stehule
pavel.stehule@gmail.com
In reply to: Konstantin Knizhnik (#20)
Re: [Proposal] Global temporary tables

pá 1. 11. 2019 v 17:09 odesílatel Konstantin Knizhnik <
k.knizhnik@postgrespro.ru> napsal:

On 01.11.2019 18:26, Robert Haas wrote:

On Fri, Nov 1, 2019 at 11:15 AM Konstantin Knizhnik
<k.knizhnik@postgrespro.ru> wrote:

It seems to me that I have found quite elegant solution for per-backend

statistic for GTT: I just inserting it in backend's catalog cache, but not
in pg_statistic table itself.

To do it I have to add InsertSysCache/InsertCatCache functions which

insert pinned entry in the correspondent cache.

I wonder if there are some pitfalls of such approach?

That sounds pretty hackish. You'd have to be very careful, for
example, that if the tables were dropped or re-analyzed, all of the
old entries got removed --

I have checked it:
- when table is reanalyzed, then cache entries are replaced.
- when table is dropped, then cache entries are removed.

and then it would still fail if any code
tried to access the statistics directly from the table, rather than
via the caches. My assumption is that the statistics ought to be
stored in some backend-private data structure designed for that
purpose, and that the code that needs the data should be taught to
look for it there when the table is a GTT.

Yes, if you do "select * from pg_statistic" then you will not see
statistic for GTT in this case.
But I do not think that it is so critical. I do not believe that anybody
is trying to manually interpret values in this table.
And optimizer is retrieving statistic through sys-cache mechanism and so
is able to build correct plan in this case.

Years ago, when I though about it, I wrote patch with similar design. It's
working, but surely it's ugly.

I have another idea. Can be pg_statistics view instead a table?

Some like

SELECT * FROM pg_catalog.pg_statistics_rel
UNION ALL
SELECT * FROM pg_catalog.pg_statistics_gtt();

Internally - when stat cache is filled, then there can be used
pg_statistics_rel and pg_statistics_gtt() directly. What I remember, there
was not possibility to work with queries, only with just relations.

Or crazy idea - today we can implement own types of heaps. Is possible to
create engine where result can be combination of some shared data and local
data. So union will be implemented on heap level.
This implementation can be simple, just scanning pages from shared buffers
and from local buffers. For these tables we don't need complex metadata.
It's crazy idea, and I think so union with table function should be best.

Regards

Pavel

Show quoted text

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

#22Julien Rouhaud
rjuju123@gmail.com
In reply to: Pavel Stehule (#21)
Re: [Proposal] Global temporary tables

On Sat, Nov 2, 2019 at 6:31 AM Pavel Stehule <pavel.stehule@gmail.com> wrote:

pá 1. 11. 2019 v 17:09 odesílatel Konstantin Knizhnik <k.knizhnik@postgrespro.ru> napsal:

On 01.11.2019 18:26, Robert Haas wrote:

On Fri, Nov 1, 2019 at 11:15 AM Konstantin Knizhnik
<k.knizhnik@postgrespro.ru> wrote:

It seems to me that I have found quite elegant solution for per-backend statistic for GTT: I just inserting it in backend's catalog cache, but not in pg_statistic table itself.
To do it I have to add InsertSysCache/InsertCatCache functions which insert pinned entry in the correspondent cache.
I wonder if there are some pitfalls of such approach?

That sounds pretty hackish. You'd have to be very careful, for
example, that if the tables were dropped or re-analyzed, all of the
old entries got removed --

I have checked it:
- when table is reanalyzed, then cache entries are replaced.
- when table is dropped, then cache entries are removed.

and then it would still fail if any code
tried to access the statistics directly from the table, rather than
via the caches. My assumption is that the statistics ought to be
stored in some backend-private data structure designed for that
purpose, and that the code that needs the data should be taught to
look for it there when the table is a GTT.

Yes, if you do "select * from pg_statistic" then you will not see
statistic for GTT in this case.
But I do not think that it is so critical. I do not believe that anybody
is trying to manually interpret values in this table.
And optimizer is retrieving statistic through sys-cache mechanism and so
is able to build correct plan in this case.

Years ago, when I though about it, I wrote patch with similar design. It's working, but surely it's ugly.

I have another idea. Can be pg_statistics view instead a table?

Some like

SELECT * FROM pg_catalog.pg_statistics_rel
UNION ALL
SELECT * FROM pg_catalog.pg_statistics_gtt();

Internally - when stat cache is filled, then there can be used pg_statistics_rel and pg_statistics_gtt() directly. What I remember, there was not possibility to work with queries, only with just relations.

It'd be a loss if you lose the ability to see the statistics, as there
are valid use cases where you need to see the stats, eg. understanding
why you don't get the plan you wanted. There's also at least one
extension [1]https://github.com/ossc-db/pg_dbms_stats that allows you to backup and use restored statistics,
so there are definitely people interested in it.

[1]: https://github.com/ossc-db/pg_dbms_stats

#23Pavel Stehule
pavel.stehule@gmail.com
In reply to: Julien Rouhaud (#22)
Re: [Proposal] Global temporary tables

so 2. 11. 2019 v 8:18 odesílatel Julien Rouhaud <rjuju123@gmail.com> napsal:

On Sat, Nov 2, 2019 at 6:31 AM Pavel Stehule <pavel.stehule@gmail.com>
wrote:

pá 1. 11. 2019 v 17:09 odesílatel Konstantin Knizhnik <

k.knizhnik@postgrespro.ru> napsal:

On 01.11.2019 18:26, Robert Haas wrote:

On Fri, Nov 1, 2019 at 11:15 AM Konstantin Knizhnik
<k.knizhnik@postgrespro.ru> wrote:

It seems to me that I have found quite elegant solution for

per-backend statistic for GTT: I just inserting it in backend's catalog
cache, but not in pg_statistic table itself.

To do it I have to add InsertSysCache/InsertCatCache functions which

insert pinned entry in the correspondent cache.

I wonder if there are some pitfalls of such approach?

That sounds pretty hackish. You'd have to be very careful, for
example, that if the tables were dropped or re-analyzed, all of the
old entries got removed --

I have checked it:
- when table is reanalyzed, then cache entries are replaced.
- when table is dropped, then cache entries are removed.

and then it would still fail if any code
tried to access the statistics directly from the table, rather than
via the caches. My assumption is that the statistics ought to be
stored in some backend-private data structure designed for that
purpose, and that the code that needs the data should be taught to
look for it there when the table is a GTT.

Yes, if you do "select * from pg_statistic" then you will not see
statistic for GTT in this case.
But I do not think that it is so critical. I do not believe that anybody
is trying to manually interpret values in this table.
And optimizer is retrieving statistic through sys-cache mechanism and so
is able to build correct plan in this case.

Years ago, when I though about it, I wrote patch with similar design.

It's working, but surely it's ugly.

I have another idea. Can be pg_statistics view instead a table?

Some like

SELECT * FROM pg_catalog.pg_statistics_rel
UNION ALL
SELECT * FROM pg_catalog.pg_statistics_gtt();

Internally - when stat cache is filled, then there can be used

pg_statistics_rel and pg_statistics_gtt() directly. What I remember, there
was not possibility to work with queries, only with just relations.

It'd be a loss if you lose the ability to see the statistics, as there
are valid use cases where you need to see the stats, eg. understanding
why you don't get the plan you wanted. There's also at least one
extension [1] that allows you to backup and use restored statistics,
so there are definitely people interested in it.

[1]: https://github.com/ossc-db/pg_dbms_stats

I don't think - the extensions can use UNION and the content will be same
as caches used by planner.

#24Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#23)
Re: [Proposal] Global temporary tables

so 2. 11. 2019 v 8:23 odesílatel Pavel Stehule <pavel.stehule@gmail.com>
napsal:

so 2. 11. 2019 v 8:18 odesílatel Julien Rouhaud <rjuju123@gmail.com>
napsal:

On Sat, Nov 2, 2019 at 6:31 AM Pavel Stehule <pavel.stehule@gmail.com>
wrote:

pá 1. 11. 2019 v 17:09 odesílatel Konstantin Knizhnik <

k.knizhnik@postgrespro.ru> napsal:

On 01.11.2019 18:26, Robert Haas wrote:

On Fri, Nov 1, 2019 at 11:15 AM Konstantin Knizhnik
<k.knizhnik@postgrespro.ru> wrote:

It seems to me that I have found quite elegant solution for

per-backend statistic for GTT: I just inserting it in backend's catalog
cache, but not in pg_statistic table itself.

To do it I have to add InsertSysCache/InsertCatCache functions

which insert pinned entry in the correspondent cache.

I wonder if there are some pitfalls of such approach?

That sounds pretty hackish. You'd have to be very careful, for
example, that if the tables were dropped or re-analyzed, all of the
old entries got removed --

I have checked it:
- when table is reanalyzed, then cache entries are replaced.
- when table is dropped, then cache entries are removed.

and then it would still fail if any code
tried to access the statistics directly from the table, rather than
via the caches. My assumption is that the statistics ought to be
stored in some backend-private data structure designed for that
purpose, and that the code that needs the data should be taught to
look for it there when the table is a GTT.

Yes, if you do "select * from pg_statistic" then you will not see
statistic for GTT in this case.
But I do not think that it is so critical. I do not believe that

anybody

is trying to manually interpret values in this table.
And optimizer is retrieving statistic through sys-cache mechanism and

so

is able to build correct plan in this case.

Years ago, when I though about it, I wrote patch with similar design.

It's working, but surely it's ugly.

I have another idea. Can be pg_statistics view instead a table?

Some like

SELECT * FROM pg_catalog.pg_statistics_rel
UNION ALL
SELECT * FROM pg_catalog.pg_statistics_gtt();

Internally - when stat cache is filled, then there can be used

pg_statistics_rel and pg_statistics_gtt() directly. What I remember, there
was not possibility to work with queries, only with just relations.

It'd be a loss if you lose the ability to see the statistics, as there
are valid use cases where you need to see the stats, eg. understanding
why you don't get the plan you wanted. There's also at least one
extension [1] that allows you to backup and use restored statistics,
so there are definitely people interested in it.

[1]: https://github.com/ossc-db/pg_dbms_stats

I don't think - the extensions can use UNION and the content will be same
as caches used by planner.

sure, if some one try to modify directly system tables, then it should be
fixed.

#25Julien Rouhaud
rjuju123@gmail.com
In reply to: Pavel Stehule (#23)
Re: [Proposal] Global temporary tables

On Sat, Nov 2, 2019 at 8:23 AM Pavel Stehule <pavel.stehule@gmail.com> wrote:

so 2. 11. 2019 v 8:18 odesílatel Julien Rouhaud <rjuju123@gmail.com> napsal:

On Sat, Nov 2, 2019 at 6:31 AM Pavel Stehule <pavel.stehule@gmail.com> wrote:

pá 1. 11. 2019 v 17:09 odesílatel Konstantin Knizhnik <k.knizhnik@postgrespro.ru> napsal:

On 01.11.2019 18:26, Robert Haas wrote:

On Fri, Nov 1, 2019 at 11:15 AM Konstantin Knizhnik
<k.knizhnik@postgrespro.ru> wrote:

It seems to me that I have found quite elegant solution for per-backend statistic for GTT: I just inserting it in backend's catalog cache, but not in pg_statistic table itself.
To do it I have to add InsertSysCache/InsertCatCache functions which insert pinned entry in the correspondent cache.
I wonder if there are some pitfalls of such approach?

That sounds pretty hackish. You'd have to be very careful, for
example, that if the tables were dropped or re-analyzed, all of the
old entries got removed --

I have checked it:
- when table is reanalyzed, then cache entries are replaced.
- when table is dropped, then cache entries are removed.

and then it would still fail if any code
tried to access the statistics directly from the table, rather than
via the caches. My assumption is that the statistics ought to be
stored in some backend-private data structure designed for that
purpose, and that the code that needs the data should be taught to
look for it there when the table is a GTT.

Yes, if you do "select * from pg_statistic" then you will not see
statistic for GTT in this case.
But I do not think that it is so critical. I do not believe that anybody
is trying to manually interpret values in this table.
And optimizer is retrieving statistic through sys-cache mechanism and so
is able to build correct plan in this case.

Years ago, when I though about it, I wrote patch with similar design. It's working, but surely it's ugly.

I have another idea. Can be pg_statistics view instead a table?

Some like

SELECT * FROM pg_catalog.pg_statistics_rel
UNION ALL
SELECT * FROM pg_catalog.pg_statistics_gtt();

Internally - when stat cache is filled, then there can be used pg_statistics_rel and pg_statistics_gtt() directly. What I remember, there was not possibility to work with queries, only with just relations.

It'd be a loss if you lose the ability to see the statistics, as there
are valid use cases where you need to see the stats, eg. understanding
why you don't get the plan you wanted. There's also at least one
extension [1] that allows you to backup and use restored statistics,
so there are definitely people interested in it.

[1]: https://github.com/ossc-db/pg_dbms_stats

I don't think - the extensions can use UNION and the content will be same as caches used by planner.

Yes, I agree that changing pg_statistics to be a view as you showed
would fix the problem. I was answering Konstantin's point:

But I do not think that it is so critical. I do not believe that anybody
is trying to manually interpret values in this table.
And optimizer is retrieving statistic through sys-cache mechanism and so
is able to build correct plan in this case.

which is IMHO a wrong assumption.

#26Konstantin Knizhnik
k.knizhnik@postgrespro.ru
In reply to: Julien Rouhaud (#22)
Re: [Proposal] Global temporary tables

On 02.11.2019 10:19, Julien Rouhaud wrote:

On Sat, Nov 2, 2019 at 6:31 AM Pavel Stehule <pavel.stehule@gmail.com> wrote:

pá 1. 11. 2019 v 17:09 odesílatel Konstantin Knizhnik <k.knizhnik@postgrespro.ru> napsal:

On 01.11.2019 18:26, Robert Haas wrote:

On Fri, Nov 1, 2019 at 11:15 AM Konstantin Knizhnik
<k.knizhnik@postgrespro.ru> wrote:

It seems to me that I have found quite elegant solution for per-backend statistic for GTT: I just inserting it in backend's catalog cache, but not in pg_statistic table itself.
To do it I have to add InsertSysCache/InsertCatCache functions which insert pinned entry in the correspondent cache.
I wonder if there are some pitfalls of such approach?

That sounds pretty hackish. You'd have to be very careful, for
example, that if the tables were dropped or re-analyzed, all of the
old entries got removed --

I have checked it:
- when table is reanalyzed, then cache entries are replaced.
- when table is dropped, then cache entries are removed.

and then it would still fail if any code
tried to access the statistics directly from the table, rather than
via the caches. My assumption is that the statistics ought to be
stored in some backend-private data structure designed for that
purpose, and that the code that needs the data should be taught to
look for it there when the table is a GTT.

Yes, if you do "select * from pg_statistic" then you will not see
statistic for GTT in this case.
But I do not think that it is so critical. I do not believe that anybody
is trying to manually interpret values in this table.
And optimizer is retrieving statistic through sys-cache mechanism and so
is able to build correct plan in this case.

Years ago, when I though about it, I wrote patch with similar design. It's working, but surely it's ugly.

I have another idea. Can be pg_statistics view instead a table?

Some like

SELECT * FROM pg_catalog.pg_statistics_rel
UNION ALL
SELECT * FROM pg_catalog.pg_statistics_gtt();

Internally - when stat cache is filled, then there can be used pg_statistics_rel and pg_statistics_gtt() directly. What I remember, there was not possibility to work with queries, only with just relations.

It'd be a loss if you lose the ability to see the statistics, as there
are valid use cases where you need to see the stats, eg. understanding
why you don't get the plan you wanted. There's also at least one
extension [1] that allows you to backup and use restored statistics,
so there are definitely people interested in it.

[1]: https://github.com/ossc-db/pg_dbms_stats

It seems to have completely no sense to backup and restore statistic for
temporary tables which life time is limited to life time of backend,
doesn't it?

#27Konstantin Knizhnik
k.knizhnik@postgrespro.ru
In reply to: Pavel Stehule (#21)
Re: [Proposal] Global temporary tables

On 02.11.2019 8:30, Pavel Stehule wrote:

pá 1. 11. 2019 v 17:09 odesílatel Konstantin Knizhnik
<k.knizhnik@postgrespro.ru <mailto:k.knizhnik@postgrespro.ru>> napsal:

On 01.11.2019 18:26, Robert Haas wrote:

On Fri, Nov 1, 2019 at 11:15 AM Konstantin Knizhnik
<k.knizhnik@postgrespro.ru <mailto:k.knizhnik@postgrespro.ru>>

wrote:

It seems to me that I have found quite elegant solution for

per-backend statistic for GTT: I just inserting it in backend's
catalog cache, but not in pg_statistic table itself.

To do it I have to add InsertSysCache/InsertCatCache functions

which insert pinned entry in the correspondent cache.

I wonder if there are some pitfalls of such approach?

That sounds pretty hackish. You'd have to be very careful, for
example, that if the tables were dropped or re-analyzed, all of the
old entries got removed --

I have checked it:
- when table is reanalyzed, then cache entries are replaced.
- when table is dropped, then cache entries are removed.

and then it would still fail if any code
tried to access the statistics directly from the table, rather than
via the caches. My assumption is that the statistics ought to be
stored in some backend-private data structure designed for that
purpose, and that the code that needs the data should be taught to
look for it there when the table is a GTT.

Yes, if you do "select * from pg_statistic" then you will not see
statistic for GTT in this case.
But I do not think that it is so critical. I do not believe that
anybody
is trying to manually interpret values in this table.
And optimizer is retrieving statistic through sys-cache mechanism
and so
is able to build correct plan in this case.

Years ago, when I though about it, I wrote patch with similar design.
It's working, but surely it's ugly.

I have another idea. Can be pg_statistics view instead a table?

Some like

SELECT * FROM pg_catalog.pg_statistics_rel
UNION ALL
SELECT * FROM pg_catalog.pg_statistics_gtt();

And pg_catalog.pg_statistics_gtt() is set returning functions?
I afraid that it is not acceptable solution from performance point of
view: pg_statictic table is accessed by keys (<relid>,<attpos>,<inh>)
If it can not be done using index scan, then it can cause significant
performance slow down.

Internally - when stat cache is filled, then there can be used
pg_statistics_rel and pg_statistics_gtt() directly. What I remember,
there was not possibility to work with queries, only with just relations.

Or crazy idea - today we can implement own types of heaps. Is possible
to create engine where result can be combination of some shared data
and local data. So union will be implemented on heap level.
This implementation can be simple, just scanning pages from shared
buffers and from local buffers. For these tables we don't need complex
metadata. It's crazy idea, and I think so union with table function
should be best.

Frankly speaking, implementing special heap access method for
pg_statistic just to handle case of global temp tables seems to be overkill
from my point of view. It requires a lot coding (or at least copying a
lot of code from heapam). Also, as I wrote above, we need also index for
efficient lookup of statistic.

#28Julien Rouhaud
rjuju123@gmail.com
In reply to: Konstantin Knizhnik (#26)
Re: [Proposal] Global temporary tables

On Sat, Nov 2, 2019 at 4:09 PM Konstantin Knizhnik
<k.knizhnik@postgrespro.ru> wrote:

On 02.11.2019 10:19, Julien Rouhaud wrote:

On Sat, Nov 2, 2019 at 6:31 AM Pavel Stehule <pavel.stehule@gmail.com> wrote:

pá 1. 11. 2019 v 17:09 odesílatel Konstantin Knizhnik <k.knizhnik@postgrespro.ru> napsal:

On 01.11.2019 18:26, Robert Haas wrote:

On Fri, Nov 1, 2019 at 11:15 AM Konstantin Knizhnik
<k.knizhnik@postgrespro.ru> wrote:

It seems to me that I have found quite elegant solution for per-backend statistic for GTT: I just inserting it in backend's catalog cache, but not in pg_statistic table itself.
To do it I have to add InsertSysCache/InsertCatCache functions which insert pinned entry in the correspondent cache.
I wonder if there are some pitfalls of such approach?

That sounds pretty hackish. You'd have to be very careful, for
example, that if the tables were dropped or re-analyzed, all of the
old entries got removed --

I have checked it:
- when table is reanalyzed, then cache entries are replaced.
- when table is dropped, then cache entries are removed.

and then it would still fail if any code
tried to access the statistics directly from the table, rather than
via the caches. My assumption is that the statistics ought to be
stored in some backend-private data structure designed for that
purpose, and that the code that needs the data should be taught to
look for it there when the table is a GTT.

Yes, if you do "select * from pg_statistic" then you will not see
statistic for GTT in this case.
But I do not think that it is so critical. I do not believe that anybody
is trying to manually interpret values in this table.
And optimizer is retrieving statistic through sys-cache mechanism and so
is able to build correct plan in this case.

Years ago, when I though about it, I wrote patch with similar design. It's working, but surely it's ugly.

I have another idea. Can be pg_statistics view instead a table?

Some like

SELECT * FROM pg_catalog.pg_statistics_rel
UNION ALL
SELECT * FROM pg_catalog.pg_statistics_gtt();

Internally - when stat cache is filled, then there can be used pg_statistics_rel and pg_statistics_gtt() directly. What I remember, there was not possibility to work with queries, only with just relations.

It'd be a loss if you lose the ability to see the statistics, as there
are valid use cases where you need to see the stats, eg. understanding
why you don't get the plan you wanted. There's also at least one
extension [1] that allows you to backup and use restored statistics,
so there are definitely people interested in it.

[1]: https://github.com/ossc-db/pg_dbms_stats

It seems to have completely no sense to backup and restore statistic for
temporary tables which life time is limited to life time of backend,
doesn't it?

In general yes I agree, but it doesn't if the goal is to understand
why even after an analyze on the temporary table your query is still
behaving poorly. It can be useful to allow reproduction or just give
someone else the statistics to see what's going on.

#29Pavel Stehule
pavel.stehule@gmail.com
In reply to: Konstantin Knizhnik (#27)
Re: [Proposal] Global temporary tables

And pg_catalog.pg_statistics_gtt() is set returning functions?

yes

I afraid that it is not acceptable solution from performance point of view:

pg_statictic table is accessed by keys (<relid>,<attpos>,<inh>)

I don't think so it is problem. The any component, that needs to use fast
access can use some special function that check index or check some memory
buffers.

If it can not be done using index scan, then it can cause significant

performance slow down.

where you need fast access when you use SQL access? Inside postgres
optimizer is caches everywhere. And statistics cache should to know so have
to check index and some memory buffers.

The proposed view will not be used by optimizer, but it can be used by some
higher layers. I think so there is a agreement so GTT metadata should not
be stored in system catalogue. If are stored in some syscache or somewhere
else is not important in this moment. But can be nice if for user the GTT
metadata should not be black hole. I think so is better to change some
current tables to views, than use some special function just specialized
for GTT (these functions should to exists in both variants). When I think
about it - this is important not just for functionality that we expect from
GTT. It is important for consistency of Postgres catalog - how much
different should be GTT than other types of tables in system catalogue from
user's perspective.

#30曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Robert Haas (#16)
1 attachment(s)
Re: [Proposal] Global temporary tables

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

Attachments:

global_temporary_table_v1.patchapplication/octet-stream; name=global_temporary_table_v1.patch; x-unix-mode=0644Download
From fbe31fe0202bd3563235963b00050193bb32b618 Mon Sep 17 00:00:00 2001
From: wenjing.zwj <wenjing.zwj@alibaba-inc.com>
Date: Tue, 25 Jun 2019 17:44:26 +0800
Subject: [PATCH] support global temp table

---
 contrib/Makefile                             |   3 ++-
 contrib/pg_gtt/Makefile                      |  22 ++++++++++++++++++++++
 contrib/pg_gtt/pg_gtt--1.0.sql               |  28 ++++++++++++++++++++++++++++
 contrib/pg_gtt/pg_gtt.c                      | 474 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 contrib/pg_gtt/pg_gtt.control                |   4 ++++
 src/backend/access/common/reloptions.c       |  17 +++++++++++++++++
 src/backend/access/gist/gistutil.c           |   4 +++-
 src/backend/access/hash/hash.c               |   4 +++-
 src/backend/access/heap/heapam_handler.c     |   4 ++--
 src/backend/access/heap/vacuumlazy.c         |  22 ++++++++++++++++++----
 src/backend/access/nbtree/nbtpage.c          |   9 ++++++++-
 src/backend/access/transam/xlog.c            |   4 ++++
 src/backend/catalog/Makefile                 |   2 ++
 src/backend/catalog/catalog.c                |   2 ++
 src/backend/catalog/heap.c                   |  86 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
 src/backend/catalog/index.c                  |  87 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
 src/backend/catalog/namespace.c              |   7 +++++++
 src/backend/catalog/storage.c                |  17 ++++++++++++++++-
 src/backend/catalog/storage_gtt.c            | 814 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/backend/commands/analyze.c               |  75 ++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
 src/backend/commands/cluster.c               |   6 ++++++
 src/backend/commands/indexcmds.c             |  10 ++++++++++
 src/backend/commands/lockcmds.c              |   3 ++-
 src/backend/commands/tablecmds.c             | 111 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
 src/backend/commands/vacuum.c                | 140 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------------------------
 src/backend/optimizer/path/allpaths.c        |   8 +++++++-
 src/backend/optimizer/plan/planner.c         |   2 ++
 src/backend/optimizer/util/plancat.c         |  33 ++++++++++++++++++++-------------
 src/backend/parser/analyze.c                 |   5 +++++
 src/backend/parser/gram.y                    |  20 ++++----------------
 src/backend/parser/parse_relation.c          |  47 +++++++++++++++++++++++++++++++++++++++++++++++
 src/backend/parser/parse_utilcmd.c           |   7 +++++++
 src/backend/postmaster/autovacuum.c          |   9 ++++++++-
 src/backend/storage/buffer/bufmgr.c          |  31 +++++++++++++++++++++++++++----
 src/backend/storage/ipc/ipci.c               |   4 ++++
 src/backend/storage/ipc/procarray.c          |  75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/backend/storage/lmgr/proc.c              |   2 ++
 src/backend/storage/smgr/md.c                |   6 ++++++
 src/backend/utils/adt/dbsize.c               |   4 ++++
 src/backend/utils/adt/selfuncs.c             |  93 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
 src/backend/utils/cache/lsyscache.c          |  14 ++++++++++++++
 src/backend/utils/cache/relcache.c           |  19 ++++++++++++++++++-
 src/backend/utils/misc/guc.c                 |  21 +++++++++++++++++++++
 src/bin/pg_dump/pg_dump.c                    |  11 +++++++++--
 src/include/catalog/pg_class.h               |   1 +
 src/include/catalog/storage.h                |   2 +-
 src/include/catalog/storage_gtt.h            |  39 +++++++++++++++++++++++++++++++++++++++
 src/include/parser/parse_relation.h          |   3 +++
 src/include/storage/lwlock.h                 |   1 +
 src/include/storage/proc.h                   |   2 ++
 src/include/storage/procarray.h              |   2 ++
 src/include/utils/guc.h                      |   4 ++++
 src/include/utils/rel.h                      |  15 ++++++++++++++-
 src/test/regress/expected/gtt_clean.out      |   7 +++++++
 src/test/regress/expected/gtt_error.out      | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/test/regress/expected/gtt_parallel_1.out |  84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/test/regress/expected/gtt_parallel_2.out | 142 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/test/regress/expected/gtt_prepare.out    |   7 +++++++
 src/test/regress/parallel_schedule           |   6 ++++++
 src/test/regress/sql/gtt_clean.sql           |   6 ++++++
 src/test/regress/sql/gtt_error.sql           | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/test/regress/sql/gtt_parallel_1.sql      |  42 ++++++++++++++++++++++++++++++++++++++++++
 src/test/regress/sql/gtt_parallel_2.sql      |  62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/test/regress/sql/gtt_prepare.sql         |  15 +++++++++++++++
 64 files changed, 2848 insertions(+), 170 deletions(-)
 create mode 100644 contrib/pg_gtt/Makefile
 create mode 100644 contrib/pg_gtt/pg_gtt--1.0.sql
 create mode 100644 contrib/pg_gtt/pg_gtt.c
 create mode 100644 contrib/pg_gtt/pg_gtt.control
 create mode 100644 src/backend/catalog/storage_gtt.c
 create mode 100644 src/include/catalog/storage_gtt.h
 create mode 100644 src/test/regress/expected/gtt_clean.out
 create mode 100644 src/test/regress/expected/gtt_error.out
 create mode 100644 src/test/regress/expected/gtt_parallel_1.out
 create mode 100644 src/test/regress/expected/gtt_parallel_2.out
 create mode 100644 src/test/regress/expected/gtt_prepare.out
 create mode 100644 src/test/regress/sql/gtt_clean.sql
 create mode 100644 src/test/regress/sql/gtt_error.sql
 create mode 100644 src/test/regress/sql/gtt_parallel_1.sql
 create mode 100644 src/test/regress/sql/gtt_parallel_2.sql
 create mode 100644 src/test/regress/sql/gtt_prepare.sql

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 2e1dcb9..3eb57df 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}}
 };
@@ -1378,7 +1391,11 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 	relopt_value *options;
 	StdRdOptions *rdopts;
 	int			numoptions;
+
+	/* add on_commit_delete_rows for global temp table */
 	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 f428729..6c9b052 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1028,7 +1028,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 fc19f40..681521a 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -602,7 +602,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
@@ -655,7 +655,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 6b2655c..dcb5acb 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -765,7 +765,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 77ad765..9ff98ff 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6319,6 +6319,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 8bece07..470683b 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -21,6 +21,8 @@ OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
 	   pg_db_role_setting.o pg_shdepend.o pg_subscription.o pg_type.o \
 	   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 a065419..db86aa6 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -406,7 +406,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 b8b4768..832a3f1 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,
@@ -406,6 +408,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.
 	 *
@@ -429,7 +435,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:
@@ -926,6 +932,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -964,8 +971,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;
@@ -1327,6 +1344,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1411,11 +1429,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
@@ -1907,6 +1929,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.
 	 */
@@ -3133,9 +3162,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;
 
@@ -3147,7 +3177,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);
@@ -3186,8 +3216,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);
 	}
 
@@ -3220,6 +3255,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
@@ -3228,23 +3265,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 4f54631..3026309 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
 	 */
@@ -2040,6 +2061,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.
@@ -2699,20 +2727,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;
+			}
 		}
 	}
 
@@ -2827,6 +2864,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
 	 */
@@ -3159,12 +3203,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;
 
 	/*
@@ -3422,6 +3476,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 8d0896c..4c04833 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 3cc886f..6295f81 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;
 }
 
@@ -455,8 +463,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..37a8a97
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,814 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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	*prev;
+
+	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 */
+	prev = list_head(gtt_session_relfrozenxid_list);
+	for (;;)
+	{
+		ListCell   *curr = lnext(prev);
+
+		if (curr == NULL ||
+			TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(curr)))
+			break;	/* it belongs after 'prev', before 'curr' */
+
+		prev = curr;
+	}
+	/* Insert datum into list after 'prev' */
+	lappend_cell_oid(gtt_session_relfrozenxid_list, prev, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index faabbeb..d79535d 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);
 
@@ -563,14 +564,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]));
 		}
 
 		/*
@@ -647,11 +649,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);
@@ -1414,7 +1425,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;
@@ -1516,31 +1527,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 ebaec4f..356df7c 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 a528241..66f4b10 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2584,6 +2584,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 417d595..b9c3a40 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 792ba56..7a42efd 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"
@@ -530,6 +531,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);
 
 
 /* ----------------------------------------------------------------
@@ -575,6 +577,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
@@ -585,8 +588,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")));
@@ -616,12 +621,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.
@@ -717,6 +738,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);
@@ -3332,6 +3388,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);
 
 	/*
@@ -3501,6 +3564,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);
@@ -7651,6 +7722,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),
@@ -7690,6 +7768,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;
 	}
 
 	/*
@@ -12702,7 +12786,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,
@@ -14109,7 +14193,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",
@@ -16716,3 +16802,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 ef275c0..c44fad8 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"
@@ -1068,12 +1069,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;
@@ -1173,24 +1187,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)
 	{
 		/*
@@ -1215,37 +1213,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. */
@@ -1337,6 +1362,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
@@ -1394,6 +1423,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 b772348..cf1c7e0 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 401299e..7d63265 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6306,7 +6306,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 40f4976..535cf80 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;
@@ -937,10 +938,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)
@@ -954,6 +955,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.
@@ -962,11 +978,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 */
@@ -976,10 +987,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 345a8e6..076a210 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 208b4a1..8cc52d9 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3267,17 +3267,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; }
@@ -11476,19 +11470,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 77a48b0..7f14d41 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);
 
 
 /*
@@ -3425,3 +3426,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 = heap_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				heap_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 2406ca7..ab6fc9a 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 fd85b9c..eb46e94 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2088,6 +2088,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);
@@ -2154,7 +2159,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 7332e6b..f023aa3 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);
@@ -663,7 +665,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;
@@ -691,7 +694,7 @@ ReadBufferWithoutRelcache(RelFileNode rnode, ForkNumber forkNum,
 	Assert(InRecovery);
 
 	return ReadBuffer_common(smgr, RELPERSISTENCE_PERMANENT, forkNum, blockNum,
-							 mode, strategy, &hit);
+							 mode, strategy, &hit, NULL);
 }
 
 
@@ -703,7 +706,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;
@@ -718,6 +722,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,
@@ -2798,6 +2811,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(relation->rd_node.relNode))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index d7d7335..3b2cc8b 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(int port)
 		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(int port)
 	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 18a0f62..0d926bd 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))))
 
@@ -3975,3 +3976,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 498373f..542cc6b 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 64acc3f..fdbc95b 100644
--- a/src/backend/storage/smgr/md.c
+++ b/src/backend/storage/smgr/md.c
@@ -629,6 +629,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 a314ecc..c957d12 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;
@@ -4586,12 +4587,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))
 							{
@@ -4670,15 +4684,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))
 		{
@@ -5991,6 +6017,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;
@@ -6008,6 +6035,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,
@@ -6019,6 +6053,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;
@@ -6034,6 +6070,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,
@@ -6900,6 +6943,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))
@@ -6912,6 +6957,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 =
@@ -6924,6 +6977,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.
@@ -6943,6 +6998,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 e8d11a1..0c7b455 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1131,6 +1131,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);
@@ -3312,6 +3322,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;
@@ -3426,6 +3440,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);
@@ -3466,7 +3483,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 f0ed326..d4590a2 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 a9c868b..3831341 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15535,6 +15535,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	{
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
+		char		*table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15586,9 +15587,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 08e0dc8..297e20c 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 ac7ee72..74a074a 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 a93ed77..a782eee 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 1385ff3..a5a991d 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -273,6 +273,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
@@ -524,7 +525,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
@@ -603,6 +605,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 8fb55f0..588f4e5 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -120,3 +120,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_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..f3cf710
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,6 @@
+
+
+reset search_path;
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_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;
+
--
libgit2 0.23.3

#31Konstantin Knizhnik
k.knizhnik@postgrespro.ru
In reply to: 曾文旌(义从) (#30)
1 attachment(s)
Re: [Proposal] Global temporary tables

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

Attachments:

global_temporary_table_v1-pg13.patchtext/x-patch; name=global_temporary_table_v1-pg13.patchDownload
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;
+
#32曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Konstantin Knizhnik (#31)
Re: [Proposal] Global temporary tables

2019年11月7日 上午12:08,Konstantin Knizhnik <k.knizhnik@postgrespro.ru> 写道:

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.

Currently I only support btree index.
I noticed that your patch supports more index types, which is where I'd like to work with you.

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.

Yes, Currently, GTT's index is only supported and created in an empty table state, and other sessions are not using it.
There has two improvements pointer:
1 Index can create on GTT(A) when the GTT(A) in the current session is not empty, requiring the GTT table to be empty in the other session.
Index_build needs to be done in the current session just like a normal table. This improvement is relatively easy.

2 Index can create on GTT(A) when more than one session are using this GTT(A).
Because when I'm done creating an index of the GTT in this session and setting it to be an valid index, it's not true for the GTT in other sessions.
Indexes on gtt in other sessions require "rebuild_index" before using it.
I don't have a better solution right now, maybe you have some suggestions.

Show quoted text

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

<global_temporary_table_v1-pg13.patch>

#33Pavel Stehule
pavel.stehule@gmail.com
In reply to: 曾文旌(义从) (#32)
Re: [Proposal] Global temporary tables

čt 7. 11. 2019 v 10:30 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
napsal:

2019年11月7日 上午12:08,Konstantin Knizhnik <k.knizhnik@postgrespro.ru> 写道:

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.
Currently I only support btree index.
I noticed that your patch supports more index types, which is where I'd
like to work with you.

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.
Yes, Currently, GTT's index is only supported and created in an empty
table state, and other sessions are not using it.
There has two improvements pointer:
1 Index can create on GTT(A) when the GTT(A) in the current session is
not empty, requiring the GTT table to be empty in the other session.
Index_build needs to be done in the current session just like a normal
table. This improvement is relatively easy.

2 Index can create on GTT(A) when more than one session are using this
GTT(A).
Because when I'm done creating an index of the GTT in this session and
setting it to be an valid index, it's not true for the GTT in other
sessions.
Indexes on gtt in other sessions require "rebuild_index" before using it.
I don't have a better solution right now, maybe you have some suggestions.

I think so DDL operations can be implemented in some reduced form - so DDL
are active only for one session, and for other sessions are invisible.
Important is state of GTT object on session start.

For example ALTER TABLE DROP COLUMN can has very fatal impact on other
sessions. So I think the best of GTT can be pattern - the structure of GTT
table is immutable for any session that doesn't do DDL operations.

Show quoted text

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

<global_temporary_table_v1-pg13.patch>

#34曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: 曾文旌(义从) (#32)
1 attachment(s)
Re: [Proposal] Global temporary tables

2019年11月7日 下午5:30,曾文旌(义从) <wenjing.zwj@alibaba-inc.com> 写道:

2019年11月7日 上午12:08,Konstantin Knizhnik <k.knizhnik@postgrespro.ru <mailto:k.knizhnik@postgrespro.ru>> 写道:

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.

Currently I only support btree index.
I noticed that your patch supports more index types, which is where I'd like to work with you.

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.

Yes, Currently, GTT's index is only supported and created in an empty table state, and other sessions are not using it.
There has two improvements pointer:
1 Index can create on GTT(A) when the GTT(A) in the current session is not empty, requiring the GTT table to be empty in the other session.
Index_build needs to be done in the current session just like a normal table. This improvement is relatively easy.

This part of the improvement has been completed.
New patch is attached.

2 Index can create on GTT(A) when more than one session are using this GTT(A).
Because when I'm done creating an index of the GTT in this session and setting it to be an valid index, it's not true for the GTT in other sessions.
Indexes on gtt in other sessions require "rebuild_index" before using it.
I don't have a better solution right now, maybe you have some suggestions.

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

Index can create on GTT(A) when the GTT(A) in the current session is not empty now.
But still requiring the GTT table to be empty in the other session.

I wonder why do you need such restriction?

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

<global_temporary_table_v1-pg13.patch>

Zeng Wenjing

Attachments:

global_temporary_table_v2.patchapplication/octet-stream; name=global_temporary_table_v2.patch; x-unix-mode=0644Download
From 94c807315d12a8c30a1654947d289775a6b7682b Mon Sep 17 00:00:00 2001
From: wenjing.zwj <wenjing.zwj@alibaba-inc.com>
Date: Tue, 25 Jun 2019 17:44:26 +0800
Subject: [PATCH] support global temp table

---
 contrib/Makefile                             |   3 ++-
 contrib/pg_gtt/Makefile                      |  22 ++++++++++++++++++++++
 contrib/pg_gtt/pg_gtt--1.0.sql               |  28 ++++++++++++++++++++++++++++
 contrib/pg_gtt/pg_gtt.c                      | 474 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 contrib/pg_gtt/pg_gtt.control                |   4 ++++
 src/backend/access/common/reloptions.c       |  17 +++++++++++++++++
 src/backend/access/gist/gistutil.c           |   4 +++-
 src/backend/access/hash/hash.c               |   4 +++-
 src/backend/access/heap/heapam_handler.c     |   4 ++--
 src/backend/access/heap/vacuumlazy.c         |  22 ++++++++++++++++++----
 src/backend/access/nbtree/nbtpage.c          |   9 ++++++++-
 src/backend/access/transam/xlog.c            |   4 ++++
 src/backend/catalog/Makefile                 |   2 ++
 src/backend/catalog/catalog.c                |   2 ++
 src/backend/catalog/heap.c                   |  86 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
 src/backend/catalog/index.c                  |  87 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
 src/backend/catalog/namespace.c              |   7 +++++++
 src/backend/catalog/storage.c                |  17 ++++++++++++++++-
 src/backend/catalog/storage_gtt.c            | 814 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/backend/commands/analyze.c               |  75 ++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
 src/backend/commands/cluster.c               |   6 ++++++
 src/backend/commands/indexcmds.c             |  10 ++++++++++
 src/backend/commands/lockcmds.c              |   3 ++-
 src/backend/commands/tablecmds.c             | 111 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
 src/backend/commands/vacuum.c                | 140 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------------------------
 src/backend/optimizer/path/allpaths.c        |   8 +++++++-
 src/backend/optimizer/plan/planner.c         |   2 ++
 src/backend/optimizer/util/plancat.c         |  33 ++++++++++++++++++++-------------
 src/backend/parser/analyze.c                 |   5 +++++
 src/backend/parser/gram.y                    |  20 ++++----------------
 src/backend/parser/parse_relation.c          |  47 +++++++++++++++++++++++++++++++++++++++++++++++
 src/backend/parser/parse_utilcmd.c           |   7 +++++++
 src/backend/postmaster/autovacuum.c          |   9 ++++++++-
 src/backend/storage/buffer/bufmgr.c          |  31 +++++++++++++++++++++++++++----
 src/backend/storage/ipc/ipci.c               |   4 ++++
 src/backend/storage/ipc/procarray.c          |  75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/backend/storage/lmgr/proc.c              |   2 ++
 src/backend/storage/smgr/md.c                |   6 ++++++
 src/backend/utils/adt/dbsize.c               |   4 ++++
 src/backend/utils/adt/selfuncs.c             |  93 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
 src/backend/utils/cache/lsyscache.c          |  14 ++++++++++++++
 src/backend/utils/cache/relcache.c           |  19 ++++++++++++++++++-
 src/backend/utils/misc/guc.c                 |  21 +++++++++++++++++++++
 src/bin/pg_dump/pg_dump.c                    |  11 +++++++++--
 src/include/catalog/pg_class.h               |   1 +
 src/include/catalog/storage.h                |   2 +-
 src/include/catalog/storage_gtt.h            |  39 +++++++++++++++++++++++++++++++++++++++
 src/include/parser/parse_relation.h          |   3 +++
 src/include/storage/lwlock.h                 |   1 +
 src/include/storage/proc.h                   |   2 ++
 src/include/storage/procarray.h              |   2 ++
 src/include/utils/guc.h                      |   4 ++++
 src/include/utils/rel.h                      |  15 ++++++++++++++-
 src/test/regress/expected/gtt_clean.out      |   7 +++++++
 src/test/regress/expected/gtt_error.out      | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/test/regress/expected/gtt_parallel_1.out |  84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/test/regress/expected/gtt_parallel_2.out | 142 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/test/regress/expected/gtt_prepare.out    |   7 +++++++
 src/test/regress/parallel_schedule           |   6 ++++++
 src/test/regress/sql/gtt_clean.sql           |   6 ++++++
 src/test/regress/sql/gtt_error.sql           | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/test/regress/sql/gtt_parallel_1.sql      |  42 ++++++++++++++++++++++++++++++++++++++++++
 src/test/regress/sql/gtt_parallel_2.sql      |  62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/test/regress/sql/gtt_prepare.sql         |  15 +++++++++++++++
 64 files changed, 2848 insertions(+), 170 deletions(-)
 create mode 100644 contrib/pg_gtt/Makefile
 create mode 100644 contrib/pg_gtt/pg_gtt--1.0.sql
 create mode 100644 contrib/pg_gtt/pg_gtt.c
 create mode 100644 contrib/pg_gtt/pg_gtt.control
 create mode 100644 src/backend/catalog/storage_gtt.c
 create mode 100644 src/include/catalog/storage_gtt.h
 create mode 100644 src/test/regress/expected/gtt_clean.out
 create mode 100644 src/test/regress/expected/gtt_error.out
 create mode 100644 src/test/regress/expected/gtt_parallel_1.out
 create mode 100644 src/test/regress/expected/gtt_parallel_2.out
 create mode 100644 src/test/regress/expected/gtt_prepare.out
 create mode 100644 src/test/regress/sql/gtt_clean.sql
 create mode 100644 src/test/regress/sql/gtt_error.sql
 create mode 100644 src/test/regress/sql/gtt_parallel_1.sql
 create mode 100644 src/test/regress/sql/gtt_parallel_2.sql
 create mode 100644 src/test/regress/sql/gtt_prepare.sql

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 2e1dcb9..3eb57df 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}}
 };
@@ -1378,7 +1391,11 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 	relopt_value *options;
 	StdRdOptions *rdopts;
 	int			numoptions;
+
+	/* add on_commit_delete_rows for global temp table */
 	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 f428729..6c9b052 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1028,7 +1028,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 fc19f40..681521a 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -602,7 +602,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
@@ -655,7 +655,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 6b2655c..dcb5acb 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -765,7 +765,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 77ad765..9ff98ff 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6319,6 +6319,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 8bece07..470683b 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -21,6 +21,8 @@ OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
 	   pg_db_role_setting.o pg_shdepend.o pg_subscription.o pg_type.o \
 	   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 a065419..db86aa6 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -406,7 +406,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 b8b4768..832a3f1 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,
@@ -406,6 +408,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.
 	 *
@@ -429,7 +435,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:
@@ -926,6 +932,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -964,8 +971,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;
@@ -1327,6 +1344,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1411,11 +1429,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
@@ -1907,6 +1929,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.
 	 */
@@ -3133,9 +3162,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;
 
@@ -3147,7 +3177,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);
@@ -3186,8 +3216,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);
 	}
 
@@ -3220,6 +3255,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
@@ -3228,23 +3265,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 4f54631..163128d 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))
+			elog(ERROR, "can not create index when have other 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 */
+		if (!gtt_storage_attached(heapRelation->rd_node.relNode))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -2040,6 +2061,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.
@@ -2699,20 +2727,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;
+			}
 		}
 	}
 
@@ -2827,6 +2864,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
 	 */
@@ -3159,12 +3203,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;
 
 	/*
@@ -3422,6 +3476,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 8d0896c..4c04833 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 3cc886f..6295f81 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;
 }
 
@@ -455,8 +463,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..37a8a97
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,814 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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	*prev;
+
+	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 */
+	prev = list_head(gtt_session_relfrozenxid_list);
+	for (;;)
+	{
+		ListCell   *curr = lnext(prev);
+
+		if (curr == NULL ||
+			TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(curr)))
+			break;	/* it belongs after 'prev', before 'curr' */
+
+		prev = curr;
+	}
+	/* Insert datum into list after 'prev' */
+	lappend_cell_oid(gtt_session_relfrozenxid_list, prev, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index faabbeb..d79535d 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);
 
@@ -563,14 +564,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]));
 		}
 
 		/*
@@ -647,11 +649,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);
@@ -1414,7 +1425,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;
@@ -1516,31 +1527,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 ebaec4f..356df7c 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 a528241..66f4b10 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2584,6 +2584,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 417d595..b9c3a40 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 792ba56..7a42efd 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"
@@ -530,6 +531,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);
 
 
 /* ----------------------------------------------------------------
@@ -575,6 +577,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
@@ -585,8 +588,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")));
@@ -616,12 +621,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.
@@ -717,6 +738,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);
@@ -3332,6 +3388,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);
 
 	/*
@@ -3501,6 +3564,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);
@@ -7651,6 +7722,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),
@@ -7690,6 +7768,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;
 	}
 
 	/*
@@ -12702,7 +12786,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,
@@ -14109,7 +14193,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",
@@ -16716,3 +16802,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 ef275c0..c44fad8 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"
@@ -1068,12 +1069,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;
@@ -1173,24 +1187,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)
 	{
 		/*
@@ -1215,37 +1213,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. */
@@ -1337,6 +1362,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
@@ -1394,6 +1423,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 b772348..cf1c7e0 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 401299e..7d63265 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6306,7 +6306,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 40f4976..535cf80 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;
@@ -937,10 +938,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)
@@ -954,6 +955,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.
@@ -962,11 +978,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 */
@@ -976,10 +987,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 345a8e6..076a210 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 208b4a1..8cc52d9 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3267,17 +3267,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; }
@@ -11476,19 +11470,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 77a48b0..7f14d41 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);
 
 
 /*
@@ -3425,3 +3426,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 = heap_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				heap_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 2406ca7..ab6fc9a 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 fd85b9c..eb46e94 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2088,6 +2088,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);
@@ -2154,7 +2159,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 7332e6b..f023aa3 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);
@@ -663,7 +665,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;
@@ -691,7 +694,7 @@ ReadBufferWithoutRelcache(RelFileNode rnode, ForkNumber forkNum,
 	Assert(InRecovery);
 
 	return ReadBuffer_common(smgr, RELPERSISTENCE_PERMANENT, forkNum, blockNum,
-							 mode, strategy, &hit);
+							 mode, strategy, &hit, NULL);
 }
 
 
@@ -703,7 +706,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;
@@ -718,6 +722,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,
@@ -2798,6 +2811,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(relation->rd_node.relNode))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index d7d7335..3b2cc8b 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(int port)
 		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(int port)
 	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 18a0f62..0d926bd 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))))
 
@@ -3975,3 +3976,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 498373f..542cc6b 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 64acc3f..fdbc95b 100644
--- a/src/backend/storage/smgr/md.c
+++ b/src/backend/storage/smgr/md.c
@@ -629,6 +629,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 a314ecc..c957d12 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;
@@ -4586,12 +4587,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))
 							{
@@ -4670,15 +4684,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))
 		{
@@ -5991,6 +6017,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;
@@ -6008,6 +6035,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,
@@ -6019,6 +6053,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;
@@ -6034,6 +6070,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,
@@ -6900,6 +6943,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))
@@ -6912,6 +6957,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 =
@@ -6924,6 +6977,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.
@@ -6943,6 +6998,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 e8d11a1..0c7b455 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1131,6 +1131,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);
@@ -3312,6 +3322,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;
@@ -3426,6 +3440,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);
@@ -3466,7 +3483,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 f0ed326..d4590a2 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 a9c868b..3831341 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15535,6 +15535,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	{
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
+		char		*table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15586,9 +15587,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 08e0dc8..297e20c 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 ac7ee72..74a074a 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 a93ed77..a782eee 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 1385ff3..a5a991d 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -273,6 +273,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
@@ -524,7 +525,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
@@ -603,6 +605,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 8fb55f0..588f4e5 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -120,3 +120,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_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..f3cf710
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,6 @@
+
+
+reset search_path;
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_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;
+
--
libgit2 0.23.3

#35曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Pavel Stehule (#33)
Re: [Proposal] Global temporary tables

2019年11月7日 下午5:40,Pavel Stehule <pavel.stehule@gmail.com> 写道:

čt 7. 11. 2019 v 10:30 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:

2019年11月7日 上午12:08,Konstantin Knizhnik <k.knizhnik@postgrespro.ru <mailto:k.knizhnik@postgrespro.ru>> 写道:

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.

Currently I only support btree index.
I noticed that your patch supports more index types, which is where I'd like to work with you.

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.

Yes, Currently, GTT's index is only supported and created in an empty table state, and other sessions are not using it.
There has two improvements pointer:
1 Index can create on GTT(A) when the GTT(A) in the current session is not empty, requiring the GTT table to be empty in the other session.
Index_build needs to be done in the current session just like a normal table. This improvement is relatively easy.

2 Index can create on GTT(A) when more than one session are using this GTT(A).
Because when I'm done creating an index of the GTT in this session and setting it to be an valid index, it's not true for the GTT in other sessions.
Indexes on gtt in other sessions require "rebuild_index" before using it.
I don't have a better solution right now, maybe you have some suggestions.

I think so DDL operations can be implemented in some reduced form - so DDL are active only for one session, and for other sessions are invisible. Important is state of GTT object on session start.

For example ALTER TABLE DROP COLUMN can has very fatal impact on other sessions. So I think the best of GTT can be pattern - the structure of GTT table is immutable for any session that doesn't do DDL operations.

Yes, Those ddl that need to rewrite data files will have this problem.
This is why I disabled alter GTT in the current version.
It can be improved, such as Alter GTT can also be allowed when only the current session is in use.
Users can also choose to kick off other sessions that are using gtt, then do alter GTT.
I provide a function(pg_gtt_attached_pid(relation, schema)) to query which session a GTT is being used by.

Show quoted text

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 <http://www.postgrespro.com/&gt;
The Russian Postgres Company

<global_temporary_table_v1-pg13.patch>

#36Pavel Stehule
pavel.stehule@gmail.com
In reply to: 曾文旌(义从) (#35)
Re: [Proposal] Global temporary tables

čt 7. 11. 2019 v 13:17 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
napsal:

2019年11月7日 下午5:40,Pavel Stehule <pavel.stehule@gmail.com> 写道:

čt 7. 11. 2019 v 10:30 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
napsal:

2019年11月7日 上午12:08,Konstantin Knizhnik <k.knizhnik@postgrespro.ru> 写道:

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.
Currently I only support btree index.
I noticed that your patch supports more index types, which is where I'd
like to work with you.

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.
Yes, Currently, GTT's index is only supported and created in an empty
table state, and other sessions are not using it.
There has two improvements pointer:
1 Index can create on GTT(A) when the GTT(A) in the current session is
not empty, requiring the GTT table to be empty in the other session.
Index_build needs to be done in the current session just like a normal
table. This improvement is relatively easy.

2 Index can create on GTT(A) when more than one session are using this
GTT(A).
Because when I'm done creating an index of the GTT in this session and
setting it to be an valid index, it's not true for the GTT in other
sessions.
Indexes on gtt in other sessions require "rebuild_index" before using it.
I don't have a better solution right now, maybe you have some suggestions.

I think so DDL operations can be implemented in some reduced form - so DDL
are active only for one session, and for other sessions are invisible.
Important is state of GTT object on session start.

For example ALTER TABLE DROP COLUMN can has very fatal impact on other
sessions. So I think the best of GTT can be pattern - the structure of GTT
table is immutable for any session that doesn't do DDL operations.

Yes, Those ddl that need to rewrite data files will have this problem.
This is why I disabled alter GTT in the current version.
It can be improved, such as Alter GTT can also be allowed when only the
current session is in use.

I think so it is acceptable solution for some first steps, but I cannot to
imagine so this behave can be good for production usage. But can be good
enough for some time.

Regards

Pavel

Users can also choose to kick off other sessions that are using gtt, then

Show quoted text

do alter GTT.
I provide a function(pg_gtt_attached_pid(relation, schema)) to query which
session a GTT is being used by.

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

<global_temporary_table_v1-pg13.patch>

#37Konstantin Knizhnik
k.knizhnik@postgrespro.ru
In reply to: 曾文旌(义从) (#32)
Re: [Proposal] Global temporary tables

On 07.11.2019 12:30, 曾文旌(义从) wrote:

May be the assumption is that all indexes has to be created before GTT start to be used.

Yes, Currently, GTT's index is only supported and created in an empty table state, and other sessions are not using it.
There has two improvements pointer:
1 Index can create on GTT(A) when the GTT(A) in the current session is not empty, requiring the GTT table to be empty in the other session.
Index_build needs to be done in the current session just like a normal table. This improvement is relatively easy.

2 Index can create on GTT(A) when more than one session are using this GTT(A).
Because when I'm done creating an index of the GTT in this session and setting it to be an valid index, it's not true for the GTT in other sessions.
Indexes on gtt in other sessions require "rebuild_index" before using it.
I don't have a better solution right now, maybe you have some suggestions.

It is possible to create index on demand:

Buffer
_bt_getbuf(Relation rel, BlockNumber blkno, int access)
{
    Buffer        buf;

    if (blkno != P_NEW)
    {
        /* Read an existing block of the relation */
        buf = ReadBuffer(rel, blkno);
        /* Session temporary relation may be not yet initialized for
this backend. */
        if (blkno == BTREE_METAPAGE &&
GlobalTempRelationPageIsNotInitialized(rel, BufferGetPage(buf)))
        {
            Relation heap = RelationIdGetRelation(rel->rd_index->indrelid);
            ReleaseBuffer(buf);
            DropRelFileNodeLocalBuffers(rel->rd_node, MAIN_FORKNUM, blkno);
            btbuild(heap, rel, BuildIndexInfo(rel));
            RelationClose(heap);
            buf = ReadBuffer(rel, blkno);
            LockBuffer(buf, access);
        }
        else
        {
            LockBuffer(buf, access);
            _bt_checkpage(rel, buf);
        }
    }
    ...

This code initializes B-Tree and load data in it when GTT index is
access and is not initialized yet.
It looks a little bit hacker but it works.

I also wonder why you are keeping information about GTT in shared
memory. Looks like the only information we really need to share is
table's metadata.
But it is already shared though catalog. All other GTT related
information is private to backend so I do not see reasons to place it in
shared memory.

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

#38曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Konstantin Knizhnik (#37)
1 attachment(s)
Re: [Proposal] Global temporary tables

2019年11月8日 上午12:32,Konstantin Knizhnik <k.knizhnik@postgrespro.ru> 写道:

On 07.11.2019 12:30, 曾文旌(义从) wrote:

May be the assumption is that all indexes has to be created before GTT start to be used.

Yes, Currently, GTT's index is only supported and created in an empty table state, and other sessions are not using it.
There has two improvements pointer:
1 Index can create on GTT(A) when the GTT(A) in the current session is not empty, requiring the GTT table to be empty in the other session.
Index_build needs to be done in the current session just like a normal table. This improvement is relatively easy.

2 Index can create on GTT(A) when more than one session are using this GTT(A).
Because when I'm done creating an index of the GTT in this session and setting it to be an valid index, it's not true for the GTT in other sessions.
Indexes on gtt in other sessions require "rebuild_index" before using it.
I don't have a better solution right now, maybe you have some suggestions.

It is possible to create index on demand:

Buffer
_bt_getbuf(Relation rel, BlockNumber blkno, int access)
{
Buffer buf;

if (blkno != P_NEW)
{
/* Read an existing block of the relation */
buf = ReadBuffer(rel, blkno);
/* Session temporary relation may be not yet initialized for this backend. */
if (blkno == BTREE_METAPAGE && GlobalTempRelationPageIsNotInitialized(rel, BufferGetPage(buf)))
{
Relation heap = RelationIdGetRelation(rel->rd_index->indrelid);
ReleaseBuffer(buf);
DropRelFileNodeLocalBuffers(rel->rd_node, MAIN_FORKNUM, blkno);
btbuild(heap, rel, BuildIndexInfo(rel));
RelationClose(heap);
buf = ReadBuffer(rel, blkno);
LockBuffer(buf, access);
}
else
{
LockBuffer(buf, access);
_bt_checkpage(rel, buf);
}
}
...

In my opinion, it is not a good idea to trigger a btbuild with a select or DML, the cost of which depends on the amount of data in the GTT.

This code initializes B-Tree and load data in it when GTT index is access and is not initialized yet.
It looks a little bit hacker but it works.

I also wonder why you are keeping information about GTT in shared memory. Looks like the only information we really need to share is table's metadata.
But it is already shared though catalog. All other GTT related information is private to backend so I do not see reasons to place it in shared memory.

The shared hash structure tracks which backend has initialized the GTT storage in order to implement the DDL of the GTT.
As for GTT, there is only one definition(include index on GTT), but each backend may have one data.
For the implementation of drop GTT, I assume that all data and definitions need to be deleted.

Show quoted text

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

Attachments:

global_temporary_table_v2-pg13.patchapplication/octet-stream; name=global_temporary_table_v2-pg13.patch; x-unix-mode=0644Download
From 979b6f9dda32d0f4735a6a2eb43076c3d822b1d4 Mon Sep 17 00:00:00 2001
From: 义从 <wenjing.zwj@alibaba-inc.com>
Date: Fri, 8 Nov 2019 11:35:02 +0800
Subject: [PATCH] global_temporary_table_v2-pg13

---
 contrib/Makefile                             |   3 ++-
 contrib/pg_gtt/Makefile                      |  22 ++++++++++++++++++++++
 contrib/pg_gtt/pg_gtt--1.0.sql               |  28 ++++++++++++++++++++++++++++
 contrib/pg_gtt/pg_gtt.c                      | 474 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 contrib/pg_gtt/pg_gtt.control                |   4 ++++
 src/backend/access/common/reloptions.c       |  15 +++++++++++++++
 src/backend/access/gist/gistutil.c           |   4 +++-
 src/backend/access/hash/hash.c               |   4 +++-
 src/backend/access/heap/heapam_handler.c     |   4 ++--
 src/backend/access/heap/vacuumlazy.c         |  22 ++++++++++++++++++----
 src/backend/access/nbtree/nbtpage.c          |   9 ++++++++-
 src/backend/access/transam/xlog.c            |   4 ++++
 src/backend/catalog/Makefile                 |   2 ++
 src/backend/catalog/catalog.c                |   2 ++
 src/backend/catalog/heap.c                   |  86 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
 src/backend/catalog/index.c                  |  87 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
 src/backend/catalog/namespace.c              |   7 +++++++
 src/backend/catalog/storage.c                |  17 ++++++++++++++++-
 src/backend/catalog/storage_gtt.c            | 813 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/backend/commands/analyze.c               |  75 ++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
 src/backend/commands/cluster.c               |   6 ++++++
 src/backend/commands/indexcmds.c             |  10 ++++++++++
 src/backend/commands/lockcmds.c              |   3 ++-
 src/backend/commands/tablecmds.c             | 111 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
 src/backend/commands/vacuum.c                | 140 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------------------------
 src/backend/optimizer/path/allpaths.c        |   8 +++++++-
 src/backend/optimizer/plan/planner.c         |   2 ++
 src/backend/optimizer/util/plancat.c         |  33 ++++++++++++++++++++-------------
 src/backend/parser/analyze.c                 |   5 +++++
 src/backend/parser/gram.y                    |  20 ++++----------------
 src/backend/parser/parse_relation.c          |  47 +++++++++++++++++++++++++++++++++++++++++++++++
 src/backend/parser/parse_utilcmd.c           |   7 +++++++
 src/backend/postmaster/autovacuum.c          |   9 ++++++++-
 src/backend/storage/buffer/bufmgr.c          |  31 +++++++++++++++++++++++++++----
 src/backend/storage/ipc/ipci.c               |   4 ++++
 src/backend/storage/ipc/procarray.c          |  75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/backend/storage/lmgr/proc.c              |   2 ++
 src/backend/storage/smgr/md.c                |   6 ++++++
 src/backend/utils/adt/dbsize.c               |   4 ++++
 src/backend/utils/adt/selfuncs.c             |  93 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
 src/backend/utils/cache/lsyscache.c          |  14 ++++++++++++++
 src/backend/utils/cache/relcache.c           |  19 ++++++++++++++++++-
 src/backend/utils/misc/guc.c                 |  21 +++++++++++++++++++++
 src/bin/pg_dump/pg_dump.c                    |  11 +++++++++--
 src/include/catalog/pg_class.h               |   1 +
 src/include/catalog/storage.h                |   2 +-
 src/include/catalog/storage_gtt.h            |  39 +++++++++++++++++++++++++++++++++++++++
 src/include/parser/parse_relation.h          |   3 +++
 src/include/storage/lwlock.h                 |   1 +
 src/include/storage/proc.h                   |   2 ++
 src/include/storage/procarray.h              |   2 ++
 src/include/utils/guc.h                      |   4 ++++
 src/include/utils/rel.h                      |  15 ++++++++++++++-
 src/test/regress/expected/gtt_clean.out      |   7 +++++++
 src/test/regress/expected/gtt_error.out      | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/test/regress/expected/gtt_parallel_1.out |  84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/test/regress/expected/gtt_parallel_2.out | 142 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/test/regress/expected/gtt_prepare.out    |   7 +++++++
 src/test/regress/parallel_schedule           |   6 ++++++
 src/test/regress/sql/gtt_clean.sql           |   6 ++++++
 src/test/regress/sql/gtt_error.sql           | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/test/regress/sql/gtt_parallel_1.sql      |  42 ++++++++++++++++++++++++++++++++++++++++++
 src/test/regress/sql/gtt_parallel_2.sql      |  62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/test/regress/sql/gtt_prepare.sql         |  15 +++++++++++++++
 64 files changed, 2845 insertions(+), 170 deletions(-)
 create mode 100644 contrib/pg_gtt/Makefile
 create mode 100644 contrib/pg_gtt/pg_gtt--1.0.sql
 create mode 100644 contrib/pg_gtt/pg_gtt.c
 create mode 100644 contrib/pg_gtt/pg_gtt.control
 create mode 100644 src/backend/catalog/storage_gtt.c
 create mode 100644 src/include/catalog/storage_gtt.h
 create mode 100644 src/test/regress/expected/gtt_clean.out
 create mode 100644 src/test/regress/expected/gtt_error.out
 create mode 100644 src/test/regress/expected/gtt_parallel_1.out
 create mode 100644 src/test/regress/expected/gtt_parallel_2.out
 create mode 100644 src/test/regress/expected/gtt_prepare.out
 create mode 100644 src/test/regress/sql/gtt_clean.sql
 create mode 100644 src/test/regress/sql/gtt_error.sql
 create mode 100644 src/test/regress/sql/gtt_parallel_1.sql
 create mode 100644 src/test/regress/sql/gtt_parallel_2.sql
 create mode 100644 src/test/regress/sql/gtt_prepare.sql

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..a9ed190 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))
+			elog(ERROR, "can not create index when have other 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 */
+		if (!gtt_storage_attached(heapRelation->rd_node.relNode))
+			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..0749afc
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,813 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
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 e84c8cc..3b921cd 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,
@@ -1964,6 +1976,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 9aa3d02..1493cf4 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -278,6 +278,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_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..f3cf710
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,6 @@
+
+
+reset search_path;
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_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;
+
--
libgit2 0.23.3

#39Konstantin Knizhnik
k.knizhnik@postgrespro.ru
In reply to: 曾文旌(义从) (#38)
Re: [Proposal] Global temporary tables

On 08.11.2019 10:50, 曾文旌(义从) wrote:

In my opinion, it is not a good idea to trigger a btbuild with a select or DML, the cost of which depends on the amount of data in the GTT.

IMHO it is better than returning error.
Also index will be used only if cost of plan with index will be
considered better than cost of plan without index. If you do not have
index, then you have to scan the whole table.
Time of such scan is comparable with time of building index.

Yes, I agree that indexes for GTT are used to be created together with
table itself before it is used by any application.
But if later DBA recognized that efficient execution of queries requires
some more indexes,
it will be strange and dangerous to prevent him from adding such index
until all clients which have accessed this table will drop their
connections.
Also maintaining in shared memory information about attached backends
seems to be overkill.

This code initializes B-Tree and load data in it when GTT index is access and is not initialized yet.
It looks a little bit hacker but it works.

I also wonder why you are keeping information about GTT in shared memory. Looks like the only information we really need to share is table's metadata.
But it is already shared though catalog. All other GTT related information is private to backend so I do not see reasons to place it in shared memory.

The shared hash structure tracks which backend has initialized the GTT storage in order to implement the DDL of the GTT.

Sorry, I do not understand this argument.
DDL is performed on shared metadata present in global catalog.
Standard postgres invalidation mechanism is used to notify all backends
about schema changes.
Why do we need to maintain some extra information in shared memory.
Can you give me example of DLL which does't work without such shared hash?

As for GTT, there is only one definition(include index on GTT), but each backend may have one data.
For the implementation of drop GTT, I assume that all data and definitions need to be deleted.

Data of dropped GTT is removed on normal backend termination or cleaned
up at server restart in case of abnormal shutdown (as it is done for
local temp tables).
I have not used any shared control structures for GTT in my
implementation and that is why I wonder why do you need it and what are
the expected problems with my
implementation?

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

#40曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Konstantin Knizhnik (#18)
Re: [Proposal] Global temporary tables

My comments for global_private_temp-4.patch

good side:
1 Lots of index type on GTT. I think we need support for all kinds of indexes.
2 serial column on GTT.
3 INHERITS GTT.
4 PARTITION GTT.

I didn't choose to support them in the first release, but you did.

Other side:
1 case: create global temp table gtt2(a int primary key, b text) on commit delete rows;
I think you've lost the meaning of the on commit delete rows clause.
After the GTT is created, the other sessions feel that this is an on commit PRESERVE rows GTT.

2 truncate gtt, mybe this is a bug in DropRelFileNodeBuffers.
GTT's local buffer is not released.
Case:
postgres=# insert into gtt2 values(1,'xx');
INSERT 0 1
postgres=# truncate gtt2;
TRUNCATE TABLE
postgres=# insert into gtt2 values(1,'xx');
ERROR: unexpected data beyond EOF in block 0 of relation base/13579/t3_16384
HINT: This has been seen to occur with buggy kernels; consider updating your system.

3 lock type of truncate GTT.
I don't think it's a good idea to hold a big lock with truncate GTT, because it only needs to process private data.

4 GTT's ddl Those ddl that need to rewrite data files may need attention.
We have discussed in the previous email. This is why I used shared hash to track the GTT file.

5 There will be problems with DDL that will change relfilenode. Such as cluster GTT ,vacuum full GTT.
A session completes vacuum full gtt(a), and other sessions will immediately start reading and writing new storage files and existing data is also lost.
I disable them in my current version.

6 drop GTT
I think drop GTT should clean up all storage files and definitions. How do you think?

7 MVCC visibility clog clean
GTT data visibility rules, like regular tables, so GTT also need clog.
We need to avoid the clog that GTT needs to be cleaned up.
At the same time, GTT does not do autovacuum, and retaining "too old data" will cause wraparound data loss.
I have given a solution in my design.

Zeng Wenjing

Show quoted text

2019年11月1日 下午11:15,Konstantin Knizhnik <k.knizhnik@postgrespro.ru> 写道:

On 25.10.2019 20:00, Pavel Stehule wrote:

So except the limitation mentioned above (which I do not consider as critical) there is only one problem which was not addressed: maintaining statistics for GTT.
If all of the following conditions are true:

1) GTT are used in joins
2) There are indexes defined for GTT
3) Size and histogram of GTT in different backends can significantly vary.
4) ANALYZE was explicitly called for GTT

then query execution plan built in one backend will be also used for other backends where it can be inefficient.
I also do not consider this problem as "show stopper" for adding GTT to Postgres.

I think that's *definitely* a show stopper.

Well, if both you and Pavel think that it is really "show stopper", then
this problem really has to be addressed.
I slightly confused about this opinion, because Pavel has told me
himself that 99% of users never create indexes for temp tables
or run "analyze" for them. And without it, this problem is not a problem
at all.

Users doesn't do ANALYZE on temp tables in 99%. It's true. But second fact is so users has lot of problems. It's very similar to wrong statistics on persistent tables. When data are small, then it is not problem for users, although from my perspective it's not optimal. When data are not small, then the problem can be brutal. Temporary tables are not a exception. And users and developers are people - we know only about fatal problems. There are lot of unoptimized queries, but because the problem is not fatal, then it is not reason for report it. And lot of people has not any idea how fast the databases can be. The knowledges of users and app developers are sad book.

Pavel

It seems to me that I have found quite elegant solution for per-backend statistic for GTT: I just inserting it in backend's catalog cache, but not in pg_statistic table itself.
To do it I have to add InsertSysCache/InsertCatCache functions which insert pinned entry in the correspondent cache.
I wonder if there are some pitfalls of such approach?

New patch for GTT is attached.
--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com <http://www.postgrespro.com/&gt;
The Russian Postgres Company
<global_private_temp-4.patch>

#41Konstantin Knizhnik
k.knizhnik@postgrespro.ru
In reply to: 曾文旌(义从) (#40)
Re: [Proposal] Global temporary tables

On 08.11.2019 18:06, 曾文旌(义从) wrote:

My comments for global_private_temp-4.patch

Thank you very much for inspecting my patch.

good side:
1 Lots of  index type on GTT. I think we need support for all kinds of
indexes.
2 serial column on GTT.
3 INHERITS GTT.
4 PARTITION GTT.

I didn't choose to support them in the first release, but you did.

Other side:
1 case: create global temp table gtt2(a int primary key, b text) on
commit delete rows;
I think you've lost the meaning of the on commit delete rows clause.
After the GTT is created, the other sessions feel that this is an on
commit PRESERVE rows GTT.

Yes, there was bug in my implementation of ON COMMIT DELETE ROWS for GTT.
It is fixed in global_private_temp-6.patch

2 truncate gtt, mybe this is a bug in DropRelFileNodeBuffers.
GTT's local buffer is not released.
Case:
postgres=# insert into gtt2 values(1,'xx');
INSERT 0 1
postgres=# truncate gtt2;
TRUNCATE TABLE
postgres=# insert into gtt2 values(1,'xx');
ERROR:  unexpected data beyond EOF in block 0 of relation
base/13579/t3_16384
HINT:  This has been seen to occur with buggy kernels; consider
updating your system.

Yes another bug, also fixed in new version of the patch.

3  lock type of truncate GTT.
I don't think it's a good idea to hold a big lock with truncate GTT,
because it only needs to process private data.

Sorry, I do not understand which lock you are talking about.
I have not introduced any special locks for GTT.

4 GTT's ddl Those ddl that need to rewrite data files may need attention.
We have discussed in the previous email. This is why I used shared
hash to track the GTT file.

You are right.
But instead of prohibiting ALTER TABLE at all for GTT, we can check
that there are no other backends using it.
I do not think that we should maintain some hash in shared memory to
check it.
As far as ALTER TABLE is rare and slow operation in any case, we can
just check presence of GTT files
created by other backends.
I have implemented this check in global_private_temp-6.patch

5 There will be problems with DDL that will change relfilenode. Such
as cluster GTT ,vacuum full GTT.
A session completes vacuum full gtt(a), and other sessions will
immediately start reading and writing new storage files and existing
data is also lost.
I disable them in my current version.

Thank you for noticing it.
Autovacuum full should really be prohibited for GTT.

6 drop GTT
I think drop GTT should clean up all storage files and definitions.
How do you think?

Storage files will be cleaned in any case on backend termination.
Certainly if backend creates  and deletes huge number of GTT in the
loop, it can cause space exhaustion.
But it seems to be very strange pattern of GTT usage.

7 MVCC visibility clog clean
GTT data visibility rules, like regular tables, so GTT also need clog.
We need to avoid the clog that GTT needs to be cleaned up.
At the same time, GTT does not do autovacuum, and retaining "too old
data" will cause wraparound data loss.
I have given a solution in my design.

But why do we need some special handling of visibility rules for GTT
comparing with normal (local) temp tables?
Them are also not proceeded by autovacuum?

In principle, I have also implemented special visibility rules for GTT,
but only for the case when them
are accessed at replica. And it is not included in this patch, because
everybody think that access to GTT
replica should be considered in separate patch.

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

#42Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Konstantin Knizhnik (#41)
Re: [Proposal] Global temporary tables

Hi,

I think we need to do something with having two patches aiming to add
global temporary tables:

[1]: https://commitfest.postgresql.org/26/2349/

[2]: https://commitfest.postgresql.org/26/2233/

As a reviewer I have no idea which of the threads to look at - certainly
not without reading both threads, which I doubt anyone will really do.
The reviews and discussions are somewhat intermixed between those two
threads, which makes it even more confusing.

I think we should agree on a minimal patch combining the necessary/good
bits from the various patches, and terminate one of the threads (i.e.
mark it as rejected or RWF). And we need to do that now, otherwise
there's about 0% chance of getting this into v13.

In general, I agree with the sentiment Rober expressed in [1]https://commitfest.postgresql.org/26/2349/ - the
patch needs to be as small as possible, not adding "nice to have"
features (like support for parallel queries - I very much doubt just
using shared instead of local buffers is enough to make it work.)

regards

--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#43曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Tomas Vondra (#42)
Re: [Proposal] Global temporary tables

In the previous communication

1 we agreed on the general direction
1.1 gtt use local (private) buffer
1.2 no replica access in first version

2 We feel that gtt needs to maintain statistics, but there is no agreement on what it will be done.

3 Still no one commented on GTT's transaction information processing, they include
3.1 Should gtt's frozenxid need to be care?
3.2 gtt’s clog clean
3.3 How to deal with "too old" gtt data

I suggest we discuss further, reach an agreement, and merge the two patches to one.

Wenjing

Show quoted text

2020年1月6日 上午4:06,Tomas Vondra <tomas.vondra@2ndquadrant.com> 写道:

Hi,

I think we need to do something with having two patches aiming to add
global temporary tables:

[1] https://commitfest.postgresql.org/26/2349/

[2] https://commitfest.postgresql.org/26/2233/

As a reviewer I have no idea which of the threads to look at - certainly
not without reading both threads, which I doubt anyone will really do.
The reviews and discussions are somewhat intermixed between those two
threads, which makes it even more confusing.

I think we should agree on a minimal patch combining the necessary/good
bits from the various patches, and terminate one of the threads (i.e.
mark it as rejected or RWF). And we need to do that now, otherwise
there's about 0% chance of getting this into v13.

In general, I agree with the sentiment Rober expressed in [1] - the
patch needs to be as small as possible, not adding "nice to have"
features (like support for parallel queries - I very much doubt just
using shared instead of local buffers is enough to make it work.)

regards

--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#44Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: 曾文旌(义从) (#43)
Re: [Proposal] Global temporary tables

On Mon, Jan 06, 2020 at 01:04:15PM +0800, 曾文旌(义从) wrote:

In the previous communication

1 we agreed on the general direction
1.1 gtt use local (private) buffer
1.2 no replica access in first version

OK, good.

2 We feel that gtt needs to maintain statistics, but there is no
agreement on what it will be done.

I certainly agree GTT needs to maintain statistics, otherwise it'll lead
to poor query plans. AFAIK the current patch stores the info in a hash
table in a backend private memory, and I don't see how else to do that
(e.g. storing it in a catalog would cause catalog bloat).

FWIW this is a reasons why I think just using shared buffers (instead of
local ones) is not sufficient to support parallel queriesl as proposed
by Alexander. The workers would not know the stats, breaking planning of
queries in PARALLEL SAFE plpgsql functions etc.

3 Still no one commented on GTT's transaction information processing, they include
3.1 Should gtt's frozenxid need to be care?
3.2 gtt’s clog clean
3.3 How to deal with "too old" gtt data

No idea what to do about this.

I suggest we discuss further, reach an agreement, and merge the two patches to one.

OK, cool. Thanks for the clarification.

regards

--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#45Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Tomas Vondra (#44)
Re: [Proposal] Global temporary tables

On Mon, 6 Jan 2020 at 11:01, Tomas Vondra <tomas.vondra@2ndquadrant.com> wrote:

On Mon, Jan 06, 2020 at 01:04:15PM +0800, 曾文旌(义从) wrote:

2 We feel that gtt needs to maintain statistics, but there is no
agreement on what it will be done.

I certainly agree GTT needs to maintain statistics, otherwise it'll lead
to poor query plans.

+1

AFAIK the current patch stores the info in a hash
table in a backend private memory, and I don't see how else to do that
(e.g. storing it in a catalog would cause catalog bloat).

It sounds like it needs a pair of system GTTs to hold the table and
column statistics for other GTTs. One would probably have the same
columns as pg_statistic, and the other just the relevant columns from
pg_class. I can see it being useful for the user to be able to see
these stats, so perhaps they could be UNIONed into the existing stats
view.

Regards,
Dean

#46Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Dean Rasheed (#45)
Re: [Proposal] Global temporary tables

On Mon, Jan 06, 2020 at 12:17:43PM +0000, Dean Rasheed wrote:

On Mon, 6 Jan 2020 at 11:01, Tomas Vondra <tomas.vondra@2ndquadrant.com> wrote:

On Mon, Jan 06, 2020 at 01:04:15PM +0800, 曾文旌(义从) wrote:

2 We feel that gtt needs to maintain statistics, but there is no
agreement on what it will be done.

I certainly agree GTT needs to maintain statistics, otherwise it'll lead
to poor query plans.

+1

AFAIK the current patch stores the info in a hash
table in a backend private memory, and I don't see how else to do that
(e.g. storing it in a catalog would cause catalog bloat).

It sounds like it needs a pair of system GTTs to hold the table and
column statistics for other GTTs. One would probably have the same
columns as pg_statistic, and the other just the relevant columns from
pg_class. I can see it being useful for the user to be able to see
these stats, so perhaps they could be UNIONed into the existing stats
view.

Hmmm, yeah. A "temporary catalog" (not sure if it can work exactly the
same as GTT) storing pg_statistics data for GTTs might work, I think. It
would not have the catalog bloat issue, which is good.

I still think we'd need to integrate this with the regular pg_statistic
catalogs somehow, so that people don't have to care about two things. I
mean, extensions like hypopg do use pg_statistic data to propose indexes
etc. and it would be nice if we don't make them more complicated.

Not sure why we'd need a temporary version of pg_class, though?

regards

--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#47Pavel Stehule
pavel.stehule@gmail.com
In reply to: Dean Rasheed (#45)
Re: [Proposal] Global temporary tables

po 6. 1. 2020 v 13:17 odesílatel Dean Rasheed <dean.a.rasheed@gmail.com>
napsal:

On Mon, 6 Jan 2020 at 11:01, Tomas Vondra <tomas.vondra@2ndquadrant.com>
wrote:

On Mon, Jan 06, 2020 at 01:04:15PM +0800, 曾文旌(义从) wrote:

2 We feel that gtt needs to maintain statistics, but there is no
agreement on what it will be done.

I certainly agree GTT needs to maintain statistics, otherwise it'll lead
to poor query plans.

+1

AFAIK the current patch stores the info in a hash
table in a backend private memory, and I don't see how else to do that
(e.g. storing it in a catalog would cause catalog bloat).

It sounds like it needs a pair of system GTTs to hold the table and
column statistics for other GTTs. One would probably have the same
columns as pg_statistic, and the other just the relevant columns from
pg_class. I can see it being useful for the user to be able to see
these stats, so perhaps they could be UNIONed into the existing stats
view.

+1

Pavel

Show quoted text

Regards,
Dean

#48曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Dean Rasheed (#45)
Re: [Proposal] Global temporary tables

2020年1月6日 下午8:17,Dean Rasheed <dean.a.rasheed@gmail.com> 写道:

On Mon, 6 Jan 2020 at 11:01, Tomas Vondra <tomas.vondra@2ndquadrant.com> wrote:

On Mon, Jan 06, 2020 at 01:04:15PM +0800, 曾文旌(义从) wrote:

2 We feel that gtt needs to maintain statistics, but there is no
agreement on what it will be done.

I certainly agree GTT needs to maintain statistics, otherwise it'll lead
to poor query plans.

+1

AFAIK the current patch stores the info in a hash
table in a backend private memory, and I don't see how else to do that
(e.g. storing it in a catalog would cause catalog bloat).

It sounds like it needs a pair of system GTTs to hold the table and
column statistics for other GTTs. One would probably have the same
columns as pg_statistic, and the other just the relevant columns from
pg_class. I can see it being useful for the user to be able to see
these stats, so perhaps they could be UNIONed into the existing stats
view.

The current patch provides several functions as extension(pg_gtt) for read gtt statistics.
Next I can move them to the kernel and let the view pg_stats can see gtt’s statistics.

Show quoted text

Regards,
Dean

#49Konstantin Knizhnik
k.knizhnik@postgrespro.ru
In reply to: 曾文旌(义从) (#43)
Re: [Proposal] Global temporary tables

On 06.01.2020 8:04, 曾文旌(义从) wrote:

In the previous communication

1 we agreed on the general direction
1.1 gtt use local (private) buffer
1.2 no replica access in first version

2 We feel that gtt needs to maintain statistics, but there is no agreement on what it will be done.

3 Still no one commented on GTT's transaction information processing, they include
3.1 Should gtt's frozenxid need to be care?
3.2 gtt’s clog clean
3.3 How to deal with "too old" gtt data

I suggest we discuss further, reach an agreement, and merge the two patches to one.

I also hope that we should come to the common solution for GTT.
If we do not try to address parallel execution issues and access to temp
tables at replicas (and I agreed
that it should be avoided in first version of the patch), then GTT patch
becomes quite small.

The most complex and challenged task is to support GTT for all kind of
indexes. Unfortunately I can not proposed some good universal solution
for it.
Just patching all existed indexes implementation seems to be the only
choice.

Statistic is another important case.
But once again I do not completely understand why we want to address all
this issues with statistic in first version of the patch? It contradicts
to the idea to make this patch as small as possible.
Also it seems to me that everybody agreed that users very rarely create
indexes for temp tables and explicitly analyze them.
So I think GTT will be useful even with limited support of statistic. In
my version statistics for GTT is provided by pushing correspondent
information to backend's cache for pg_statistic table.
Also I provided pg_temp_statistic view for inspecting it by users. The
idea to make pg_statistic a view which combines statistic of normal and
temporary tables is overkill from my point of view.

I do not understand why do we need to maintain hash with some extra
information for GTT in backends memory (as it was done in Wenjing patch).
Also idea to use create extension for accessing this information seems
to be dubious.

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

#50Konstantin Knizhnik
k.knizhnik@postgrespro.ru
In reply to: Tomas Vondra (#44)
Re: [Proposal] Global temporary tables

On 06.01.2020 14:01, Tomas Vondra wrote:

On Mon, Jan 06, 2020 at 01:04:15PM +0800, 曾文旌(义从) wrote:

In the previous communication

1 we agreed on the general direction
1.1 gtt use local (private) buffer
1.2 no replica access in first version

OK, good.

2 We feel that gtt needs to maintain statistics, but there is no
agreement on what it will be done.

I certainly agree GTT needs to maintain statistics, otherwise it'll lead
to poor query plans. AFAIK the current patch stores the info in a hash
table in a backend private memory, and I don't see how else to do that
(e.g. storing it in a catalog would cause catalog bloat).

FWIW this is a reasons why I think just using shared buffers (instead of
local ones) is not sufficient to support parallel queriesl as proposed
by Alexander. The workers would not know the stats, breaking planning of
queries in PARALLEL SAFE plpgsql functions etc.

I do not think that "all or nothing" approach is so good for software
development as for database transactions.
Yes, if we have function in PL/pgSQL which performs queries om temporary
tables, then
parallel workers may build inefficient plan for this queries due to lack
of statistics.
From my point of view this is not a pitfall of GTT but result of lack
of global plan cache in Postgres. And it should be fixed not at GTT level.

Also I never see real use cases with such functions, even in the systems
which using hard temporary tables and stored procedures.
But there are many other real problems with temp tables  (except already
mentioned in this thread).
In PgPro/EE we have fixes for some of them, for example:

1. Do not reserve space in the file for temp relations. Right now append
of relation cause writing zero page to the disk by mdextend.
It cause useless disk IO for temp tables which in most cases fit in
memory and should not be written at disk.

2. Implicitly perform analyze of temp table intermediately after storing
data in it. Usually tables are analyzed by autovacuum in background.
But it doesn't work for temp tables which are not processes by
autovacuum and are accessed immediately after filling them with data and
lack of statistic  may cause
building very inefficient plan. We have online_analyze extension which
force analyze of the table after appending some bulk of data to it.
It can be used for normal table but most of all it is useful for temp
relations.

Unlike hypothetical example with parallel safe function working with
temp tables,
this are real problems observed by some of our customers.
Them are applicable both to local and global temp tables and this is why
I do not want to discuss them in context of GTT.

3 Still no one commented on GTT's transaction information processing,
they include
3.1 Should gtt's frozenxid need to be care?
3.2 gtt’s clog clean
3.3 How to deal with "too old" gtt data

No idea what to do about this.

I wonder what is the specific of GTT here?
The same problem takes place for normal (local) temp tables, doesn't it?

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

#51Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Konstantin Knizhnik (#50)
Re: [Proposal] Global temporary tables

On Thu, Jan 09, 2020 at 06:07:46PM +0300, Konstantin Knizhnik wrote:

On 06.01.2020 14:01, Tomas Vondra wrote:

On Mon, Jan 06, 2020 at 01:04:15PM +0800, 曾文旌(义从) wrote:

In the previous communication

1 we agreed on the general direction 1.1 gtt use local (private)
buffer 1.2 no replica access in first version

OK, good.

2 We feel that gtt needs to maintain statistics, but there is no
agreement on what it will be done.

I certainly agree GTT needs to maintain statistics, otherwise it'll
lead to poor query plans. AFAIK the current patch stores the info in a
hash table in a backend private memory, and I don't see how else to do
that (e.g. storing it in a catalog would cause catalog bloat).

FWIW this is a reasons why I think just using shared buffers (instead
of local ones) is not sufficient to support parallel queriesl as
proposed by Alexander. The workers would not know the stats, breaking
planning of queries in PARALLEL SAFE plpgsql functions etc.

I do not think that "all or nothing" approach is so good for software
development as for database transactions.

Well, sure. I'm not saying we need to have a perfect solution in v1. I'm
saying if we have two choices:

(1) Use shared buffers even if it means the parallel query plan may be
arbitrarily bad.

(2) Use private buffers, even if it means no parallel queries with temp
tables.

Then I'm voting for (2) because it's less likely to break down. I can
imagine allowing parallel queries with GTT when there's no risk of
having to plan in the worker, but that's not there yet.

If we can come up with a reasonable solution for the parallel case, we
can enable it later.

Yes, if we have function in PL/pgSQL which performs queries om
temporary tables, then
parallel workers may build inefficient plan for this queries due to
lack of statistics.

IMHO that's a pretty awful deficiency, because it essentially means
users may need to disable parallelism for such queries. Which means
we'll get complaints from users, and we'll have to come up with some
sort of solution. I'd rather not be in that position.

From my point of view this is not a pitfall of GTT but result of lack
of global plan cache in Postgres. And it should be fixed not at GTT
level.

That doesn't give us free pass to just ignore the issue. Even if it
really was due to a lack of global plan cache, the fact is we don't have
that feature, so we have a problem. I mean, if you need infrastructure
that is not available, you either have to implement that infrastructure
or make it work properly without it.

Also I never see real use cases with such functions, even in the
systems which using hard temporary tables and stored procedures.
But there are many other real problems with temp tables  (except
already mentioned in this thread).

Oh, I'm sure there are pretty large plpgsql applications, and I'd be
surprised if at least some of those were not affected. And I'm sure
there are apps using UDF to do all sorts of stuff (e.g. I wonder if
PostGIS would have this issue - IIRC it's using SPI etc.).

The question is whether we should consider existing apps affected,
because they are using the regular temporary tables and not GTT. So
unless they switch to GTT there is no regression ...

But even in that case I don't think it's a good idea to accept this as
an acceptable limitation. I admit one of the reasons why I think that
may be that statistics and planning are my areas of interest, so I'm not
quite willing to accept incomplete stuff as OK.

In PgPro/EE we have fixes for some of them, for example:

1. Do not reserve space in the file for temp relations. Right now
append of relation cause writing zero page to the disk by mdextend.
It cause useless disk IO for temp tables which in most cases fit in
memory and should not be written at disk.

2. Implicitly perform analyze of temp table intermediately after
storing data in it. Usually tables are analyzed by autovacuum in
background.
But it doesn't work for temp tables which are not processes by
autovacuum and are accessed immediately after filling them with data
and lack of statistic  may cause
building very inefficient plan. We have online_analyze extension which
force analyze of the table after appending some bulk of data to it.
It can be used for normal table but most of all it is useful for temp
relations.

Unlike hypothetical example with parallel safe function working with
temp tables,
this are real problems observed by some of our customers.
Them are applicable both to local and global temp tables and this is
why I do not want to discuss them in context of GTT.

I think those are both interesting issues worth fixing, but I don't
think it makes the issue discussed here less important.

3 Still no one commented on GTT's transaction information
processing, they include
3.1 Should gtt's frozenxid need to be care?
3.2 gtt’s clog clean
3.3 How to deal with "too old" gtt data

No idea what to do about this.

I wonder what is the specific of GTT here?
The same problem takes place for normal (local) temp tables, doesn't it?

Not sure. TBH I'm not sure I understand what the issue actually is.

regards

--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#52Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Konstantin Knizhnik (#49)
Re: [Proposal] Global temporary tables

On Thu, Jan 09, 2020 at 02:17:08PM +0300, Konstantin Knizhnik wrote:

On 06.01.2020 8:04, 曾文旌(义从) wrote:

In the previous communication

1 we agreed on the general direction
1.1 gtt use local (private) buffer
1.2 no replica access in first version

2 We feel that gtt needs to maintain statistics, but there is no agreement on what it will be done.

3 Still no one commented on GTT's transaction information processing, they include
3.1 Should gtt's frozenxid need to be care?
3.2 gtt’s clog clean
3.3 How to deal with "too old" gtt data

I suggest we discuss further, reach an agreement, and merge the two patches to one.

I also hope that we should come to the common solution for GTT.
If we do not try to address parallel execution issues and access to
temp tables at replicas (and I agreed
that it should be avoided in first version of the patch), then GTT
patch becomes quite small.

Well, that was kinda my goal - making the patch as small as possible by
eliminating bits that are contentious or where we don't know the
solution (like planning for parallel queries).

The most complex and challenged task is to support GTT for all kind of
indexes. Unfortunately I can not proposed some good universal solution
for it.
Just patching all existed indexes implementation seems to be the only
choice.

I haven't looked at the indexing issue closely, but IMO we need to
ensure that every session sees/uses only indexes on GTT that were
defined before the seesion started using the table.

Can't we track which indexes a particular session sees, somehow?

Statistic is another important case.
But once again I do not completely understand why we want to address
all this issues with statistic in first version of the patch?

I think the question is which "issues with statistic" you mean. I'm sure
we can ignore some of them, e.g. the one with parallel workers not
having any stats (assuming we consider functions using GTT to be
parallel restricted).

It contradicts to the idea to make this patch as small as possible.

Well, there's "making patch as small as possible" vs. "patch behaving
correctly" trade-off ;-)

Also it seems to me that everybody agreed that users very rarely
create indexes for temp tables and explicitly analyze them.

I certainly *disagree* with this.

We often see temporary tables as a fix or misestimates in complex
queries, and/or as a replacement for CTEs with statistics/indexes. In
fact it's a pretty valuable tool when helping customers with complex
queries affected by poor estimates.

So I think GTT will be useful even with limited support of statistic.
In my version statistics for GTT is provided by pushing correspondent
information to backend's cache for pg_statistic table.

I think someone pointed out pushing stuff directly into the cache is
rather problematic, but I don't recall the details.

Also I provided pg_temp_statistic view for inspecting it by users. The
idea to make pg_statistic a view which combines statistic of normal
and temporary tables is overkill from my point of view.

I do not understand why do we need to maintain hash with some extra
information for GTT in backends memory (as it was done in Wenjing
patch).
Also idea to use create extension for accessing this information seems
to be dubious.

I think the extension was more a PoC rather than a final solution.

regards
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#53Konstantin Knizhnik
k.knizhnik@postgrespro.ru
In reply to: Tomas Vondra (#52)
Re: [Proposal] Global temporary tables

On 09.01.2020 19:48, Tomas Vondra wrote:

The most complex and challenged task is to support GTT for all kind
of indexes. Unfortunately I can not proposed some good universal
solution for it.
Just patching all existed indexes implementation seems to be the only
choice.

I haven't looked at the indexing issue closely, but IMO we need to
ensure that every session sees/uses only indexes on GTT that were
defined before the seesion started using the table.

Why? It contradicts with behavior of normal tables.
Assume that you have active clients and at some point of time DBA
recognizes that them are spending to much time in scanning some GTT.
It cab create index for this GTT but if existed client will not be able
to use this index, then we need somehow make this clients to restart
their sessions?
In my patch I have implemented building indexes for GTT on demand: if
accessed index on GTT is not yet initialized, then it is filled with
local data.

Can't we track which indexes a particular session sees, somehow?

Statistic is another important case.
But once again I do not completely understand why we want to address
all this issues with statistic in first version of the patch?

I think the question is which "issues with statistic" you mean. I'm sure
we can ignore some of them, e.g. the one with parallel workers not
having any stats (assuming we consider functions using GTT to be
parallel restricted).

If we do not use shared buffers for GTT then parallel processing of GTT
is not possible at all, so there is no problem with statistic for
parallel workers.

I think someone pointed out pushing stuff directly into the cache is
rather problematic, but I don't recall the details.

I have not encountered any problems, so if you can point me on what is
wrong with this approach, I will think about alternative solution.

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

#54Konstantin Knizhnik
k.knizhnik@postgrespro.ru
In reply to: Tomas Vondra (#51)
Re: [Proposal] Global temporary tables

On 09.01.2020 19:30, Tomas Vondra wrote:

3 Still no one commented on GTT's transaction information
processing, they include
3.1 Should gtt's frozenxid need to be care?
3.2 gtt’s clog clean
3.3 How to deal with "too old" gtt data

No idea what to do about this.

I wonder what is the specific of GTT here?
The same problem takes place for normal (local) temp tables, doesn't it?

Not sure. TBH I'm not sure I understand what the issue actually is.

Just open session, create temporary table and insert some data in it.
Then in other session run 2^31 transactions (at my desktop it takes
about 2 hours).
As far as temp tables are not proceeded by vacuum, database is stalled:

 ERROR:  database is not accepting commands to avoid wraparound data
loss in database "postgres"

It seems to be quite dubious behavior and it is strange to me that
nobody complains about it.
We discuss  many issues related with temp tables (statistic, parallel
queries,...) which seems to be less critical.

But this problem is not specific to GTT - it can be reproduced with
normal (local) temp tables.
This is why I wonder why do we need to solve it in GTT patch.

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

#55曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Tomas Vondra (#42)
1 attachment(s)
Re: [Proposal] Global temporary tables

Hi all

This is the latest patch

The updates are as follows:
1. Support global temp Inherit table global temp partition table
2. Support serial column in GTT
3. Provide views pg_gtt_relstats pg_gtt_stats for GTT’s statistics
4. Provide view pg_gtt_attached_pids to manage GTT
5. Provide function pg_list_gtt_relfrozenxids() to manage GTT
6. Alter GTT or rename GTT is allowed under some conditions

Please give me feedback.

Wenjing

Show quoted text

2020年1月6日 上午4:06,Tomas Vondra <tomas.vondra@2ndquadrant.com> 写道:

Hi,

I think we need to do something with having two patches aiming to add
global temporary tables:

[1] https://commitfest.postgresql.org/26/2349/

[2] https://commitfest.postgresql.org/26/2233/

As a reviewer I have no idea which of the threads to look at - certainly
not without reading both threads, which I doubt anyone will really do.
The reviews and discussions are somewhat intermixed between those two
threads, which makes it even more confusing.

I think we should agree on a minimal patch combining the necessary/good
bits from the various patches, and terminate one of the threads (i.e.
mark it as rejected or RWF). And we need to do that now, otherwise
there's about 0% chance of getting this into v13.

In general, I agree with the sentiment Rober expressed in [1] - the
patch needs to be as small as possible, not adding "nice to have"
features (like support for parallel queries - I very much doubt just
using shared instead of local buffers is enough to make it work.)

regards

--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

global_temporary_table_v3-pg13.patchapplication/octet-stream; name=global_temporary_table_v3-pg13.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 79430d2..b7173c7 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 | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},	
 	/* list terminator */
 	{{NULL}}
 };
@@ -1486,6 +1499,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1586,13 +1601,18 @@ build_reloptions(Datum reloptions, bool validate,
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	/*
 	 * There are no options for partitioned tables yet, but this is able to do
 	 * some validation.
 	 */
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index dd975b1..1610e7d 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1013,7 +1013,9 @@ gistGetFakeLSN(Relation rel)
 {
 	static XLogRecPtr counter = FirstNormalUnloggedLSN;
 
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	/* global temp is same as local temp table */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 4bb6efc..8dc8b03 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -146,7 +146,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 a6c369e..675c2b8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -598,7 +598,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -651,7 +651,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index a5fe904..be6fc5f 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?
@@ -217,8 +218,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)
@@ -277,8 +280,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 f05cbe7..946c9d2 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -763,7 +763,14 @@ _bt_getbuf(Relation rel, BlockNumber blkno, int access)
 		/* Read an existing block of the relation */
 		buf = ReadBuffer(rel, blkno);
 		LockBuffer(buf, access);
-		_bt_checkpage(rel, buf);
+
+		/* global temp table may be not yet initialized for this backend. */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			blkno == BTREE_METAPAGE &&
+			PageIsNew(BufferGetPage(buf)))
+			_bt_initmetapage(BufferGetPage(buf), P_NONE, 0);
+		else
+			_bt_checkpage(rel, buf);
 	}
 	else
 	{
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index c814733..ff51840 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -24,6 +24,7 @@
 #include "access/heapam.h"		/* for ss_* */
 #include "access/tableam.h"
 #include "access/xact.h"
+#include "catalog/storage_gtt.h"
 #include "optimizer/plancat.h"
 #include "storage/bufmgr.h"
 #include "storage/shmem.h"
@@ -560,10 +561,20 @@ table_block_relation_estimate_size(Relation rel, int32 *attr_widths,
 	/* it should have storage, so we can 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;
+	/* 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;
+	}
 
 	/*
 	 * HACK: if the relation has never yet been vacuumed, use a minimum size
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 7f4f784..aba8a9f 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6327,6 +6327,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 61db650..fb87816 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 16cb6d8..f28f2c2 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -398,7 +398,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/* global temp table is same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 0fdff29..54a5243 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -404,6 +406,10 @@ heap_create(const char *relname,
 									 relpersistence,
 									 relkind);
 
+	/* global temp table not create storage file when catalog create */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		create_storage = false;
+
 	/*
 	 * Have the storage manager create the relation's disk file, if needed.
 	 *
@@ -427,7 +433,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -956,6 +962,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -994,8 +1001,18 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/* global temp table not remember transaction info in catalog */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1357,6 +1374,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1441,11 +1459,15 @@ heap_create_with_catalog(const char *relname,
 	 */
 	StoreConstraints(new_rel_desc, cooked_constraints, is_internal);
 
-	/*
-	 * If there's a special on-commit action, remember it
-	 */
-	if (oncommit != ONCOMMIT_NOOP)
-		register_on_commit_action(relid, oncommit);
+	/* global temp table register action when storage init */
+	if (relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		/*
+		 * If there's a special on-commit action, remember it
+		 */
+		if (oncommit != ONCOMMIT_NOOP)
+			register_on_commit_action(relid, oncommit);
+	}
 
 	/*
 	 * ok, the relation has been cataloged, so close our relations and return
@@ -1937,6 +1959,13 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/* We allow to drop global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(rel->rd_node))
+			elog(ERROR, "can not drop relation when other backend attached this global temp table");
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3163,9 +3192,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;
 
@@ -3177,7 +3207,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3223,8 +3253,13 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		/* truncate global temp table only need RowExclusiveLock */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3257,6 +3292,8 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
+	bool		truncate_toastrel = false;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3265,23 +3302,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 3e59e64..1d6306d 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -52,6 +52,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -877,6 +878,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))
+			elog(ERROR, "can not create index when have other 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 */
+		if (!gtt_storage_attached(heapRelation->rd_node.relNode))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -2045,6 +2066,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.
@@ -2736,20 +2764,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;
+			}
 		}
 	}
 
@@ -2864,6 +2901,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
 	 */
@@ -3196,12 +3240,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;
 
 	/*
@@ -3459,6 +3513,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 c82f9fc..8248109 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -647,6 +647,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		/* global temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp relations in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index fddfbf1..03806ae 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -26,6 +26,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -73,9 +74,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;
@@ -85,6 +87,8 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		/* global temp table use same storage strategy as local temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -117,6 +121,10 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/* remember global temp table storage info to localhash */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel)
+		remember_gtt_storage_info(rnode, rel);
+
 	return srel;
 }
 
@@ -486,8 +494,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..13cab8e
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1130 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  code to create and destroy physical storage for global temparary table
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/table.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <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);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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 || entry->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		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;
+		int			i;
+
+		rnode.spcNode = entry->spcnode;
+		rnode.dbNode = MyDatabaseId;
+		rnode.relNode = entry->relid;
+
+		gtt_storage_checkout(rnode, false);
+
+		Assert(TransactionIdIsNormal(entry->relfrozenxid));
+		remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+
+		for (i = 0; i < entry->natts; i++)
+		{
+			if (entry->att_stat_tups[i])
+			{
+				heap_freetuple(entry->att_stat_tups[i]);
+				entry->att_stat_tups[i] = NULL;
+			}
+		}
+	}
+
+	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.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (gtt_storage_local_hash == NULL)
+		return false;
+
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_FIND, &found);
+
+	if (!found)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	if (relpages)
+		*relpages = entry->relpages;
+
+	if (reltuples)
+		*reltuples = entry->reltuples;
+
+	if (relallvisible)
+		*relallvisible = entry->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = entry->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = entry->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	MemoryContext			oldcontext;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+						(void *) &reloid, HASH_FIND, &found);
+
+	if (!found)
+		return;
+
+	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;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	HeapTuple	tuple;
+	Relation	rel = NULL;
+	int		attnum = PG_GETARG_INT32(1);
+	Oid		reloid = PG_GETARG_OID(0);
+	char		rel_persistence;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	  tupdesc;
+	MemoryContext oldcontext;
+	Tuplestorestate *tupstore;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = table_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+
+	table_close(rel, NoLock);
+	table_close(pg_tatistic, AccessShareLock);
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate	*tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Relation	rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	if (get_gtt_relstats(reloid,
+						&relpages, &reltuples, &relallvisible,
+						&relfrozenxid, &relminmxid))
+	{
+		Datum	values[5];
+		bool	isnull[5];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = Int32GetDatum(relpages);
+		values[1] = Float4GetDatum((float4)reltuples);
+		values[2] = Int32GetDatum(relallvisible);
+		values[3] = UInt32GetDatum(relfrozenxid);
+		values[4] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+
+	tuplestore_donestoring(tupstore);
+	table_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(rel->rd_node);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	table_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2fc3e3f..d0efd18 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM 
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 53b11d7..104d6ce 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 e9d7a7f..3088e26 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -391,6 +391,12 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/* not support cluster global temp table yet */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("not support cluster global temporary tables yet")));
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 52ce02f..3d3f7dc 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2583,6 +2583,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 75410f0..eb1acea 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/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..44f350d 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -94,7 +94,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +108,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -222,7 +223,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* now initialize the sequence's data */
 	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -327,7 +328,7 @@ ResetSequence(Oid seq_relid)
 	/*
 	 * Insert the modified tuple into the new storage file.
 	 */
-	fill_seq_with_data(seq_rel, tuple);
+	fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -340,17 +341,21 @@ ResetSequence(Oid seq_relid)
  * Initialize a sequence's relation with the specified tuple as content
  */
 static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 	sequence_magic *sm;
 	OffsetNumber offnum;
+	bool lockBuffer = false;
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ReadBuffer(rel, P_NEW);
-	Assert(BufferGetBlockNumber(buf) == 0);
+	if (buf == InvalidBuffer)
+	{
+		buf = ReadBuffer(rel, P_NEW);
+		Assert(BufferGetBlockNumber(buf) == 0);
+		lockBuffer = true;
+	}
 
 	page = BufferGetPage(buf);
 
@@ -360,7 +365,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* Now insert sequence tuple */
 
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+	if (lockBuffer)
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +416,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	END_CRIT_SECTION();
 
-	UnlockReleaseBuffer(buf);
+	if (lockBuffer)
+		UnlockReleaseBuffer(buf);
 }
 
 /*
@@ -502,7 +509,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * Insert the modified tuple into the new storage file.
 		 */
-		fill_seq_with_data(seqrel, newdatatuple);
+		fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
 	}
 
 	/* process OWNED BY if given */
@@ -1178,6 +1185,25 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
 	LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
 
 	page = BufferGetPage(*buf);
+	if (GlobalTempRelationPageIsNotInitialized(rel, page))
+	{
+		/* Initialize sequence for global temporary tables */
+		Datum		value[SEQ_COL_LASTCOL] = {0};
+		bool		null[SEQ_COL_LASTCOL] = {false};
+		HeapTuple	tuple;
+		int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+		/*
+		 * last_value from pg_sequence.seqstart
+		 * log_cnt = 0
+		 * is_called = false
+		 */
+		value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+		tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+		fill_seq_with_data(rel, tuple, *buf);
+		heap_freetuple(tuple);
+	}
 	sm = (sequence_magic *) PageGetSpecialPointer(page);
 
 	if (sm->magic != SEQ_MAGIC)
@@ -1954,3 +1980,23 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1c4394a..84bc81e 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -527,6 +528,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);
 
 
 /* ----------------------------------------------------------------
@@ -572,6 +574,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
@@ -582,8 +585,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")));
@@ -613,7 +618,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	/* global temp table same as local temp table */
+	if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+		 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -714,6 +721,57 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* global temp table */
+	has_oncommit_clause = has_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* inherit table or parition table inherit on commit property from parent table*/
+		if (inheritOids && stmt->oncommit == ONCOMMIT_NOOP && !has_oncommit_clause)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			if (RELATION_GTT_ON_COMMIT_DELETE(relation))
+				stmt->oncommit = ONCOMMIT_DELETE_ROWS;
+			else
+				stmt->oncommit = ONCOMMIT_PRESERVE_ROWS;
+			table_close(relation, NoLock);
+		}
+
+		if (has_oncommit_clause)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "can not defeine global temp table with on commit and with clause at same time");
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("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);
 
@@ -1775,7 +1833,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);
@@ -3334,6 +3393,13 @@ RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal, bo
 	 * specially.
 	 */
 	targetrelation = relation_open(myrelid, is_index ? ShareUpdateExclusiveLock : AccessExclusiveLock);
+
+	if (RELATION_IS_GLOBAL_TEMP(targetrelation))
+	{
+		if (is_other_backend_use_gtt(targetrelation->rd_node))
+			elog(ERROR, "can not rename relation when other backend attached this global temp table");
+	}
+
 	namespaceId = RelationGetNamespace(targetrelation);
 
 	/*
@@ -3503,6 +3569,13 @@ AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt)
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(relid, NoLock);
 
+	/* We allow to alter global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(rel->rd_node))
+			elog(ERROR, "can not alter relation when other backend attached this global temp table");
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode);
@@ -7656,6 +7729,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),
@@ -7695,6 +7775,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;
 	}
 
 	/*
@@ -12706,7 +12792,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,
@@ -14113,7 +14199,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",
@@ -16753,3 +16841,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 bb34e25..6e1896f 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 8286d9c..c3992a4 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -591,6 +591,8 @@ static void
 set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 						  RangeTblEntry *rte)
 {
+	char relpersistence;
+
 	/*
 	 * The flag has previously been initialized to false, so we can just
 	 * return if it becomes clear that we can't safely set it.
@@ -618,7 +620,11 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			/* global temp table is same as local temp table */
+			relpersistence = get_rel_persistence(rte->relid);
+
+			if (relpersistence == RELPERSISTENCE_TEMP ||
+				relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index d6f2153..310a9e2 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6312,7 +6312,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
+	/* global temp table is same as local temp table */
 	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index d82fc5a..189286b 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -31,6 +31,7 @@
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -53,6 +54,7 @@
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
 
+
 /* GUC parameter */
 int			constraint_exclusion = CONSTRAINT_EXCLUSION_PARTITION;
 
@@ -946,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)
@@ -963,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.
@@ -971,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 */
@@ -985,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 447a61e..a27f57f 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2576,6 +2576,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 ad5be90..dff21e0 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3270,17 +3270,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; }
@@ -11568,19 +11562,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 ceed0ce..cd1ca50 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3564,3 +3565,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 42095ab..d5fcbed 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -436,6 +436,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index f0e40e3..bb60926 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2086,6 +2086,11 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* autovacuum skip vacuum global temp table */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2152,7 +2157,9 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		/* autovacuum skip vacuum global temp table */
+		if (classForm->relpersistence == RELPERSISTENCE_TEMP || 
+			classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index aba3960..68adcd5 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,
@@ -2809,6 +2822,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(relation->rd_node.relNode))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d5..7adb969 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -21,6 +21,7 @@
 #include "access/nbtree.h"
 #include "access/subtrans.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, active_gtt_shared_hash_size());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* global temporary table */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index c3adb2e..eb95e5f 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -62,6 +62,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -4087,3 +4088,77 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temp table.
+ */
+int
+list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct *arrayP = procArray;
+	TransactionId result = InvalidTransactionId;
+	int			index;
+	int			flags = 0;
+	int			i = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+		*n = 0;
+	}
+
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		volatile PGXACT *pgxact = &allPgXact[pgprocno];
+
+		if (pgxact->vacuumFlags & flags)
+			continue;
+
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->session_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->session_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->session_gtt_frozenxid, result))
+				result = proc->session_gtt_frozenxid;
+
+			if (max_size > 0)
+			{
+				pids[i] = proc->pid;
+				xids[i] = proc->session_gtt_frozenxid;
+				i++;
+			}
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (max_size > 0)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 99e5221..d3a0a18 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c
index 199c722..92aefd0 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 8406644..46eb23a 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 18d77ac..eaeebfe 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -142,6 +142,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;
@@ -4578,12 +4579,25 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -4708,15 +4722,27 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6098,6 +6124,7 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	{
 		/* Simple variable --- look to stats for the underlying table */
 		RangeTblEntry *rte = planner_rt_fetch(index->rel->relid, root);
+		char	rel_persistence = get_rel_persistence(rte->relid);
 
 		Assert(rte->rtekind == RTE_RELATION);
 		relid = rte->relid;
@@ -6115,6 +6142,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6126,6 +6160,8 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	}
 	else
 	{
+		char	rel_persistence = get_rel_persistence(index->indexoid);
+
 		/* Expression --- maybe there are stats for the index itself */
 		relid = index->indexoid;
 		colnum = 1;
@@ -6141,6 +6177,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7029,6 +7072,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))
@@ -7041,6 +7086,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 =
@@ -7053,6 +7106,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.
@@ -7072,6 +7127,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 1e3e6d3..44686ce 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 df025a5..1c30d86 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1124,6 +1124,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);
@@ -3313,6 +3323,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;
@@ -3427,6 +3441,9 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
 
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+		elog(ERROR, "global temp table does not allow setting new relfilenode");
+
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
@@ -3467,7 +3484,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 a4e5d08..37ce83d 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -139,6 +139,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,
@@ -1979,6 +1991,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 799b698..b98d396 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15583,6 +15583,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	{
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
+		char		*table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15634,9 +15635,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 			binary_upgrade_set_pg_class_oids(fout, q,
 											 tbinfo->dobj.catId.oid, false);
 
+		if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED)
+			table_type = "UNLOGGED ";
+		else if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			table_type = "GLOBAL TEMPORARY ";
+		else
+			table_type = "";
+
 		appendPQExpBuffer(q, "CREATE %s%s %s",
-						  tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ?
-						  "UNLOGGED " : "",
+						  table_type,
 						  reltypename,
 						  qualrelname);
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index a12fc1f..78958e4 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -165,6 +165,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 0b6045a..6bc884e 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5477,6 +5477,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4191',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4192',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{relid,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4193',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4194',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 048003c..af48cdf 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -19,7 +19,7 @@
 #include "storage/smgr.h"
 #include "utils/relcache.h"
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationTruncate(Relation rel, BlockNumber nblocks);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..eacc214
--- /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 bool 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 b8bff23..70c01f7 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -117,4 +117,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 870ecb5..92c590e 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 8fda8e4..172eae9 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_TBM,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_SXACT,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 5b407e6..f13250a 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -117,6 +117,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId session_gtt_frozenxid;	/* session level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ce93ace..0f7262e 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -281,6 +281,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 44ed04d..3f9720d 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -277,6 +277,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -535,7 +536,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
@@ -602,6 +604,17 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..50ca9ac
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,7 @@
+reset search_path;
+drop schema gtt cascade;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..2c69f14
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,197 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+ERROR:  cannot create indexes on global temporary tables using concurrent mode
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+ERROR:  not support cluster global temporary tables yet
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ok
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  regular table cannot specifie on_commit_delete_rows
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+);
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp);
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  can not defeine global temp table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test;
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  referenced relation "products" is not a global temp table
+-- ERROR
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  referenced relation "products" is not a global temp table
+-- ERROR
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ALL ERROR
+create index idx_err on gtt1 using hash (a);
+ERROR:  only support btree index on global temp table
+create index idx_err on gtt1 using gist (a);
+ERROR:  data type integer has no default operator class for access method "gist"
+HINT:  You must specify an operator class for the index or define a default operator class for the data type.
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+ERROR:  only support btree index on global temp table
+create index idx_tmp_t0_1 on tmp_t0 using gist (c0);
+ERROR:  only support btree index on global temp table
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 15 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
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/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..b258b7c
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,76 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+(1 row)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+(1 row)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 80a0782..d3d9927 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1348,6 +1348,93 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d33a4e1..643afe4 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..f3cf710
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,6 @@
+
+
+reset search_path;
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..49c3f11
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,163 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ok
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+);
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp);
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test;
+
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ERROR
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ERROR
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ALL ERROR
+create index idx_err on gtt1 using hash (a);
+create index idx_err on gtt1 using gist (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_1 on tmp_t0 using gist (c0);
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d7d81de
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,42 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..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;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..f041892
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,42 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
#56Pavel Stehule
pavel.stehule@gmail.com
In reply to: 曾文旌(义从) (#55)
Re: [Proposal] Global temporary tables

Hi

so 11. 1. 2020 v 15:00 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
napsal:

Hi all

This is the latest patch

The updates are as follows:
1. Support global temp Inherit table global temp partition table
2. Support serial column in GTT
3. Provide views pg_gtt_relstats pg_gtt_stats for GTT’s statistics
4. Provide view pg_gtt_attached_pids to manage GTT
5. Provide function pg_list_gtt_relfrozenxids() to manage GTT
6. Alter GTT or rename GTT is allowed under some conditions

Please give me feedback.

I tested the functionality

1. i think so "ON COMMIT PRESERVE ROWS" should be default mode (like local
temp tables).

I tested some simple scripts

test01.sql

CREATE TEMP TABLE foo(a int, b int);
INSERT INTO foo SELECT random()*100, random()*1000 FROM
generate_series(1,1000);
ANALYZE foo;
SELECT sum(a), sum(b) FROM foo;
DROP TABLE foo; -- simulate disconnect

after 100 sec, the table pg_attribute has 3.2MB
and 64 tps, 6446 transaction

test02.sql

INSERT INTO foo SELECT random()*100, random()*1000 FROM
generate_series(1,1000);
ANALYZE foo;
SELECT sum(a), sum(b) FROM foo;
DELETE FROM foo; -- simulate disconnect

after 100 sec, 1688 tps, 168830 transactions

So performance is absolutely different as we expected.

From my perspective, this functionality is great.

Todo:

pg_table_size function doesn't work

Regards

Pavel

Show quoted text

Wenjing

2020年1月6日 上午4:06,Tomas Vondra <tomas.vondra@2ndquadrant.com> 写道:

Hi,

I think we need to do something with having two patches aiming to add
global temporary tables:

[1] https://commitfest.postgresql.org/26/2349/

[2] https://commitfest.postgresql.org/26/2233/

As a reviewer I have no idea which of the threads to look at - certainly
not without reading both threads, which I doubt anyone will really do.
The reviews and discussions are somewhat intermixed between those two
threads, which makes it even more confusing.

I think we should agree on a minimal patch combining the necessary/good
bits from the various patches, and terminate one of the threads (i.e.
mark it as rejected or RWF). And we need to do that now, otherwise
there's about 0% chance of getting this into v13.

In general, I agree with the sentiment Rober expressed in [1] - the
patch needs to be as small as possible, not adding "nice to have"
features (like support for parallel queries - I very much doubt just
using shared instead of local buffers is enough to make it work.)

regards

--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#57Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Konstantin Knizhnik (#54)
Re: [Proposal] Global temporary tables

On Fri, Jan 10, 2020 at 03:24:34PM +0300, Konstantin Knizhnik wrote:

On 09.01.2020 19:30, Tomas Vondra wrote:

3 Still no one commented on GTT's transaction information
processing, they include
3.1 Should gtt's frozenxid need to be care?
3.2 gtt’s clog clean
3.3 How to deal with "too old" gtt data

No idea what to do about this.

I wonder what is the specific of GTT here?
The same problem takes place for normal (local) temp tables, doesn't it?

Not sure. TBH I'm not sure I understand what the issue actually is.

Just open session, create temporary table and insert some data in it.
Then in other session run 2^31 transactions (at my desktop it takes
about 2 hours).
As far as temp tables are not proceeded by vacuum, database is stalled:

 ERROR:  database is not accepting commands to avoid wraparound data
loss in database "postgres"

It seems to be quite dubious behavior and it is strange to me that
nobody complains about it.
We discuss  many issues related with temp tables (statistic, parallel
queries,...) which seems to be less critical.

But this problem is not specific to GTT - it can be reproduced with
normal (local) temp tables.
This is why I wonder why do we need to solve it in GTT patch.

Yeah, I think that's out of scope for GTT patch. Once we solve it for
plain temporary tables, we'll solve it for GTT too.

regards

--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#58Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Konstantin Knizhnik (#53)
Re: [Proposal] Global temporary tables

On Fri, Jan 10, 2020 at 11:47:42AM +0300, Konstantin Knizhnik wrote:

On 09.01.2020 19:48, Tomas Vondra wrote:

The most complex and challenged task is to support GTT for all
kind of indexes. Unfortunately I can not proposed some good
universal solution for it.
Just patching all existed indexes implementation seems to be the
only choice.

I haven't looked at the indexing issue closely, but IMO we need to
ensure that every session sees/uses only indexes on GTT that were
defined before the seesion started using the table.

Why? It contradicts with behavior of normal tables.
Assume that you have active clients and at some point of time DBA
recognizes that them are spending to much time in scanning some GTT.
It cab create index for this GTT but if existed client will not be
able to use this index, then we need somehow make this clients to
restart their sessions?
In my patch I have implemented building indexes for GTT on demand: if
accessed index on GTT is not yet initialized, then it is filled with
local data.

Yes, I know the behavior would be different from behavior for regular
tables. And yes, it would not allow fixing slow queries in sessions
without interrupting those sessions.

I proposed just ignoring those new indexes because it seems much simpler
than alternative solutions that I can think of, and it's not like those
other solutions don't have other issues.

For example, I've looked at the "on demand" building as implemented in
global_private_temp-8.patch, I kinda doubt adding a bunch of index build
calls into various places in index code seems somewht suspicious.

* brinbuild is added to brinRevmapInitialize, which is meant to
initialize state for scanning. It seems wrong to build the index we're
scanning from this function (layering and all that).

* btbuild is called from _bt_getbuf. That seems a bit ... suspicious?

... and so on for other index types. Also, what about custom indexes
implemented in extensions? It seems a bit strange each of them has to
support this separately.

IMHO if this really is the right solution, we need to make it work for
existing indexes without having to tweak them individually. Why don't we
track a flag whether an index on GTT was initialized in a given session,
and if it was not then call the build function before calling any other
function from the index AM?

But let's talk about other issues caused by "on demand" build. Imagine
you have 50 sessions, each using the same GTT with a GB of per-session
data. Now you create a new index on the GTT, which forces the sessions
to build it's "local" index. Those builds will use maintenance_work_mem
each, so 50 * m_w_m. I doubt that's expected/sensible.

So I suggest we start by just ignoring the *new* indexes, and improve
this in the future (by building the indexes on demand or whatever).

Can't we track which indexes a particular session sees, somehow?

Statistic is another important case.
But once again I do not completely understand why we want to
address all this issues with statistic in first version of the
patch?

I think the question is which "issues with statistic" you mean. I'm sure
we can ignore some of them, e.g. the one with parallel workers not
having any stats (assuming we consider functions using GTT to be
parallel restricted).

If we do not use shared buffers for GTT then parallel processing of
GTT is not possible at all, so there is no problem with statistic for
parallel workers.

Right.

I think someone pointed out pushing stuff directly into the cache is
rather problematic, but I don't recall the details.

I have not encountered any problems, so if you can point me on what is
wrong with this approach, I will think about alternative solution.

I meant this comment by Robert:

/messages/by-id/CA+TgmoZFWaND4PpT_CJbeu6VZGZKi2rrTuSTL-Ykd97fexTN-w@mail.gmail.com

regards

--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#59Konstantin Knizhnik
k.knizhnik@postgrespro.ru
In reply to: Tomas Vondra (#58)
Re: [Proposal] Global temporary tables

On 12.01.2020 4:51, Tomas Vondra wrote:

On Fri, Jan 10, 2020 at 11:47:42AM +0300, Konstantin Knizhnik wrote:

On 09.01.2020 19:48, Tomas Vondra wrote:

The most complex and challenged task is to support GTT for all kind
of indexes. Unfortunately I can not proposed some good universal
solution for it.
Just patching all existed indexes implementation seems to be the
only choice.

I haven't looked at the indexing issue closely, but IMO we need to
ensure that every session sees/uses only indexes on GTT that were
defined before the seesion started using the table.

Why? It contradicts with behavior of normal tables.
Assume that you have active clients and at some point of time DBA
recognizes that them are spending to much time in scanning some GTT.
It cab create index for this GTT but if existed client will not be
able to use this index, then we need somehow make this clients to
restart their sessions?
In my patch I have implemented building indexes for GTT on demand: if
accessed index on GTT is not yet initialized, then it is filled with
local data.

Yes, I know the behavior would be different from behavior for regular
tables. And yes, it would not allow fixing slow queries in sessions
without interrupting those sessions.

I proposed just ignoring those new indexes because it seems much simpler
than alternative solutions that I can think of, and it's not like those
other solutions don't have other issues.

Quit opposite: prohibiting sessions to see indexes created before
session start to use GTT requires more efforts. We need to somehow
maintain and check GTT first access time.

For example, I've looked at the "on demand" building as implemented in
global_private_temp-8.patch, I kinda doubt adding a bunch of index build
calls into various places in index code seems somewht suspicious.

We in any case has to initialize GTT indexes on demand even if we
prohibit usages of indexes created after first access by session to GTT.
So the difference is only in one thing: should we just initialize empty
index or populate it with local data (if rules for index usability are
the same for GTT as for normal tables).
From implementation point of view there is no big difference. Actually
building index in standard way is even simpler than constructing empty
index. Originally I have implemented
first approach (I just forgot to consider case when GTT was already user
by a session). Then I rewrited it using second approach and patch even
became simpler.

* brinbuild is added to brinRevmapInitialize, which is meant to
  initialize state for scanning. It seems wrong to build the index we're
  scanning from this function (layering and all that).

* btbuild is called from _bt_getbuf. That seems a bit ... suspicious?

As I already mentioned - support of indexes for GTT is one of the most
challenged things in my patch.
I didn't find good and universal solution. So I agreed that call of
btbuild from _bt_getbuf may be considered as suspicious.
I will be pleased if you or sombody else can propose better elternative
and not only for B-Tree, but for all other indexes.

But as I already wrote above, prohibiting session to used indexes
created after first access to GTT doesn't solve the problem.
For normal tables (and for local temp tables) indexes are initialized at
the time of their creation.
With GTT it doesn't work, because each session has its own local data of
GTT.
We should either initialize/build index on demand (when it is first
accessed), either at the moment of session start initialize indexes for
all existed GTTs.
Last options seem to be much worser from my point of view: there may me
huge number of GTT and session may not need to access GTT at all.

... and so on for other index types. Also, what about custom indexes
implemented in extensions? It seems a bit strange each of them has to
support this separately.

I have already complained about it: my patch supports GTT for all
built-in indexes, but custom indexes has to handle it themselves.
Looks like to provide some generic solution we need to extend index API,
providing two diffrent operations: creation and initialization.
But extending index API is very critical change... And also it doesn't
solve the problem with all existed extensions: them in any case have
to be rewritten to implement new API version in order to support GTT.

IMHO if this really is the right solution, we need to make it work for
existing indexes without having to tweak them individually. Why don't we
track a flag whether an index on GTT was initialized in a given session,
and if it was not then call the build function before calling any other
function from the index AM?
But let's talk about other issues caused by "on demand" build. Imagine
you have 50 sessions, each using the same GTT with a GB of per-session
data. Now you create a new index on the GTT, which forces the sessions
to build it's "local" index. Those builds will use maintenance_work_mem
each, so 50 * m_w_m. I doubt that's expected/sensible.

I do not see principle difference here with scenario when 50 sessions
create (local) temp table,
populate it with GB of data and create index for it.

So I suggest we start by just ignoring the *new* indexes, and improve
this in the future (by building the indexes on demand or whatever).

Sorry, but still do not agree with this suggestions:
- it doesn't simplify things
- it makes behavior of GTT incompatible with normal tables.
- it doesn't prevent some bad or unexpected behavior which can't be
currently reproduced with normal (local) temp tables.

I think someone pointed out pushing stuff directly into the cache is
rather problematic, but I don't recall the details.

I have not encountered any problems, so if you can point me on what
is wrong with this approach, I will think about alternative solution.

I meant this comment by Robert:

/messages/by-id/CA+TgmoZFWaND4PpT_CJbeu6VZGZKi2rrTuSTL-Ykd97fexTN-w@mail.gmail.com

"if any code tried to access the statistics directly from the table,
rather than via the caches".

Currently optimizer is accessing statistic though caches. So this
approach works. If somebody will rewrite optimizer or provide own custom
optimizer in extension which access statistic directly
then it we really be a problem. But I wonder why bypassing catalog cache
may be needed.

Moreover, if we implement alternative solution - for example make
pg_statistic a view which combines results for normal tables and GTT,
then existed optimizer has to be rewritten
because it can not access statistic in the way it is doing now. And
there will be all problem with all existed extensions which are
accessing statistic in most natural way - through system cache.

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

#60Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Konstantin Knizhnik (#59)
Re: [Proposal] Global temporary tables

On Mon, Jan 13, 2020 at 11:08:40AM +0300, Konstantin Knizhnik wrote:

On 12.01.2020 4:51, Tomas Vondra wrote:

On Fri, Jan 10, 2020 at 11:47:42AM +0300, Konstantin Knizhnik wrote:

On 09.01.2020 19:48, Tomas Vondra wrote:

The most complex and challenged task is to support GTT for all
kind of indexes. Unfortunately I can not proposed some good
universal solution for it.
Just patching all existed indexes implementation seems to be
the only choice.

I haven't looked at the indexing issue closely, but IMO we need to
ensure that every session sees/uses only indexes on GTT that were
defined before the seesion started using the table.

Why? It contradicts with behavior of normal tables.
Assume that you have active clients and at some point of time DBA
recognizes that them are spending to much time in scanning some
GTT.
It cab create index for this GTT but if existed client will not be
able to use this index, then we need somehow make this clients to
restart their sessions?
In my patch I have implemented building indexes for GTT on demand:
if accessed index on GTT is not yet initialized, then it is filled
with local data.

Yes, I know the behavior would be different from behavior for regular
tables. And yes, it would not allow fixing slow queries in sessions
without interrupting those sessions.

I proposed just ignoring those new indexes because it seems much simpler
than alternative solutions that I can think of, and it's not like those
other solutions don't have other issues.

Quit opposite: prohibiting sessions to see indexes created before
session start to use GTT requires more efforts. We need to somehow
maintain and check GTT first access time.

Hmmm, OK. I'd expect such check to be much simpler than the on-demand
index building, but I admit I haven't tried implementing either of those
options.

For example, I've looked at the "on demand" building as implemented in
global_private_temp-8.patch, I kinda doubt adding a bunch of index build
calls into various places in index code seems somewht suspicious.

We in any case has to initialize GTT indexes on demand even if we
prohibit usages of indexes created after first access by session to
GTT.
So the difference is only in one thing: should we just initialize
empty index or populate it with local data (if rules for index
usability are the same for GTT as for normal tables).
From implementation point of view there is no big difference. Actually
building index in standard way is even simpler than constructing empty
index. Originally I have implemented
first approach (I just forgot to consider case when GTT was already
user by a session). Then I rewrited it using second approach and patch
even became simpler.

* brinbuild is added to brinRevmapInitialize, which is meant to
� initialize state for scanning. It seems wrong to build the index we're
� scanning from this function (layering and all that).

* btbuild is called from _bt_getbuf. That seems a bit ... suspicious?

As I already mentioned - support of indexes for GTT is one of the most
challenged things in my patch.
I didn't find good and universal solution. So I agreed that call of
btbuild from _bt_getbuf may be considered as suspicious.
I will be pleased if you or sombody else can propose better
elternative and not only for B-Tree, but for all other indexes.

But as I already wrote above, prohibiting session to used indexes
created after first access to GTT doesn't solve the problem.
For normal tables (and for local temp tables) indexes are initialized
at the time of their creation.
With GTT it doesn't work, because each session has its own local data
of GTT.
We should either initialize/build index on demand (when it is first
accessed), either at the moment of session start initialize indexes
for all existed GTTs.
Last options seem to be much worser from my point of view: there may
me huge number of GTT and session may not need to access GTT at all.

... and so on for other index types. Also, what about custom indexes
implemented in extensions? It seems a bit strange each of them has to
support this separately.

I have already complained about it: my patch supports GTT for all
built-in indexes, but custom indexes has to handle it themselves.
Looks like to provide some generic solution we need to extend index
API, providing two diffrent operations: creation and initialization.
But extending index API is very critical change... And also it doesn't
solve the problem with all existed extensions: them in any case have
to be rewritten to implement new API version in order to support GTT.

Why not to allow creating only indexes implementing this new API method
(on GTT)?

IMHO if this really is the right solution, we need to make it work for
existing indexes without having to tweak them individually. Why don't we
track a flag whether an index on GTT was initialized in a given session,
and if it was not then call the build function before calling any other
function from the index AM?
But let's talk about other issues caused by "on demand" build. Imagine
you have 50 sessions, each using the same GTT with a GB of per-session
data. Now you create a new index on the GTT, which forces the sessions
to build it's "local" index. Those builds will use maintenance_work_mem
each, so 50 * m_w_m. I doubt that's expected/sensible.

I do not see principle difference here with scenario when 50 sessions
create (local) temp table,
populate it with GB of data and create index for it.

I'd say the high memory consumption is pretty significant.

So I suggest we start by just ignoring the *new* indexes, and improve
this in the future (by building the indexes on demand or whatever).

Sorry, but still do not agree with this suggestions:
- it doesn't simplify things
- it makes behavior of GTT incompatible with normal tables.
- it doesn't prevent some bad or unexpected behavior which can't be
currently reproduced with normal (local) temp tables.

I think someone pointed out pushing stuff directly into the cache is
rather problematic, but I don't recall the details.

I have not encountered any problems, so if you can point me on
what is wrong with this approach, I will think about alternative
solution.

I meant this comment by Robert:

/messages/by-id/CA+TgmoZFWaND4PpT_CJbeu6VZGZKi2rrTuSTL-Ykd97fexTN-w@mail.gmail.com

"if any code tried to access the statistics directly from the table,
rather than via the caches".

Currently optimizer is accessing statistic though caches. So this
approach works. If somebody will rewrite optimizer or provide own
custom optimizer in extension which access statistic directly
then it we really be a problem. But I wonder why bypassing catalog
cache may be needed.

I don't know, but it seems extensions like hypopg do it.

Moreover, if we implement alternative solution - for example make
pg_statistic a view which combines results for normal tables and GTT,
then existed optimizer has to be rewritten
because it can not access statistic in the way it is doing now. And
there will be all problem with all existed extensions which are
accessing statistic in most natural way - through system cache.

Perhaps. I don't know enough about this part of the code to have a
strong opinion.

regards

--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#61Julien Rouhaud
rjuju123@gmail.com
In reply to: Tomas Vondra (#60)
Re: [Proposal] Global temporary tables

On Mon, Jan 13, 2020 at 05:32:53PM +0100, Tomas Vondra wrote:

On Mon, Jan 13, 2020 at 11:08:40AM +0300, Konstantin Knizhnik wrote:

"if any code tried to access the statistics directly from the table,
rather than via the caches".

Currently optimizer is accessing statistic though caches. So this
approach works. If somebody will rewrite optimizer or provide own
custom optimizer in extension which access statistic directly
then it we really be a problem. But I wonder why bypassing catalog
cache may be needed.

I don't know, but it seems extensions like hypopg do it.

AFAIR, hypopg only opens pg_statistic to use its tupledesc when creating
statistics on hypothetical partitions, but it should otherwise never reads or
need plain pg_statistic rows.

#62Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Julien Rouhaud (#61)
Re: [Proposal] Global temporary tables

On Mon, Jan 13, 2020 at 09:12:38PM +0100, Julien Rouhaud wrote:

On Mon, Jan 13, 2020 at 05:32:53PM +0100, Tomas Vondra wrote:

On Mon, Jan 13, 2020 at 11:08:40AM +0300, Konstantin Knizhnik wrote:

"if any code tried to access the statistics directly from the table,
rather than via the caches".

Currently optimizer is accessing statistic though caches. So this
approach works. If somebody will rewrite optimizer or provide own
custom optimizer in extension which access statistic directly
then it we really be a problem. But I wonder why bypassing catalog
cache may be needed.

I don't know, but it seems extensions like hypopg do it.

AFAIR, hypopg only opens pg_statistic to use its tupledesc when creating
statistics on hypothetical partitions, but it should otherwise never reads or
need plain pg_statistic rows.

Ah, OK! Thanks for the clarification. I knew it does something with the
catalog, didn't realize it only gets the descriptor.

regards

--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#63曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Pavel Stehule (#56)
Re: [Proposal] Global temporary tables

Thank you for review my patch.

2020年1月12日 上午4:27,Pavel Stehule <pavel.stehule@gmail.com> 写道:

Hi

so 11. 1. 2020 v 15:00 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:
Hi all

This is the latest patch

The updates are as follows:
1. Support global temp Inherit table global temp partition table
2. Support serial column in GTT
3. Provide views pg_gtt_relstats pg_gtt_stats for GTT’s statistics
4. Provide view pg_gtt_attached_pids to manage GTT
5. Provide function pg_list_gtt_relfrozenxids() to manage GTT
6. Alter GTT or rename GTT is allowed under some conditions

Please give me feedback.

I tested the functionality

1. i think so "ON COMMIT PRESERVE ROWS" should be default mode (like local temp tables).

makes sense, I will fix it.

I tested some simple scripts

test01.sql

CREATE TEMP TABLE foo(a int, b int);
INSERT INTO foo SELECT random()*100, random()*1000 FROM generate_series(1,1000);
ANALYZE foo;
SELECT sum(a), sum(b) FROM foo;
DROP TABLE foo; -- simulate disconnect

after 100 sec, the table pg_attribute has 3.2MB
and 64 tps, 6446 transaction

test02.sql

INSERT INTO foo SELECT random()*100, random()*1000 FROM generate_series(1,1000);
ANALYZE foo;
SELECT sum(a), sum(b) FROM foo;
DELETE FROM foo; -- simulate disconnect

after 100 sec, 1688 tps, 168830 transactions

So performance is absolutely different as we expected.

From my perspective, this functionality is great.

Yes, frequent ddl causes catalog bloat, GTT avoids this problem.

Todo:

pg_table_size function doesn't work

Do you mean that function pg_table_size() need get the storage space used by the one GTT in the entire db(include all session) .

Show quoted text

Regards

Pavel

Wenjing

2020年1月6日 上午4:06,Tomas Vondra <tomas.vondra@2ndquadrant.com <mailto:tomas.vondra@2ndquadrant.com>> 写道:

Hi,

I think we need to do something with having two patches aiming to add
global temporary tables:

[1] https://commitfest.postgresql.org/26/2349/ <https://commitfest.postgresql.org/26/2349/&gt;

[2] https://commitfest.postgresql.org/26/2233/ <https://commitfest.postgresql.org/26/2233/&gt;

As a reviewer I have no idea which of the threads to look at - certainly
not without reading both threads, which I doubt anyone will really do.
The reviews and discussions are somewhat intermixed between those two
threads, which makes it even more confusing.

I think we should agree on a minimal patch combining the necessary/good
bits from the various patches, and terminate one of the threads (i.e.
mark it as rejected or RWF). And we need to do that now, otherwise
there's about 0% chance of getting this into v13.

In general, I agree with the sentiment Rober expressed in [1] - the
patch needs to be as small as possible, not adding "nice to have"
features (like support for parallel queries - I very much doubt just
using shared instead of local buffers is enough to make it work.)

regards

--
Tomas Vondra http://www.2ndQuadrant.com <http://www.2ndquadrant.com/&gt;
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#64Pavel Stehule
pavel.stehule@gmail.com
In reply to: 曾文旌(义从) (#63)
Re: [Proposal] Global temporary tables

út 14. 1. 2020 v 14:09 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
napsal:

Thank you for review my patch.

2020年1月12日 上午4:27,Pavel Stehule <pavel.stehule@gmail.com> 写道:

Hi

so 11. 1. 2020 v 15:00 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
napsal:

Hi all

This is the latest patch

The updates are as follows:
1. Support global temp Inherit table global temp partition table
2. Support serial column in GTT
3. Provide views pg_gtt_relstats pg_gtt_stats for GTT’s statistics
4. Provide view pg_gtt_attached_pids to manage GTT
5. Provide function pg_list_gtt_relfrozenxids() to manage GTT
6. Alter GTT or rename GTT is allowed under some conditions

Please give me feedback.

I tested the functionality

1. i think so "ON COMMIT PRESERVE ROWS" should be default mode (like local
temp tables).

makes sense, I will fix it.

I tested some simple scripts

test01.sql

CREATE TEMP TABLE foo(a int, b int);
INSERT INTO foo SELECT random()*100, random()*1000 FROM
generate_series(1,1000);
ANALYZE foo;
SELECT sum(a), sum(b) FROM foo;
DROP TABLE foo; -- simulate disconnect

after 100 sec, the table pg_attribute has 3.2MB
and 64 tps, 6446 transaction

test02.sql

INSERT INTO foo SELECT random()*100, random()*1000 FROM
generate_series(1,1000);
ANALYZE foo;
SELECT sum(a), sum(b) FROM foo;
DELETE FROM foo; -- simulate disconnect

after 100 sec, 1688 tps, 168830 transactions

So performance is absolutely different as we expected.

From my perspective, this functionality is great.

Yes, frequent ddl causes catalog bloat, GTT avoids this problem.

Todo:

pg_table_size function doesn't work

Do you mean that function pg_table_size() need get the storage space used
by the one GTT in the entire db(include all session) .

It's question how much GTT tables should be similar to classic tables. But
the reporting in psql should to work \dt+, \l+, \di+

Show quoted text

Regards

Pavel

Wenjing

2020年1月6日 上午4:06,Tomas Vondra <tomas.vondra@2ndquadrant.com> 写道:

Hi,

I think we need to do something with having two patches aiming to add
global temporary tables:

[1] https://commitfest.postgresql.org/26/2349/

[2] https://commitfest.postgresql.org/26/2233/

As a reviewer I have no idea which of the threads to look at - certainly
not without reading both threads, which I doubt anyone will really do.
The reviews and discussions are somewhat intermixed between those two
threads, which makes it even more confusing.

I think we should agree on a minimal patch combining the necessary/good
bits from the various patches, and terminate one of the threads (i.e.
mark it as rejected or RWF). And we need to do that now, otherwise
there's about 0% chance of getting this into v13.

In general, I agree with the sentiment Rober expressed in [1] - the
patch needs to be as small as possible, not adding "nice to have"
features (like support for parallel queries - I very much doubt just
using shared instead of local buffers is enough to make it work.)

regards

--
Tomas Vondra http://www.2ndQuadrant.com
<http://www.2ndquadrant.com/&gt;
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#65曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Tomas Vondra (#57)
Re: [Proposal] Global temporary tables

2020年1月12日 上午9:14,Tomas Vondra <tomas.vondra@2ndquadrant.com> 写道:

On Fri, Jan 10, 2020 at 03:24:34PM +0300, Konstantin Knizhnik wrote:

On 09.01.2020 19:30, Tomas Vondra wrote:

3 Still no one commented on GTT's transaction information processing, they include
3.1 Should gtt's frozenxid need to be care?
3.2 gtt’s clog clean
3.3 How to deal with "too old" gtt data

No idea what to do about this.

I wonder what is the specific of GTT here?
The same problem takes place for normal (local) temp tables, doesn't it?

Not sure. TBH I'm not sure I understand what the issue actually is.

Just open session, create temporary table and insert some data in it.
Then in other session run 2^31 transactions (at my desktop it takes about 2 hours).
As far as temp tables are not proceeded by vacuum, database is stalled:

ERROR: database is not accepting commands to avoid wraparound data loss in database "postgres"

It seems to be quite dubious behavior and it is strange to me that nobody complains about it.
We discuss many issues related with temp tables (statistic, parallel queries,...) which seems to be less critical.

But this problem is not specific to GTT - it can be reproduced with normal (local) temp tables.
This is why I wonder why do we need to solve it in GTT patch.

Yeah, I think that's out of scope for GTT patch. Once we solve it for
plain temporary tables, we'll solve it for GTT too.

1. The core problem is that the data contains transaction information (xid), which needs to be vacuum(freeze) regularly to avoid running out of xid.
The autovacuum supports vacuum regular table but local temp does not. autovacuum also does not support GTT.

2. However, the difference between the local temp table and the global temp table(GTT) is that
a) For local temp table: one table hava one piece of data. the frozenxid of one local temp table is store in the catalog(pg_class).
b) For global temp table: each session has a separate copy of data, one GTT may contain maxbackend frozenxid.
and I don't think it's a good idea to keep frozenxid of GTT in the catalog(pg_class).
It becomes a question: how to handle GTT transaction information?

I agree that problem 1 should be completely solved by a some feature, such as local transactions. It is definitely not included in the GTT patch.

But, I think we need to ensure the durability of GTT data. For example, data in GTT cannot be lost due to the clog being cleaned up. It belongs to problem 2.

Wenjing

Show quoted text

regards

--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#66曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Pavel Stehule (#64)
Re: [Proposal] Global temporary tables

2020年1月14日 下午9:20,Pavel Stehule <pavel.stehule@gmail.com> 写道:

út 14. 1. 2020 v 14:09 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:
Thank you for review my patch.

2020年1月12日 上午4:27,Pavel Stehule <pavel.stehule@gmail.com <mailto:pavel.stehule@gmail.com>> 写道:

Hi

so 11. 1. 2020 v 15:00 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:
Hi all

This is the latest patch

The updates are as follows:
1. Support global temp Inherit table global temp partition table
2. Support serial column in GTT
3. Provide views pg_gtt_relstats pg_gtt_stats for GTT’s statistics
4. Provide view pg_gtt_attached_pids to manage GTT
5. Provide function pg_list_gtt_relfrozenxids() to manage GTT
6. Alter GTT or rename GTT is allowed under some conditions

Please give me feedback.

I tested the functionality

1. i think so "ON COMMIT PRESERVE ROWS" should be default mode (like local temp tables).

makes sense, I will fix it.

I tested some simple scripts

test01.sql

CREATE TEMP TABLE foo(a int, b int);
INSERT INTO foo SELECT random()*100, random()*1000 FROM generate_series(1,1000);
ANALYZE foo;
SELECT sum(a), sum(b) FROM foo;
DROP TABLE foo; -- simulate disconnect

after 100 sec, the table pg_attribute has 3.2MB
and 64 tps, 6446 transaction

test02.sql

INSERT INTO foo SELECT random()*100, random()*1000 FROM generate_series(1,1000);
ANALYZE foo;
SELECT sum(a), sum(b) FROM foo;
DELETE FROM foo; -- simulate disconnect

after 100 sec, 1688 tps, 168830 transactions

So performance is absolutely different as we expected.

From my perspective, this functionality is great.

Yes, frequent ddl causes catalog bloat, GTT avoids this problem.

Todo:

pg_table_size function doesn't work

Do you mean that function pg_table_size() need get the storage space used by the one GTT in the entire db(include all session) .

It's question how much GTT tables should be similar to classic tables. But the reporting in psql should to work \dt+, \l+, \di+

Get it, I will fix it.

Show quoted text

Regards

Pavel

Wenjing

2020年1月6日 上午4:06,Tomas Vondra <tomas.vondra@2ndquadrant.com <mailto:tomas.vondra@2ndquadrant.com>> 写道:

Hi,

I think we need to do something with having two patches aiming to add
global temporary tables:

[1] https://commitfest.postgresql.org/26/2349/ <https://commitfest.postgresql.org/26/2349/&gt;

[2] https://commitfest.postgresql.org/26/2233/ <https://commitfest.postgresql.org/26/2233/&gt;

As a reviewer I have no idea which of the threads to look at - certainly
not without reading both threads, which I doubt anyone will really do.
The reviews and discussions are somewhat intermixed between those two
threads, which makes it even more confusing.

I think we should agree on a minimal patch combining the necessary/good
bits from the various patches, and terminate one of the threads (i.e.
mark it as rejected or RWF). And we need to do that now, otherwise
there's about 0% chance of getting this into v13.

In general, I agree with the sentiment Rober expressed in [1] - the
patch needs to be as small as possible, not adding "nice to have"
features (like support for parallel queries - I very much doubt just
using shared instead of local buffers is enough to make it work.)

regards

--
Tomas Vondra http://www.2ndQuadrant.com <http://www.2ndquadrant.com/&gt;
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#67曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Konstantin Knizhnik (#59)
Re: [Proposal] Global temporary tables

2020年1月13日 下午4:08,Konstantin Knizhnik <k.knizhnik@postgrespro.ru> 写道:

On 12.01.2020 4:51, Tomas Vondra wrote:

On Fri, Jan 10, 2020 at 11:47:42AM +0300, Konstantin Knizhnik wrote:

On 09.01.2020 19:48, Tomas Vondra wrote:

The most complex and challenged task is to support GTT for all kind of indexes. Unfortunately I can not proposed some good universal solution for it.
Just patching all existed indexes implementation seems to be the only choice.

I haven't looked at the indexing issue closely, but IMO we need to
ensure that every session sees/uses only indexes on GTT that were
defined before the seesion started using the table.

Why? It contradicts with behavior of normal tables.
Assume that you have active clients and at some point of time DBA recognizes that them are spending to much time in scanning some GTT.
It cab create index for this GTT but if existed client will not be able to use this index, then we need somehow make this clients to restart their sessions?
In my patch I have implemented building indexes for GTT on demand: if accessed index on GTT is not yet initialized, then it is filled with local data.

Yes, I know the behavior would be different from behavior for regular
tables. And yes, it would not allow fixing slow queries in sessions
without interrupting those sessions.

I proposed just ignoring those new indexes because it seems much simpler
than alternative solutions that I can think of, and it's not like those
other solutions don't have other issues.

Quit opposite: prohibiting sessions to see indexes created before session start to use GTT requires more efforts. We need to somehow maintain and check GTT first access time.

For example, I've looked at the "on demand" building as implemented in
global_private_temp-8.patch, I kinda doubt adding a bunch of index build
calls into various places in index code seems somewht suspicious.

We in any case has to initialize GTT indexes on demand even if we prohibit usages of indexes created after first access by session to GTT.
So the difference is only in one thing: should we just initialize empty index or populate it with local data (if rules for index usability are the same for GTT as for normal tables).
From implementation point of view there is no big difference. Actually building index in standard way is even simpler than constructing empty index. Originally I have implemented
first approach (I just forgot to consider case when GTT was already user by a session). Then I rewrited it using second approach and patch even became simpler.

* brinbuild is added to brinRevmapInitialize, which is meant to
initialize state for scanning. It seems wrong to build the index we're
scanning from this function (layering and all that).

* btbuild is called from _bt_getbuf. That seems a bit ... suspicious?

As I already mentioned - support of indexes for GTT is one of the most challenged things in my patch.
I didn't find good and universal solution. So I agreed that call of btbuild from _bt_getbuf may be considered as suspicious.
I will be pleased if you or sombody else can propose better elternative and not only for B-Tree, but for all other indexes.

But as I already wrote above, prohibiting session to used indexes created after first access to GTT doesn't solve the problem.
For normal tables (and for local temp tables) indexes are initialized at the time of their creation.
With GTT it doesn't work, because each session has its own local data of GTT.
We should either initialize/build index on demand (when it is first accessed), either at the moment of session start initialize indexes for all existed GTTs.
Last options seem to be much worser from my point of view: there may me huge number of GTT and session may not need to access GTT at all.

... and so on for other index types. Also, what about custom indexes
implemented in extensions? It seems a bit strange each of them has to
support this separately.

I have already complained about it: my patch supports GTT for all built-in indexes, but custom indexes has to handle it themselves.
Looks like to provide some generic solution we need to extend index API, providing two diffrent operations: creation and initialization.
But extending index API is very critical change... And also it doesn't solve the problem with all existed extensions: them in any case have
to be rewritten to implement new API version in order to support GTT.

IMHO if this really is the right solution, we need to make it work for
existing indexes without having to tweak them individually. Why don't we
track a flag whether an index on GTT was initialized in a given session,
and if it was not then call the build function before calling any other
function from the index AM?
But let's talk about other issues caused by "on demand" build. Imagine
you have 50 sessions, each using the same GTT with a GB of per-session
data. Now you create a new index on the GTT, which forces the sessions
to build it's "local" index. Those builds will use maintenance_work_mem
each, so 50 * m_w_m. I doubt that's expected/sensible.

I do not see principle difference here with scenario when 50 sessions create (local) temp table,
populate it with GB of data and create index for it.

I think the problem is that when one session completes the creation of the index on GTT,
it will trigger the other sessions build own local index of GTT in a centralized time.
This will consume a lot of hardware resources (cpu io memory) in a short time,
and even the database service becomes slow, because 50 sessions are building index.
I think this is not what we expected.

So I suggest we start by just ignoring the *new* indexes, and improve
this in the future (by building the indexes on demand or whatever).

Sorry, but still do not agree with this suggestions:
- it doesn't simplify things
- it makes behavior of GTT incompatible with normal tables.
- it doesn't prevent some bad or unexpected behavior which can't be currently reproduced with normal (local) temp tables.

From a user perspective, this proposal is reasonable.
From an implementation perspective, the same GTT index needs to maintain different states (valid or invalid) in different sessions,
which seems difficult to do in the current framework.

So in my first version, I chose to complete all index creation before using GTT.
I think this will satisfy most use cases.

Show quoted text

I think someone pointed out pushing stuff directly into the cache is
rather problematic, but I don't recall the details.

I have not encountered any problems, so if you can point me on what is wrong with this approach, I will think about alternative solution.

I meant this comment by Robert:

/messages/by-id/CA+TgmoZFWaND4PpT_CJbeu6VZGZKi2rrTuSTL-Ykd97fexTN-w@mail.gmail.com

"if any code tried to access the statistics directly from the table, rather than via the caches".

Currently optimizer is accessing statistic though caches. So this approach works. If somebody will rewrite optimizer or provide own custom optimizer in extension which access statistic directly
then it we really be a problem. But I wonder why bypassing catalog cache may be needed.

Moreover, if we implement alternative solution - for example make pg_statistic a view which combines results for normal tables and GTT, then existed optimizer has to be rewritten
because it can not access statistic in the way it is doing now. And there will be all problem with all existed extensions which are accessing statistic in most natural way - through system cache.

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

#68Konstantin Knizhnik
k.knizhnik@postgrespro.ru
In reply to: 曾文旌(义从) (#67)
Re: [Proposal] Global temporary tables

On 15.01.2020 16:10, 曾文旌(义从) wrote:

I do not see principle difference here with scenario when 50 sessions create (local) temp table,
populate it with GB of data and create index for it.

I think the problem is that when one session completes the creation of the index on GTT,
it will trigger the other sessions build own local index of GTT in a centralized time.
This will consume a lot of hardware resources (cpu io memory) in a short time,
and even the database service becomes slow, because 50 sessions are building index.
I think this is not what we expected.

First of all creating index for GTT ni one session doesn't immediately
initiate building indexes in all other sessions.
Indexes are built on demand. If session is not using this GTT any more,
then index for it will not build at all.
And if GTT is really are actively used by all sessions, then building
index and using it for constructing optimal execution plan is better,
then continue to  use sequential scan and read all GTT data from the disk.

And as I already mentioned I do not see some principle difference in
aspect of resource consumptions comparing with current usage of local
temp tables.
If we have have many sessions, each creating temp table, populating it
with data and building index for it, then we will
observe the same CPU utilization and memory resource consumption as in
case of using GTT and creating index for it.

Sorry, but I still not convinced by your and Tomas arguments.
Yes, building GTT index may cause high memory consumption
(maintenance_work_mem * n_backends).
But such consumption can be  observed also without GTT and it has to be
taken in account when choosing value for maintenance_work_mem.
But from my point of view it is much more important to make behavior of
GTT as much compatible with normal tables as possible.
Also from database administration point of view, necessity to restart
sessions to make then use new indexes seems to be very strange and
inconvenient.
Alternatively DBA can address the problem with high memory consumption
by adjusting maintenance_work_mem, so this solution is more flexible.

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

#69曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Pavel Stehule (#64)
1 attachment(s)
Re: [Proposal] Global temporary tables

2020年1月14日 下午9:20,Pavel Stehule <pavel.stehule@gmail.com> 写道:

út 14. 1. 2020 v 14:09 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:
Thank you for review my patch.

2020年1月12日 上午4:27,Pavel Stehule <pavel.stehule@gmail.com <mailto:pavel.stehule@gmail.com>> 写道:

Hi

so 11. 1. 2020 v 15:00 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:
Hi all

This is the latest patch

The updates are as follows:
1. Support global temp Inherit table global temp partition table
2. Support serial column in GTT
3. Provide views pg_gtt_relstats pg_gtt_stats for GTT’s statistics
4. Provide view pg_gtt_attached_pids to manage GTT
5. Provide function pg_list_gtt_relfrozenxids() to manage GTT
6. Alter GTT or rename GTT is allowed under some conditions

Please give me feedback.

I tested the functionality

1. i think so "ON COMMIT PRESERVE ROWS" should be default mode (like local temp tables).

makes sense, I will fix it.

I tested some simple scripts

test01.sql

CREATE TEMP TABLE foo(a int, b int);
INSERT INTO foo SELECT random()*100, random()*1000 FROM generate_series(1,1000);
ANALYZE foo;
SELECT sum(a), sum(b) FROM foo;
DROP TABLE foo; -- simulate disconnect

after 100 sec, the table pg_attribute has 3.2MB
and 64 tps, 6446 transaction

test02.sql

INSERT INTO foo SELECT random()*100, random()*1000 FROM generate_series(1,1000);
ANALYZE foo;
SELECT sum(a), sum(b) FROM foo;
DELETE FROM foo; -- simulate disconnect

after 100 sec, 1688 tps, 168830 transactions

So performance is absolutely different as we expected.

From my perspective, this functionality is great.

Yes, frequent ddl causes catalog bloat, GTT avoids this problem.

Todo:

pg_table_size function doesn't work

Do you mean that function pg_table_size() need get the storage space used by the one GTT in the entire db(include all session) .

It's question how much GTT tables should be similar to classic tables. But the reporting in psql should to work \dt+, \l+, \di+

I have fixed this problem.

Please let me know where I need to improve.

Thanks

Wenjing

Show quoted text

Regards

Pavel

Wenjing

2020年1月6日 上午4:06,Tomas Vondra <tomas.vondra@2ndquadrant.com <mailto:tomas.vondra@2ndquadrant.com>> 写道:

Hi,

I think we need to do something with having two patches aiming to add
global temporary tables:

[1] https://commitfest.postgresql.org/26/2349/ <https://commitfest.postgresql.org/26/2349/&gt;

[2] https://commitfest.postgresql.org/26/2233/ <https://commitfest.postgresql.org/26/2233/&gt;

As a reviewer I have no idea which of the threads to look at - certainly
not without reading both threads, which I doubt anyone will really do.
The reviews and discussions are somewhat intermixed between those two
threads, which makes it even more confusing.

I think we should agree on a minimal patch combining the necessary/good
bits from the various patches, and terminate one of the threads (i.e.
mark it as rejected or RWF). And we need to do that now, otherwise
there's about 0% chance of getting this into v13.

In general, I agree with the sentiment Rober expressed in [1] - the
patch needs to be as small as possible, not adding "nice to have"
features (like support for parallel queries - I very much doubt just
using shared instead of local buffers is enough to make it work.)

regards

--
Tomas Vondra http://www.2ndQuadrant.com <http://www.2ndquadrant.com/&gt;
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

global_temporary_table_v4-pg13.patchapplication/octet-stream; name=global_temporary_table_v4-pg13.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 79430d2..b7173c7 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 | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},	
 	/* list terminator */
 	{{NULL}}
 };
@@ -1486,6 +1499,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1586,13 +1601,18 @@ build_reloptions(Datum reloptions, bool validate,
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	/*
 	 * There are no options for partitioned tables yet, but this is able to do
 	 * some validation.
 	 */
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index dd975b1..1610e7d 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1013,7 +1013,9 @@ gistGetFakeLSN(Relation rel)
 {
 	static XLogRecPtr counter = FirstNormalUnloggedLSN;
 
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	/* global temp is same as local temp table */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 4bb6efc..8dc8b03 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -146,7 +146,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 a6c369e..675c2b8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -598,7 +598,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -651,7 +651,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index a5fe904..be6fc5f 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?
@@ -217,8 +218,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)
@@ -277,8 +280,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 f05cbe7..946c9d2 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -763,7 +763,14 @@ _bt_getbuf(Relation rel, BlockNumber blkno, int access)
 		/* Read an existing block of the relation */
 		buf = ReadBuffer(rel, blkno);
 		LockBuffer(buf, access);
-		_bt_checkpage(rel, buf);
+
+		/* global temp table may be not yet initialized for this backend. */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			blkno == BTREE_METAPAGE &&
+			PageIsNew(BufferGetPage(buf)))
+			_bt_initmetapage(BufferGetPage(buf), P_NONE, 0);
+		else
+			_bt_checkpage(rel, buf);
 	}
 	else
 	{
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index c814733..ff51840 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -24,6 +24,7 @@
 #include "access/heapam.h"		/* for ss_* */
 #include "access/tableam.h"
 #include "access/xact.h"
+#include "catalog/storage_gtt.h"
 #include "optimizer/plancat.h"
 #include "storage/bufmgr.h"
 #include "storage/shmem.h"
@@ -560,10 +561,20 @@ table_block_relation_estimate_size(Relation rel, int32 *attr_widths,
 	/* it should have storage, so we can 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;
+	/* 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;
+	}
 
 	/*
 	 * HACK: if the relation has never yet been vacuumed, use a minimum size
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 7f4f784..aba8a9f 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6327,6 +6327,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 61db650..fb87816 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 16cb6d8..f28f2c2 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -398,7 +398,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/* global temp table is same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 0fdff29..54a5243 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -404,6 +406,10 @@ heap_create(const char *relname,
 									 relpersistence,
 									 relkind);
 
+	/* global temp table not create storage file when catalog create */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		create_storage = false;
+
 	/*
 	 * Have the storage manager create the relation's disk file, if needed.
 	 *
@@ -427,7 +433,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -956,6 +962,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -994,8 +1001,18 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/* global temp table not remember transaction info in catalog */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1357,6 +1374,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1441,11 +1459,15 @@ heap_create_with_catalog(const char *relname,
 	 */
 	StoreConstraints(new_rel_desc, cooked_constraints, is_internal);
 
-	/*
-	 * If there's a special on-commit action, remember it
-	 */
-	if (oncommit != ONCOMMIT_NOOP)
-		register_on_commit_action(relid, oncommit);
+	/* global temp table register action when storage init */
+	if (relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		/*
+		 * If there's a special on-commit action, remember it
+		 */
+		if (oncommit != ONCOMMIT_NOOP)
+			register_on_commit_action(relid, oncommit);
+	}
 
 	/*
 	 * ok, the relation has been cataloged, so close our relations and return
@@ -1937,6 +1959,13 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/* We allow to drop global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(rel->rd_node))
+			elog(ERROR, "can not drop relation when other backend attached this global temp table");
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3163,9 +3192,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;
 
@@ -3177,7 +3207,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3223,8 +3253,13 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		/* truncate global temp table only need RowExclusiveLock */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3257,6 +3292,8 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
+	bool		truncate_toastrel = false;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3265,23 +3302,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 3e59e64..9867d6f 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -52,6 +52,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -877,6 +878,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))
+			elog(ERROR, "can not create index when have other 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 */
+		if (!gtt_storage_attached(heapRelation->rd_node.relNode))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -2045,6 +2066,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.
@@ -2736,20 +2764,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;
+			}
 		}
 	}
 
@@ -2864,6 +2901,12 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, index, val);
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		if (!gtt_storage_attached(indexRelation->rd_node.relNode))
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3196,12 +3239,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;
 
 	/*
@@ -3459,6 +3512,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 c82f9fc..8248109 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -647,6 +647,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		/* global temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp relations in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index fddfbf1..03806ae 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -26,6 +26,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -73,9 +74,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;
@@ -85,6 +87,8 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		/* global temp table use same storage strategy as local temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -117,6 +121,10 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/* remember global temp table storage info to localhash */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel)
+		remember_gtt_storage_info(rnode, rel);
+
 	return srel;
 }
 
@@ -486,8 +494,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..f489cd3
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1129 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  code to create and destroy physical storage for global temparary table
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/table.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_relstats_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+typedef struct gtt_ctl_data
+{
+	LWLock			lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct
+{
+	RelFileNode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+	Oid			relid;
+
+	Oid			spcnode;
+	/* pg_class stat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+	char		relkind;
+	bool		on_commit_delete;
+
+	/* pg_statistic */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(RelFileNode rnode);
+static void gtt_storage_checkout(RelFileNode rnode, bool skiplock);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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 || entry->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		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;
+		int			i;
+
+		rnode.spcNode = entry->spcnode;
+		rnode.dbNode = MyDatabaseId;
+		rnode.relNode = entry->relid;
+
+		gtt_storage_checkout(rnode, false);
+
+		Assert(TransactionIdIsNormal(entry->relfrozenxid));
+		remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+
+		for (i = 0; i < entry->natts; i++)
+		{
+			if (entry->att_stat_tups[i])
+			{
+				heap_freetuple(entry->att_stat_tups[i]);
+				entry->att_stat_tups[i] = NULL;
+			}
+		}
+	}
+
+	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.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (gtt_storage_local_hash == NULL)
+		return false;
+
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_FIND, &found);
+
+	if (!found)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	if (relpages)
+		*relpages = entry->relpages;
+
+	if (reltuples)
+		*reltuples = entry->reltuples;
+
+	if (relallvisible)
+		*relallvisible = entry->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = entry->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = entry->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	MemoryContext			oldcontext;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+						(void *) &reloid, HASH_FIND, &found);
+
+	if (!found)
+		return;
+
+	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;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	HeapTuple	tuple;
+	Relation	rel = NULL;
+	int		attnum = PG_GETARG_INT32(1);
+	Oid		reloid = PG_GETARG_OID(0);
+	char		rel_persistence;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	  tupdesc;
+	MemoryContext oldcontext;
+	Tuplestorestate *tupstore;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = table_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+
+	table_close(rel, NoLock);
+	table_close(pg_tatistic, AccessShareLock);
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate	*tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Relation	rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	if (get_gtt_relstats(reloid,
+						&relpages, &reltuples, &relallvisible,
+						&relfrozenxid, &relminmxid))
+	{
+		Datum	values[5];
+		bool	isnull[5];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = Int32GetDatum(relpages);
+		values[1] = Float4GetDatum((float4)reltuples);
+		values[2] = Int32GetDatum(relallvisible);
+		values[3] = UInt32GetDatum(relfrozenxid);
+		values[4] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+
+	tuplestore_donestoring(tupstore);
+	table_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(rel->rd_node);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	table_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2fc3e3f..d0efd18 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM 
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 53b11d7..104d6ce 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 e9d7a7f..3088e26 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -391,6 +391,12 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/* not support cluster global temp table yet */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("not support cluster global temporary tables yet")));
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 52ce02f..3d3f7dc 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2583,6 +2583,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 75410f0..eb1acea 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/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..44f350d 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -94,7 +94,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +108,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -222,7 +223,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* now initialize the sequence's data */
 	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -327,7 +328,7 @@ ResetSequence(Oid seq_relid)
 	/*
 	 * Insert the modified tuple into the new storage file.
 	 */
-	fill_seq_with_data(seq_rel, tuple);
+	fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -340,17 +341,21 @@ ResetSequence(Oid seq_relid)
  * Initialize a sequence's relation with the specified tuple as content
  */
 static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 	sequence_magic *sm;
 	OffsetNumber offnum;
+	bool lockBuffer = false;
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ReadBuffer(rel, P_NEW);
-	Assert(BufferGetBlockNumber(buf) == 0);
+	if (buf == InvalidBuffer)
+	{
+		buf = ReadBuffer(rel, P_NEW);
+		Assert(BufferGetBlockNumber(buf) == 0);
+		lockBuffer = true;
+	}
 
 	page = BufferGetPage(buf);
 
@@ -360,7 +365,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* Now insert sequence tuple */
 
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+	if (lockBuffer)
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +416,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	END_CRIT_SECTION();
 
-	UnlockReleaseBuffer(buf);
+	if (lockBuffer)
+		UnlockReleaseBuffer(buf);
 }
 
 /*
@@ -502,7 +509,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * Insert the modified tuple into the new storage file.
 		 */
-		fill_seq_with_data(seqrel, newdatatuple);
+		fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
 	}
 
 	/* process OWNED BY if given */
@@ -1178,6 +1185,25 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
 	LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
 
 	page = BufferGetPage(*buf);
+	if (GlobalTempRelationPageIsNotInitialized(rel, page))
+	{
+		/* Initialize sequence for global temporary tables */
+		Datum		value[SEQ_COL_LASTCOL] = {0};
+		bool		null[SEQ_COL_LASTCOL] = {false};
+		HeapTuple	tuple;
+		int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+		/*
+		 * last_value from pg_sequence.seqstart
+		 * log_cnt = 0
+		 * is_called = false
+		 */
+		value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+		tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+		fill_seq_with_data(rel, tuple, *buf);
+		heap_freetuple(tuple);
+	}
 	sm = (sequence_magic *) PageGetSpecialPointer(page);
 
 	if (sm->magic != SEQ_MAGIC)
@@ -1954,3 +1980,23 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1c4394a..84bc81e 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -527,6 +528,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);
 
 
 /* ----------------------------------------------------------------
@@ -572,6 +574,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
@@ -582,8 +585,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")));
@@ -613,7 +618,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	/* global temp table same as local temp table */
+	if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+		 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -714,6 +721,57 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* global temp table */
+	has_oncommit_clause = has_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* inherit table or parition table inherit on commit property from parent table*/
+		if (inheritOids && stmt->oncommit == ONCOMMIT_NOOP && !has_oncommit_clause)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			if (RELATION_GTT_ON_COMMIT_DELETE(relation))
+				stmt->oncommit = ONCOMMIT_DELETE_ROWS;
+			else
+				stmt->oncommit = ONCOMMIT_PRESERVE_ROWS;
+			table_close(relation, NoLock);
+		}
+
+		if (has_oncommit_clause)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "can not defeine global temp table with on commit and with clause at same time");
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("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);
 
@@ -1775,7 +1833,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);
@@ -3334,6 +3393,13 @@ RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal, bo
 	 * specially.
 	 */
 	targetrelation = relation_open(myrelid, is_index ? ShareUpdateExclusiveLock : AccessExclusiveLock);
+
+	if (RELATION_IS_GLOBAL_TEMP(targetrelation))
+	{
+		if (is_other_backend_use_gtt(targetrelation->rd_node))
+			elog(ERROR, "can not rename relation when other backend attached this global temp table");
+	}
+
 	namespaceId = RelationGetNamespace(targetrelation);
 
 	/*
@@ -3503,6 +3569,13 @@ AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt)
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(relid, NoLock);
 
+	/* We allow to alter global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(rel->rd_node))
+			elog(ERROR, "can not alter relation when other backend attached this global temp table");
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode);
@@ -7656,6 +7729,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),
@@ -7695,6 +7775,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;
 	}
 
 	/*
@@ -12706,7 +12792,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,
@@ -14113,7 +14199,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",
@@ -16753,3 +16841,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 bb34e25..6e1896f 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 8286d9c..c3992a4 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -591,6 +591,8 @@ static void
 set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 						  RangeTblEntry *rte)
 {
+	char relpersistence;
+
 	/*
 	 * The flag has previously been initialized to false, so we can just
 	 * return if it becomes clear that we can't safely set it.
@@ -618,7 +620,11 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			/* global temp table is same as local temp table */
+			relpersistence = get_rel_persistence(rte->relid);
+
+			if (relpersistence == RELPERSISTENCE_TEMP ||
+				relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index d6f2153..310a9e2 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6312,7 +6312,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
+	/* global temp table is same as local temp table */
 	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index d82fc5a..189286b 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -31,6 +31,7 @@
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -53,6 +54,7 @@
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
 
+
 /* GUC parameter */
 int			constraint_exclusion = CONSTRAINT_EXCLUSION_PARTITION;
 
@@ -946,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)
@@ -963,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.
@@ -971,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 */
@@ -985,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 447a61e..a27f57f 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2576,6 +2576,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 ad5be90..dff21e0 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3270,17 +3270,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; }
@@ -11568,19 +11562,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 ceed0ce..cd1ca50 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3564,3 +3565,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 42095ab..d5fcbed 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -436,6 +436,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index f0e40e3..bb60926 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2086,6 +2086,11 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* autovacuum skip vacuum global temp table */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2152,7 +2157,9 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		/* autovacuum skip vacuum global temp table */
+		if (classForm->relpersistence == RELPERSISTENCE_TEMP || 
+			classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index aba3960..68adcd5 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,
@@ -2809,6 +2822,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(relation->rd_node.relNode))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d5..7adb969 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -21,6 +21,7 @@
 #include "access/nbtree.h"
 #include "access/subtrans.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, active_gtt_shared_hash_size());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* global temporary table */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index c3adb2e..eb95e5f 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -62,6 +62,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -4087,3 +4088,77 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temp table.
+ */
+int
+list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct *arrayP = procArray;
+	TransactionId result = InvalidTransactionId;
+	int			index;
+	int			flags = 0;
+	int			i = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+		*n = 0;
+	}
+
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		volatile PGXACT *pgxact = &allPgXact[pgprocno];
+
+		if (pgxact->vacuumFlags & flags)
+			continue;
+
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->session_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->session_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->session_gtt_frozenxid, result))
+				result = proc->session_gtt_frozenxid;
+
+			if (max_size > 0)
+			{
+				pids[i] = proc->pid;
+				xids[i] = proc->session_gtt_frozenxid;
+				i++;
+			}
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (max_size > 0)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 99e5221..d3a0a18 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c
index 199c722..92aefd0 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 8406644..92f028e 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -19,6 +19,8 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_tablespace.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/index.h"
 #include "commands/dbcommands.h"
 #include "commands/tablespace.h"
 #include "miscadmin.h"
@@ -262,7 +264,6 @@ pg_tablespace_size_name(PG_FUNCTION_ARGS)
 	PG_RETURN_INT64(size);
 }
 
-
 /*
  * calculate size of (one fork of) a relation
  *
@@ -307,6 +308,41 @@ calculate_relation_size(RelFileNode *rfn, BackendId backend, ForkNumber forknum)
 	return totalsize;
 }
 
+static int64
+calculate_relation_size_all_file(Relation rel, const Bitmapset *gtt_map)
+{
+	int64		size = 0;
+	ForkNumber	forkNum;
+	BackendId	backendid;
+
+	/* For global temp table */
+	if (gtt_map)
+	{
+		Bitmapset *map_copy = bms_copy(gtt_map);
+
+		backendid = bms_first_member(map_copy);
+
+		do
+		{
+			for (forkNum = 0; forkNum <= MAX_FORKNUM; forkNum++)
+				size += calculate_relation_size(&(rel->rd_node),
+										backendid,
+										forkNum);
+			backendid = bms_next_member(map_copy, backendid);
+		} while (backendid > 0);
+
+		pfree(map_copy);
+	}
+	else
+	{
+		for (forkNum = 0; forkNum <= MAX_FORKNUM; forkNum++)
+			size += calculate_relation_size(&(rel->rd_node),
+											rel->rd_backend, forkNum);
+	}
+
+	return size;
+}
+
 Datum
 pg_relation_size(PG_FUNCTION_ARGS)
 {
@@ -340,20 +376,17 @@ pg_relation_size(PG_FUNCTION_ARGS)
  * Must not be applied to non-TOAST relations.
  */
 static int64
-calculate_toast_table_size(Oid toastrelid)
+calculate_toast_table_size(Oid toastrelid, Bitmapset *gtt_map)
 {
 	int64		size = 0;
 	Relation	toastRel;
-	ForkNumber	forkNum;
 	ListCell   *lc;
 	List	   *indexlist;
 
 	toastRel = relation_open(toastrelid, AccessShareLock);
 
 	/* toast heap size, including FSM and VM size */
-	for (forkNum = 0; forkNum <= MAX_FORKNUM; forkNum++)
-		size += calculate_relation_size(&(toastRel->rd_node),
-										toastRel->rd_backend, forkNum);
+	size += calculate_relation_size_all_file(toastRel, gtt_map);
 
 	/* toast index size, including FSM and VM size */
 	indexlist = RelationGetIndexList(toastRel);
@@ -365,9 +398,8 @@ calculate_toast_table_size(Oid toastrelid)
 
 		toastIdxRel = relation_open(lfirst_oid(lc),
 									AccessShareLock);
-		for (forkNum = 0; forkNum <= MAX_FORKNUM; forkNum++)
-			size += calculate_relation_size(&(toastIdxRel->rd_node),
-											toastIdxRel->rd_backend, forkNum);
+
+		size += calculate_relation_size_all_file(toastIdxRel, gtt_map);
 
 		relation_close(toastIdxRel, AccessShareLock);
 	}
@@ -386,23 +418,20 @@ calculate_toast_table_size(Oid toastrelid)
  * those won't have attached toast tables, but they can have multiple forks.
  */
 static int64
-calculate_table_size(Relation rel)
+calculate_table_size(Relation rel, Bitmapset *gtt_map)
 {
 	int64		size = 0;
-	ForkNumber	forkNum;
 
 	/*
 	 * heap size, including FSM and VM
 	 */
-	for (forkNum = 0; forkNum <= MAX_FORKNUM; forkNum++)
-		size += calculate_relation_size(&(rel->rd_node), rel->rd_backend,
-										forkNum);
+	size += calculate_relation_size_all_file(rel, gtt_map);
 
 	/*
 	 * Size of toast relation
 	 */
 	if (OidIsValid(rel->rd_rel->reltoastrelid))
-		size += calculate_toast_table_size(rel->rd_rel->reltoastrelid);
+		size += calculate_toast_table_size(rel->rd_rel->reltoastrelid, gtt_map);
 
 	return size;
 }
@@ -413,7 +442,7 @@ calculate_table_size(Relation rel)
  * Can be applied safely to an index, but you'll just get zero.
  */
 static int64
-calculate_indexes_size(Relation rel)
+calculate_indexes_size(Relation rel, Bitmapset *gtt_map)
 {
 	int64		size = 0;
 
@@ -429,14 +458,10 @@ calculate_indexes_size(Relation rel)
 		{
 			Oid			idxOid = lfirst_oid(cell);
 			Relation	idxRel;
-			ForkNumber	forkNum;
 
 			idxRel = relation_open(idxOid, AccessShareLock);
 
-			for (forkNum = 0; forkNum <= MAX_FORKNUM; forkNum++)
-				size += calculate_relation_size(&(idxRel->rd_node),
-												idxRel->rd_backend,
-												forkNum);
+			size += calculate_relation_size_all_file(idxRel, gtt_map);
 
 			relation_close(idxRel, AccessShareLock);
 		}
@@ -453,16 +478,40 @@ pg_table_size(PG_FUNCTION_ARGS)
 	Oid			relOid = PG_GETARG_OID(0);
 	Relation	rel;
 	int64		size;
+	Bitmapset	*gtt_map = NULL;
 
 	rel = try_relation_open(relOid, AccessShareLock);
 
 	if (rel == NULL)
 		PG_RETURN_NULL();
 
-	size = calculate_table_size(rel);
+	/* For GTT, calculate the size of the data file in the session where the GTT has been initialized */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (rel->rd_rel->relkind == RELKIND_INDEX)
+		{
+			Relation	relation;
+			Oid			relid;
+
+			relid = IndexGetRelation(RelationGetRelid(rel), false);
+			relation = try_relation_open(relid, AccessShareLock);
+			if (relation)
+			{
+				gtt_map = copy_active_gtt_bitmap(relation->rd_node);
+				relation_close(relation, AccessShareLock);
+			}
+		}
+		else
+			gtt_map = copy_active_gtt_bitmap(rel->rd_node);
+	}
+
+	size = calculate_table_size(rel, gtt_map);
 
 	relation_close(rel, AccessShareLock);
 
+	if(gtt_map)
+		pfree(gtt_map);
+
 	PG_RETURN_INT64(size);
 }
 
@@ -472,16 +521,24 @@ pg_indexes_size(PG_FUNCTION_ARGS)
 	Oid			relOid = PG_GETARG_OID(0);
 	Relation	rel;
 	int64		size;
+	Bitmapset	*gtt_map = NULL;
 
 	rel = try_relation_open(relOid, AccessShareLock);
 
 	if (rel == NULL)
 		PG_RETURN_NULL();
 
-	size = calculate_indexes_size(rel);
+	/* For GTT, calculate the size of the data file in the session where the GTT has been initialized */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		gtt_map = copy_active_gtt_bitmap(rel->rd_node);
+
+	size = calculate_indexes_size(rel, gtt_map);
 
 	relation_close(rel, AccessShareLock);
 
+	if (gtt_map)
+		pfree(gtt_map);
+
 	PG_RETURN_INT64(size);
 }
 
@@ -493,17 +550,25 @@ static int64
 calculate_total_relation_size(Relation rel)
 {
 	int64		size;
+	Bitmapset	*gtt_map = NULL;
+
+	/* For GTT, calculate the size of the data file in the session where the GTT has been initialized */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		gtt_map = copy_active_gtt_bitmap(rel->rd_node);
 
 	/*
 	 * Aggregate the table size, this includes size of the heap, toast and
 	 * toast index with free space and visibility map
 	 */
-	size = calculate_table_size(rel);
+	size = calculate_table_size(rel, gtt_map);
 
 	/*
 	 * Add size of all attached indexes as well
 	 */
-	size += calculate_indexes_size(rel);
+	size += calculate_indexes_size(rel, gtt_map);
+
+	if (gtt_map)
+		pfree(gtt_map);
 
 	return size;
 }
@@ -1008,6 +1073,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 18d77ac..eaeebfe 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -142,6 +142,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;
@@ -4578,12 +4579,25 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -4708,15 +4722,27 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6098,6 +6124,7 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	{
 		/* Simple variable --- look to stats for the underlying table */
 		RangeTblEntry *rte = planner_rt_fetch(index->rel->relid, root);
+		char	rel_persistence = get_rel_persistence(rte->relid);
 
 		Assert(rte->rtekind == RTE_RELATION);
 		relid = rte->relid;
@@ -6115,6 +6142,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6126,6 +6160,8 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	}
 	else
 	{
+		char	rel_persistence = get_rel_persistence(index->indexoid);
+
 		/* Expression --- maybe there are stats for the index itself */
 		relid = index->indexoid;
 		colnum = 1;
@@ -6141,6 +6177,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7029,6 +7072,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))
@@ -7041,6 +7086,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 =
@@ -7053,6 +7106,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.
@@ -7072,6 +7127,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 1e3e6d3..44686ce 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 df025a5..1c30d86 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1124,6 +1124,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);
@@ -3313,6 +3323,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;
@@ -3427,6 +3441,9 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
 
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+		elog(ERROR, "global temp table does not allow setting new relfilenode");
+
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
@@ -3467,7 +3484,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 a4e5d08..37ce83d 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -139,6 +139,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,
@@ -1979,6 +1991,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 799b698..b98d396 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15583,6 +15583,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	{
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
+		char		*table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15634,9 +15635,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 			binary_upgrade_set_pg_class_oids(fout, q,
 											 tbinfo->dobj.catId.oid, false);
 
+		if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED)
+			table_type = "UNLOGGED ";
+		else if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			table_type = "GLOBAL TEMPORARY ";
+		else
+			table_type = "";
+
 		appendPQExpBuffer(q, "CREATE %s%s %s",
-						  tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ?
-						  "UNLOGGED " : "",
+						  table_type,
 						  reltypename,
 						  qualrelname);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index f3c7eb9..28134e2 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3723,7 +3723,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index a12fc1f..78958e4 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -165,6 +165,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 0b6045a..6bc884e 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5477,6 +5477,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4191',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4192',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{relid,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4193',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4194',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 048003c..af48cdf 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -19,7 +19,7 @@
 #include "storage/smgr.h"
 #include "utils/relcache.h"
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationTruncate(Relation rel, BlockNumber nblocks);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..eacc214
--- /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 bool 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 b8bff23..70c01f7 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -117,4 +117,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 870ecb5..92c590e 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 8fda8e4..172eae9 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_TBM,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_SXACT,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 5b407e6..f13250a 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -117,6 +117,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId session_gtt_frozenxid;	/* session level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ce93ace..0f7262e 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -281,6 +281,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 44ed04d..3f9720d 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -277,6 +277,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -535,7 +536,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
@@ -602,6 +604,17 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..50ca9ac
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,7 @@
+reset search_path;
+drop schema gtt cascade;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..2c69f14
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,197 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+ERROR:  cannot create indexes on global temporary tables using concurrent mode
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+ERROR:  not support cluster global temporary tables yet
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ok
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  regular table cannot specifie on_commit_delete_rows
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+);
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp);
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  can not defeine global temp table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test;
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  referenced relation "products" is not a global temp table
+-- ERROR
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  referenced relation "products" is not a global temp table
+-- ERROR
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ALL ERROR
+create index idx_err on gtt1 using hash (a);
+ERROR:  only support btree index on global temp table
+create index idx_err on gtt1 using gist (a);
+ERROR:  data type integer has no default operator class for access method "gist"
+HINT:  You must specify an operator class for the index or define a default operator class for the data type.
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+ERROR:  only support btree index on global temp table
+create index idx_tmp_t0_1 on tmp_t0 using gist (c0);
+ERROR:  only support btree index on global temp table
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 15 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..30d8a7b
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,84 @@
+set search_path=gtt,sys;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..850ef3e
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,161 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          475136 |                 974848
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |           409600 |        409600 |                 409600
+(2 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..9735a93
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,9 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text);
+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;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..b258b7c
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,76 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+(1 row)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+(1 row)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 80a0782..d3d9927 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1348,6 +1348,93 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d33a4e1..643afe4 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..f3cf710
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,6 @@
+
+
+reset search_path;
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..49c3f11
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,163 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ok
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+);
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp);
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test;
+
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ERROR
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ERROR
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ALL ERROR
+create index idx_err on gtt1 using hash (a);
+create index idx_err on gtt1 using gist (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_1 on tmp_t0 using gist (c0);
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d7d81de
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,42 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..5203c2b
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,76 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..1e4cf27
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,16 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+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;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..f041892
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,42 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
#70Erik Rijkers
er@xs4all.nl
In reply to: 曾文旌(义从) (#69)
Re: [Proposal] Global temporary tables

On 2020-01-19 18:04, 曾文旌(义从) wrote:

2020年1月14日 下午9:20,Pavel Stehule <pavel.stehule@gmail.com> 写道:
út 14. 1. 2020 v 14:09 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com
<mailto:wenjing.zwj@alibaba-inc.com>> napsal:

[global_temporary_table_v4-pg13.patch ]

Hi,

This patch doesn't quiet apply for me:

patching file src/backend/access/common/reloptions.c
patching file src/backend/access/gist/gistutil.c
patching file src/backend/access/hash/hash.c
Hunk #1 succeeded at 149 (offset 3 lines).
patching file src/backend/access/heap/heapam_handler.c
patching file src/backend/access/heap/vacuumlazy.c
patching file src/backend/access/nbtree/nbtpage.c
patching file src/backend/access/table/tableam.c
patching file src/backend/access/transam/xlog.c
patching file src/backend/catalog/Makefile
Hunk #1 FAILED at 44.
1 out of 1 hunk FAILED -- saving rejects to file
src/backend/catalog/Makefile.rej
[...]
(The rest applies without errors)

src/backend/catalog/Makefile.rej contains:

------------------------
--- src/backend/catalog/Makefile
+++ 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
------------------------

Can you have a look?

thanks,

Erik Rijkers

#71曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Erik Rijkers (#70)
1 attachment(s)
Re: [Proposal] Global temporary tables

2020年1月20日 上午1:32,Erik Rijkers <er@xs4all.nl> 写道:

On 2020-01-19 18:04, 曾文旌(义从) wrote:

2020年1月14日 下午9:20,Pavel Stehule <pavel.stehule@gmail.com> 写道:
út 14. 1. 2020 v 14:09 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:

[global_temporary_table_v4-pg13.patch ]

Hi,

This patch doesn't quiet apply for me:

patching file src/backend/access/common/reloptions.c
patching file src/backend/access/gist/gistutil.c
patching file src/backend/access/hash/hash.c
Hunk #1 succeeded at 149 (offset 3 lines).
patching file src/backend/access/heap/heapam_handler.c
patching file src/backend/access/heap/vacuumlazy.c
patching file src/backend/access/nbtree/nbtpage.c
patching file src/backend/access/table/tableam.c
patching file src/backend/access/transam/xlog.c
patching file src/backend/catalog/Makefile
Hunk #1 FAILED at 44.
1 out of 1 hunk FAILED -- saving rejects to file src/backend/catalog/Makefile.rej
[...]
(The rest applies without errors)

src/backend/catalog/Makefile.rej contains:

------------------------
--- src/backend/catalog/Makefile
+++ 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
------------------------

Can you have a look?

I updated the code and remade the patch.
Please give me feedback if you have any more questions.

Attachments:

global_temporary_table_v5-pg13.patchapplication/octet-stream; name=global_temporary_table_v5-pg13.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 79430d2..b7173c7 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 | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},	
 	/* list terminator */
 	{{NULL}}
 };
@@ -1486,6 +1499,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1586,13 +1601,18 @@ build_reloptions(Datum reloptions, bool validate,
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	/*
 	 * There are no options for partitioned tables yet, but this is able to do
 	 * some validation.
 	 */
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index dd975b1..1610e7d 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1013,7 +1013,9 @@ gistGetFakeLSN(Relation rel)
 {
 	static XLogRecPtr counter = FirstNormalUnloggedLSN;
 
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	/* global temp is same as local temp table */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 4871b7f..16b00c9 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -149,7 +149,9 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	/* global temp table is same as local temp table */
+	if (!(index->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		index->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 1f6f6d0..5727ccd 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -598,7 +598,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -651,7 +651,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index b331f4c..5bd681f 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -79,6 +79,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?
@@ -397,8 +398,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)
@@ -457,8 +460,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 f05cbe7..946c9d2 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -763,7 +763,14 @@ _bt_getbuf(Relation rel, BlockNumber blkno, int access)
 		/* Read an existing block of the relation */
 		buf = ReadBuffer(rel, blkno);
 		LockBuffer(buf, access);
-		_bt_checkpage(rel, buf);
+
+		/* global temp table may be not yet initialized for this backend. */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			blkno == BTREE_METAPAGE &&
+			PageIsNew(BufferGetPage(buf)))
+			_bt_initmetapage(BufferGetPage(buf), P_NONE, 0);
+		else
+			_bt_checkpage(rel, buf);
 	}
 	else
 	{
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index c814733..ff51840 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -24,6 +24,7 @@
 #include "access/heapam.h"		/* for ss_* */
 #include "access/tableam.h"
 #include "access/xact.h"
+#include "catalog/storage_gtt.h"
 #include "optimizer/plancat.h"
 #include "storage/bufmgr.h"
 #include "storage/shmem.h"
@@ -560,10 +561,20 @@ table_block_relation_estimate_size(Relation rel, int32 *attr_widths,
 	/* it should have storage, so we can 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;
+	/* 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;
+	}
 
 	/*
 	 * HACK: if the relation has never yet been vacuumed, use a minimum size
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 7f4f784..aba8a9f 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6327,6 +6327,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 d5da81c..c753b0e 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -42,6 +42,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 16cb6d8..f28f2c2 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -398,7 +398,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/* global temp table is same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 0fdff29..54a5243 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -404,6 +406,10 @@ heap_create(const char *relname,
 									 relpersistence,
 									 relkind);
 
+	/* global temp table not create storage file when catalog create */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		create_storage = false;
+
 	/*
 	 * Have the storage manager create the relation's disk file, if needed.
 	 *
@@ -427,7 +433,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -956,6 +962,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -994,8 +1001,18 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/* global temp table not remember transaction info in catalog */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1357,6 +1374,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1441,11 +1459,15 @@ heap_create_with_catalog(const char *relname,
 	 */
 	StoreConstraints(new_rel_desc, cooked_constraints, is_internal);
 
-	/*
-	 * If there's a special on-commit action, remember it
-	 */
-	if (oncommit != ONCOMMIT_NOOP)
-		register_on_commit_action(relid, oncommit);
+	/* global temp table register action when storage init */
+	if (relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		/*
+		 * If there's a special on-commit action, remember it
+		 */
+		if (oncommit != ONCOMMIT_NOOP)
+			register_on_commit_action(relid, oncommit);
+	}
 
 	/*
 	 * ok, the relation has been cataloged, so close our relations and return
@@ -1937,6 +1959,13 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/* We allow to drop global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(rel->rd_node))
+			elog(ERROR, "can not drop relation when other backend attached this global temp table");
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3163,9 +3192,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;
 
@@ -3177,7 +3207,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3223,8 +3253,13 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		/* truncate global temp table only need RowExclusiveLock */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3257,6 +3292,8 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
+	bool		truncate_toastrel = false;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3265,23 +3302,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 3e59e64..9867d6f 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -52,6 +52,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -877,6 +878,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))
+			elog(ERROR, "can not create index when have other 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 */
+		if (!gtt_storage_attached(heapRelation->rd_node.relNode))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -2045,6 +2066,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.
@@ -2736,20 +2764,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;
+			}
 		}
 	}
 
@@ -2864,6 +2901,12 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, index, val);
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		if (!gtt_storage_attached(indexRelation->rd_node.relNode))
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3196,12 +3239,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;
 
 	/*
@@ -3459,6 +3512,15 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 				 errmsg("cannot reindex temporary tables of other sessions")));
 
 	/*
+	 * Because global temp table cannot change relfilenode
+	 * no support reindex on global temp table yet.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(iRel))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot reindex global temporary tables")));
+
+	/*
 	 * Also check for active uses of the index in the current transaction; we
 	 * don't want to reindex underneath an open indexscan.
 	 */
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index e70243a..301da79 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -647,6 +647,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		/* global temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp relations in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index fddfbf1..03806ae 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -26,6 +26,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -73,9 +74,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;
@@ -85,6 +87,8 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		/* global temp table use same storage strategy as local temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -117,6 +121,10 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/* remember global temp table storage info to localhash */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel)
+		remember_gtt_storage_info(rnode, rel);
+
 	return srel;
 }
 
@@ -486,8 +494,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..f489cd3
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1129 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  code to create and destroy physical storage for global temparary table
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/table.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_relstats_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+typedef struct gtt_ctl_data
+{
+	LWLock			lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct
+{
+	RelFileNode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+	Oid			relid;
+
+	Oid			spcnode;
+	/* pg_class stat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+	char		relkind;
+	bool		on_commit_delete;
+
+	/* pg_statistic */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(RelFileNode rnode);
+static void gtt_storage_checkout(RelFileNode rnode, bool skiplock);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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 || entry->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		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;
+		int			i;
+
+		rnode.spcNode = entry->spcnode;
+		rnode.dbNode = MyDatabaseId;
+		rnode.relNode = entry->relid;
+
+		gtt_storage_checkout(rnode, false);
+
+		Assert(TransactionIdIsNormal(entry->relfrozenxid));
+		remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+
+		for (i = 0; i < entry->natts; i++)
+		{
+			if (entry->att_stat_tups[i])
+			{
+				heap_freetuple(entry->att_stat_tups[i]);
+				entry->att_stat_tups[i] = NULL;
+			}
+		}
+	}
+
+	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.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (gtt_storage_local_hash == NULL)
+		return false;
+
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_FIND, &found);
+
+	if (!found)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	if (relpages)
+		*relpages = entry->relpages;
+
+	if (reltuples)
+		*reltuples = entry->reltuples;
+
+	if (relallvisible)
+		*relallvisible = entry->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = entry->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = entry->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	MemoryContext			oldcontext;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+						(void *) &reloid, HASH_FIND, &found);
+
+	if (!found)
+		return;
+
+	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;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	HeapTuple	tuple;
+	Relation	rel = NULL;
+	int		attnum = PG_GETARG_INT32(1);
+	Oid		reloid = PG_GETARG_OID(0);
+	char		rel_persistence;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	  tupdesc;
+	MemoryContext oldcontext;
+	Tuplestorestate *tupstore;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = table_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+
+	table_close(rel, NoLock);
+	table_close(pg_tatistic, AccessShareLock);
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate	*tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Relation	rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	if (get_gtt_relstats(reloid,
+						&relpages, &reltuples, &relallvisible,
+						&relfrozenxid, &relminmxid))
+	{
+		Datum	values[5];
+		bool	isnull[5];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = Int32GetDatum(relpages);
+		values[1] = Float4GetDatum((float4)reltuples);
+		values[2] = Int32GetDatum(relallvisible);
+		values[3] = UInt32GetDatum(relfrozenxid);
+		values[4] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+
+	tuplestore_donestoring(tupstore);
+	table_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(rel->rd_node);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	table_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index c9e75f4..2adde62 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM 
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c4420dd..a789718 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -66,6 +66,7 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+#include "catalog/storage_gtt.h"
 
 /* Per-index data for ANALYZE */
 typedef struct AnlIndexData
@@ -103,7 +104,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -586,14 +587,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 		 * pg_statistic for columns we didn't process, we leave them alone.)
 		 */
 		update_attstats(RelationGetRelid(onerel), inh,
-						attr_cnt, vacattrstats);
+						attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
 
 		for (ind = 0; ind < nindexes; ind++)
 		{
 			AnlIndexData *thisdata = &indexdata[ind];
 
 			update_attstats(RelationGetRelid(Irel[ind]), false,
-							thisdata->attr_cnt, thisdata->vacattrstats);
+							thisdata->attr_cnt, thisdata->vacattrstats,
+							RelationGetRelPersistence(Irel[ind]));
 		}
 
 		/*
@@ -673,11 +675,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);
@@ -1456,7 +1467,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
  *		by taking a self-exclusive lock on the relation in analyze_rel().
  */
 static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
 {
 	Relation	sd;
 	int			attno;
@@ -1558,31 +1569,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 e9d7a7f..3088e26 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -391,6 +391,12 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/* not support cluster global temp table yet */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("not support cluster global temporary tables yet")));
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 52ce02f..3d3f7dc 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2583,6 +2583,16 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 			!isTempNamespace(classtuple->relnamespace))
 			continue;
 
+		/* not support reindex on global temp table, so skip it */
+		if (classtuple->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			ereport(WARNING,
+				(errmsg("global temp table \"%s.%s\" skip reindexed",
+					get_namespace_name(get_rel_namespace(relid)),
+					get_rel_name(relid))));
+			continue;
+		}
+
 		/* Check user/system classification, and optionally skip */
 		if (objectKind == REINDEX_OBJECT_SYSTEM &&
 			!IsSystemClass(relid, classtuple))
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 329ab84..dfa257c 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -96,7 +96,8 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 	 * transaction.
 	 */
 	relpersistence = get_rel_persistence(relid);
-	if (relpersistence == RELPERSISTENCE_TEMP)
+	if (relpersistence == RELPERSISTENCE_TEMP ||
+		relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
 
 	/* Check permissions. */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..44f350d 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -94,7 +94,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +108,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -222,7 +223,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* now initialize the sequence's data */
 	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -327,7 +328,7 @@ ResetSequence(Oid seq_relid)
 	/*
 	 * Insert the modified tuple into the new storage file.
 	 */
-	fill_seq_with_data(seq_rel, tuple);
+	fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -340,17 +341,21 @@ ResetSequence(Oid seq_relid)
  * Initialize a sequence's relation with the specified tuple as content
  */
 static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 	sequence_magic *sm;
 	OffsetNumber offnum;
+	bool lockBuffer = false;
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ReadBuffer(rel, P_NEW);
-	Assert(BufferGetBlockNumber(buf) == 0);
+	if (buf == InvalidBuffer)
+	{
+		buf = ReadBuffer(rel, P_NEW);
+		Assert(BufferGetBlockNumber(buf) == 0);
+		lockBuffer = true;
+	}
 
 	page = BufferGetPage(buf);
 
@@ -360,7 +365,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* Now insert sequence tuple */
 
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+	if (lockBuffer)
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +416,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	END_CRIT_SECTION();
 
-	UnlockReleaseBuffer(buf);
+	if (lockBuffer)
+		UnlockReleaseBuffer(buf);
 }
 
 /*
@@ -502,7 +509,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * Insert the modified tuple into the new storage file.
 		 */
-		fill_seq_with_data(seqrel, newdatatuple);
+		fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
 	}
 
 	/* process OWNED BY if given */
@@ -1178,6 +1185,25 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
 	LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
 
 	page = BufferGetPage(*buf);
+	if (GlobalTempRelationPageIsNotInitialized(rel, page))
+	{
+		/* Initialize sequence for global temporary tables */
+		Datum		value[SEQ_COL_LASTCOL] = {0};
+		bool		null[SEQ_COL_LASTCOL] = {false};
+		HeapTuple	tuple;
+		int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+		/*
+		 * last_value from pg_sequence.seqstart
+		 * log_cnt = 0
+		 * is_called = false
+		 */
+		value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+		tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+		fill_seq_with_data(rel, tuple, *buf);
+		heap_freetuple(tuple);
+	}
 	sm = (sequence_magic *) PageGetSpecialPointer(page);
 
 	if (sm->magic != SEQ_MAGIC)
@@ -1954,3 +1980,23 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 30b72b6..6db8c31 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -552,6 +553,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 								  Relation partitionTbl);
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static bool has_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -597,6 +599,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	bool		has_oncommit_clause = false;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -607,8 +610,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Check consistency of arguments
 	 */
+	/* global temp table same as local temp table */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !(stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+			 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -638,7 +643,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	/* global temp table same as local temp table */
+	if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+		 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -739,6 +746,57 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* global temp table */
+	has_oncommit_clause = has_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* inherit table or parition table inherit on commit property from parent table*/
+		if (inheritOids && stmt->oncommit == ONCOMMIT_NOOP && !has_oncommit_clause)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			if (RELATION_GTT_ON_COMMIT_DELETE(relation))
+				stmt->oncommit = ONCOMMIT_DELETE_ROWS;
+			else
+				stmt->oncommit = ONCOMMIT_PRESERVE_ROWS;
+			table_close(relation, NoLock);
+		}
+
+		if (has_oncommit_clause)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "can not defeine global temp table with on commit and with clause at same time");
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("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);
 
@@ -1800,7 +1858,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);
@@ -3359,6 +3418,13 @@ RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal, bo
 	 * specially.
 	 */
 	targetrelation = relation_open(myrelid, is_index ? ShareUpdateExclusiveLock : AccessExclusiveLock);
+
+	if (RELATION_IS_GLOBAL_TEMP(targetrelation))
+	{
+		if (is_other_backend_use_gtt(targetrelation->rd_node))
+			elog(ERROR, "can not rename relation when other backend attached this global temp table");
+	}
+
 	namespaceId = RelationGetNamespace(targetrelation);
 
 	/*
@@ -3534,6 +3600,13 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(rel->rd_node))
+			elog(ERROR, "can not alter relation when other backend attached this global temp table");
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -8099,6 +8172,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),
@@ -8138,6 +8218,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;
 	}
 
 	/*
@@ -13174,7 +13260,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,
@@ -14581,7 +14667,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",
@@ -17221,3 +17309,20 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
 		table_close(rel, NoLock);
 	}
 }
+
+static bool
+has_oncommit_option(List *options)
+{
+	ListCell   *listptr;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (pg_strcasecmp(def->defname, "on_commit_delete_rows") == 0)
+			return true;
+	}
+
+	return false;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index d625d17..db966e8 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"
@@ -1125,12 +1126,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;
@@ -1230,24 +1244,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)
 	{
 		/*
@@ -1272,37 +1270,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. */
@@ -1394,6 +1419,10 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/* global temp table relstats not in pg_class */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1451,6 +1480,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 8286d9c..c3992a4 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -591,6 +591,8 @@ static void
 set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 						  RangeTblEntry *rte)
 {
+	char relpersistence;
+
 	/*
 	 * The flag has previously been initialized to false, so we can just
 	 * return if it becomes clear that we can't safely set it.
@@ -618,7 +620,11 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			/* global temp table is same as local temp table */
+			relpersistence = get_rel_persistence(rte->relid);
+
+			if (relpersistence == RELPERSISTENCE_TEMP ||
+				relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index d6f2153..310a9e2 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6312,7 +6312,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
+	/* global temp table is same as local temp table */
 	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index d82fc5a..189286b 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -31,6 +31,7 @@
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -53,6 +54,7 @@
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
 
+
 /* GUC parameter */
 int			constraint_exclusion = CONSTRAINT_EXCLUSION_PARTITION;
 
@@ -946,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)
@@ -963,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.
@@ -971,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 */
@@ -985,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 748bebf..a5ddbcd 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2579,6 +2579,11 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("materialized views must not use temporary tables or views")));
 
+		if (is_query_using_gtt(query))
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("materialized views must not use global temporary tables or views")));
+
 		/*
 		 * A materialized view would either need to save parameters for use in
 		 * maintaining/loading the data or prohibit them entirely.  The latter
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ba5916b..0ee6931 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3290,17 +3290,11 @@ OptTemp:	TEMPORARY					{ $$ = RELPERSISTENCE_TEMP; }
 			| LOCAL TEMP				{ $$ = RELPERSISTENCE_TEMP; }
 			| GLOBAL TEMPORARY
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
-					$$ = RELPERSISTENCE_TEMP;
+					$$ = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
-					$$ = RELPERSISTENCE_TEMP;
+					$$ = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED					{ $$ = RELPERSISTENCE_UNLOGGED; }
 			| /*EMPTY*/					{ $$ = RELPERSISTENCE_PERMANENT; }
@@ -11588,19 +11582,13 @@ OptTempTableName:
 				}
 			| GLOBAL TEMPORARY opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED opt_table qualified_name
 				{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b875a50..123945b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index ee2d2b5..9c9abaa 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 6d1f28c..ed837d1 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2086,6 +2086,11 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* autovacuum skip vacuum global temp table */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2152,7 +2157,9 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		/* autovacuum skip vacuum global temp table */
+		if (classForm->relpersistence == RELPERSISTENCE_TEMP || 
+			classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index aba3960..68adcd5 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,
@@ -2809,6 +2822,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(relation->rd_node.relNode))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d5..7adb969 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -21,6 +21,7 @@
 #include "access/nbtree.h"
 #include "access/subtrans.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, active_gtt_shared_hash_size());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* global temporary table */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index c3adb2e..eb95e5f 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -62,6 +62,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -4087,3 +4088,77 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temp table.
+ */
+int
+list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct *arrayP = procArray;
+	TransactionId result = InvalidTransactionId;
+	int			index;
+	int			flags = 0;
+	int			i = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+		*n = 0;
+	}
+
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		volatile PGXACT *pgxact = &allPgXact[pgprocno];
+
+		if (pgxact->vacuumFlags & flags)
+			continue;
+
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->session_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->session_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->session_gtt_frozenxid, result))
+				result = proc->session_gtt_frozenxid;
+
+			if (max_size > 0)
+			{
+				pids[i] = proc->pid;
+				xids[i] = proc->session_gtt_frozenxid;
+				i++;
+			}
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (max_size > 0)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 32df8c8..e4e1125 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c
index 85b7115..f5eae8c 100644
--- a/src/backend/storage/smgr/md.c
+++ b/src/backend/storage/smgr/md.c
@@ -646,6 +646,12 @@ mdread(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
 		 */
 		if (zero_damaged_pages || InRecovery)
 			MemSet(buffer, 0, BLCKSZ);
+		else if(SmgrIsTemp(reln) && blocknum == 0 && forknum == MAIN_FORKNUM)
+		{
+			/* global temp table init btree meta page */
+			MemSet(buffer, 0, BLCKSZ);
+			mdwrite(reln, forknum, blocknum, buffer, true);
+		}
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_DATA_CORRUPTED),
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 8406644..92f028e 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -19,6 +19,8 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_tablespace.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/index.h"
 #include "commands/dbcommands.h"
 #include "commands/tablespace.h"
 #include "miscadmin.h"
@@ -262,7 +264,6 @@ pg_tablespace_size_name(PG_FUNCTION_ARGS)
 	PG_RETURN_INT64(size);
 }
 
-
 /*
  * calculate size of (one fork of) a relation
  *
@@ -307,6 +308,41 @@ calculate_relation_size(RelFileNode *rfn, BackendId backend, ForkNumber forknum)
 	return totalsize;
 }
 
+static int64
+calculate_relation_size_all_file(Relation rel, const Bitmapset *gtt_map)
+{
+	int64		size = 0;
+	ForkNumber	forkNum;
+	BackendId	backendid;
+
+	/* For global temp table */
+	if (gtt_map)
+	{
+		Bitmapset *map_copy = bms_copy(gtt_map);
+
+		backendid = bms_first_member(map_copy);
+
+		do
+		{
+			for (forkNum = 0; forkNum <= MAX_FORKNUM; forkNum++)
+				size += calculate_relation_size(&(rel->rd_node),
+										backendid,
+										forkNum);
+			backendid = bms_next_member(map_copy, backendid);
+		} while (backendid > 0);
+
+		pfree(map_copy);
+	}
+	else
+	{
+		for (forkNum = 0; forkNum <= MAX_FORKNUM; forkNum++)
+			size += calculate_relation_size(&(rel->rd_node),
+											rel->rd_backend, forkNum);
+	}
+
+	return size;
+}
+
 Datum
 pg_relation_size(PG_FUNCTION_ARGS)
 {
@@ -340,20 +376,17 @@ pg_relation_size(PG_FUNCTION_ARGS)
  * Must not be applied to non-TOAST relations.
  */
 static int64
-calculate_toast_table_size(Oid toastrelid)
+calculate_toast_table_size(Oid toastrelid, Bitmapset *gtt_map)
 {
 	int64		size = 0;
 	Relation	toastRel;
-	ForkNumber	forkNum;
 	ListCell   *lc;
 	List	   *indexlist;
 
 	toastRel = relation_open(toastrelid, AccessShareLock);
 
 	/* toast heap size, including FSM and VM size */
-	for (forkNum = 0; forkNum <= MAX_FORKNUM; forkNum++)
-		size += calculate_relation_size(&(toastRel->rd_node),
-										toastRel->rd_backend, forkNum);
+	size += calculate_relation_size_all_file(toastRel, gtt_map);
 
 	/* toast index size, including FSM and VM size */
 	indexlist = RelationGetIndexList(toastRel);
@@ -365,9 +398,8 @@ calculate_toast_table_size(Oid toastrelid)
 
 		toastIdxRel = relation_open(lfirst_oid(lc),
 									AccessShareLock);
-		for (forkNum = 0; forkNum <= MAX_FORKNUM; forkNum++)
-			size += calculate_relation_size(&(toastIdxRel->rd_node),
-											toastIdxRel->rd_backend, forkNum);
+
+		size += calculate_relation_size_all_file(toastIdxRel, gtt_map);
 
 		relation_close(toastIdxRel, AccessShareLock);
 	}
@@ -386,23 +418,20 @@ calculate_toast_table_size(Oid toastrelid)
  * those won't have attached toast tables, but they can have multiple forks.
  */
 static int64
-calculate_table_size(Relation rel)
+calculate_table_size(Relation rel, Bitmapset *gtt_map)
 {
 	int64		size = 0;
-	ForkNumber	forkNum;
 
 	/*
 	 * heap size, including FSM and VM
 	 */
-	for (forkNum = 0; forkNum <= MAX_FORKNUM; forkNum++)
-		size += calculate_relation_size(&(rel->rd_node), rel->rd_backend,
-										forkNum);
+	size += calculate_relation_size_all_file(rel, gtt_map);
 
 	/*
 	 * Size of toast relation
 	 */
 	if (OidIsValid(rel->rd_rel->reltoastrelid))
-		size += calculate_toast_table_size(rel->rd_rel->reltoastrelid);
+		size += calculate_toast_table_size(rel->rd_rel->reltoastrelid, gtt_map);
 
 	return size;
 }
@@ -413,7 +442,7 @@ calculate_table_size(Relation rel)
  * Can be applied safely to an index, but you'll just get zero.
  */
 static int64
-calculate_indexes_size(Relation rel)
+calculate_indexes_size(Relation rel, Bitmapset *gtt_map)
 {
 	int64		size = 0;
 
@@ -429,14 +458,10 @@ calculate_indexes_size(Relation rel)
 		{
 			Oid			idxOid = lfirst_oid(cell);
 			Relation	idxRel;
-			ForkNumber	forkNum;
 
 			idxRel = relation_open(idxOid, AccessShareLock);
 
-			for (forkNum = 0; forkNum <= MAX_FORKNUM; forkNum++)
-				size += calculate_relation_size(&(idxRel->rd_node),
-												idxRel->rd_backend,
-												forkNum);
+			size += calculate_relation_size_all_file(idxRel, gtt_map);
 
 			relation_close(idxRel, AccessShareLock);
 		}
@@ -453,16 +478,40 @@ pg_table_size(PG_FUNCTION_ARGS)
 	Oid			relOid = PG_GETARG_OID(0);
 	Relation	rel;
 	int64		size;
+	Bitmapset	*gtt_map = NULL;
 
 	rel = try_relation_open(relOid, AccessShareLock);
 
 	if (rel == NULL)
 		PG_RETURN_NULL();
 
-	size = calculate_table_size(rel);
+	/* For GTT, calculate the size of the data file in the session where the GTT has been initialized */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (rel->rd_rel->relkind == RELKIND_INDEX)
+		{
+			Relation	relation;
+			Oid			relid;
+
+			relid = IndexGetRelation(RelationGetRelid(rel), false);
+			relation = try_relation_open(relid, AccessShareLock);
+			if (relation)
+			{
+				gtt_map = copy_active_gtt_bitmap(relation->rd_node);
+				relation_close(relation, AccessShareLock);
+			}
+		}
+		else
+			gtt_map = copy_active_gtt_bitmap(rel->rd_node);
+	}
+
+	size = calculate_table_size(rel, gtt_map);
 
 	relation_close(rel, AccessShareLock);
 
+	if(gtt_map)
+		pfree(gtt_map);
+
 	PG_RETURN_INT64(size);
 }
 
@@ -472,16 +521,24 @@ pg_indexes_size(PG_FUNCTION_ARGS)
 	Oid			relOid = PG_GETARG_OID(0);
 	Relation	rel;
 	int64		size;
+	Bitmapset	*gtt_map = NULL;
 
 	rel = try_relation_open(relOid, AccessShareLock);
 
 	if (rel == NULL)
 		PG_RETURN_NULL();
 
-	size = calculate_indexes_size(rel);
+	/* For GTT, calculate the size of the data file in the session where the GTT has been initialized */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		gtt_map = copy_active_gtt_bitmap(rel->rd_node);
+
+	size = calculate_indexes_size(rel, gtt_map);
 
 	relation_close(rel, AccessShareLock);
 
+	if (gtt_map)
+		pfree(gtt_map);
+
 	PG_RETURN_INT64(size);
 }
 
@@ -493,17 +550,25 @@ static int64
 calculate_total_relation_size(Relation rel)
 {
 	int64		size;
+	Bitmapset	*gtt_map = NULL;
+
+	/* For GTT, calculate the size of the data file in the session where the GTT has been initialized */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		gtt_map = copy_active_gtt_bitmap(rel->rd_node);
 
 	/*
 	 * Aggregate the table size, this includes size of the heap, toast and
 	 * toast index with free space and visibility map
 	 */
-	size = calculate_table_size(rel);
+	size = calculate_table_size(rel, gtt_map);
 
 	/*
 	 * Add size of all attached indexes as well
 	 */
-	size += calculate_indexes_size(rel);
+	size += calculate_indexes_size(rel, gtt_map);
+
+	if (gtt_map)
+		pfree(gtt_map);
 
 	return size;
 }
@@ -1008,6 +1073,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 7c6f057..589bd38 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -142,6 +142,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;
@@ -4578,12 +4579,25 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -4708,15 +4722,27 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6098,6 +6124,7 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	{
 		/* Simple variable --- look to stats for the underlying table */
 		RangeTblEntry *rte = planner_rt_fetch(index->rel->relid, root);
+		char	rel_persistence = get_rel_persistence(rte->relid);
 
 		Assert(rte->rtekind == RTE_RELATION);
 		relid = rte->relid;
@@ -6115,6 +6142,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6126,6 +6160,8 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	}
 	else
 	{
+		char	rel_persistence = get_rel_persistence(index->indexoid);
+
 		/* Expression --- maybe there are stats for the index itself */
 		relid = index->indexoid;
 		colnum = 1;
@@ -6141,6 +6177,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7054,6 +7097,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		/* attempt to lookup stats in relation for this index column */
 		if (attnum != 0)
 		{
+			char	rel_persistence = get_rel_persistence(rte->relid);
+
 			/* Simple variable -- look to stats for the underlying table */
 			if (get_relation_stats_hook &&
 				(*get_relation_stats_hook) (root, rte, attnum, &vardata))
@@ -7066,6 +7111,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7078,6 +7131,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		}
 		else
 		{
+			char	rel_persistence = get_rel_persistence(index->indexoid);
+
 			/*
 			 * Looks like we've found an expression column in the index. Let's
 			 * see if there's any stats for it.
@@ -7097,6 +7152,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 1e3e6d3..44686ce 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 df025a5..1c30d86 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1124,6 +1124,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);
@@ -3313,6 +3323,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;
@@ -3427,6 +3441,9 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
 
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+		elog(ERROR, "global temp table does not allow setting new relfilenode");
+
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
@@ -3467,7 +3484,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 e44f71e..44c86c8 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -139,6 +139,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,
@@ -1992,6 +2004,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 799b698..b98d396 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15583,6 +15583,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	{
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
+		char		*table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15634,9 +15635,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 			binary_upgrade_set_pg_class_oids(fout, q,
 											 tbinfo->dobj.catId.oid, false);
 
+		if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED)
+			table_type = "UNLOGGED ";
+		else if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			table_type = "GLOBAL TEMPORARY ";
+		else
+			table_type = "";
+
 		appendPQExpBuffer(q, "CREATE %s%s %s",
-						  tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ?
-						  "UNLOGGED " : "",
+						  table_type,
 						  reltypename,
 						  qualrelname);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index f3c7eb9..28134e2 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3723,7 +3723,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index a12fc1f..78958e4 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -165,6 +165,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index fcf2a12..ba0bd5f 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5484,6 +5484,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4191',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4192',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{relid,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4193',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4194',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 048003c..af48cdf 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -19,7 +19,7 @@
 #include "storage/smgr.h"
 #include "utils/relcache.h"
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationTruncate(Relation rel, BlockNumber nblocks);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..eacc214
--- /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 bool 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 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 870ecb5..92c590e 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 8fda8e4..172eae9 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_TBM,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_SXACT,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 5b407e6..f13250a 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -117,6 +117,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId session_gtt_frozenxid;	/* session level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ce93ace..0f7262e 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -281,6 +281,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 44ed04d..3f9720d 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -277,6 +277,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -535,7 +536,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
@@ -602,6 +604,17 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..50ca9ac
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,7 @@
+reset search_path;
+drop schema gtt cascade;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..2c69f14
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,197 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+ERROR:  cannot create indexes on global temporary tables using concurrent mode
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+ERROR:  not support cluster global temporary tables yet
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ok
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  regular table cannot specifie on_commit_delete_rows
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+);
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp);
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  can not defeine global temp table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test;
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  referenced relation "products" is not a global temp table
+-- ERROR
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  referenced relation "products" is not a global temp table
+-- ERROR
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ALL ERROR
+create index idx_err on gtt1 using hash (a);
+ERROR:  only support btree index on global temp table
+create index idx_err on gtt1 using gist (a);
+ERROR:  data type integer has no default operator class for access method "gist"
+HINT:  You must specify an operator class for the index or define a default operator class for the data type.
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+ERROR:  only support btree index on global temp table
+create index idx_tmp_t0_1 on tmp_t0 using gist (c0);
+ERROR:  only support btree index on global temp table
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 15 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..30d8a7b
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,84 @@
+set search_path=gtt,sys;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..850ef3e
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,161 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          475136 |                 974848
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |           409600 |        409600 |                 409600
+(2 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..9735a93
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,9 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text);
+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;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..b258b7c
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,76 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+(1 row)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+(1 row)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 70e1e2f..30cf4bd 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1348,6 +1348,93 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d33a4e1..643afe4 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..f3cf710
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,6 @@
+
+
+reset search_path;
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..49c3f11
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,163 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ok
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+);
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp);
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test;
+
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ERROR
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ERROR
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ALL ERROR
+create index idx_err on gtt1 using hash (a);
+create index idx_err on gtt1 using gist (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_1 on tmp_t0 using gist (c0);
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d7d81de
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,42 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..5203c2b
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,76 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..1e4cf27
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,16 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+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;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..f041892
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,42 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
#72Pavel Stehule
pavel.stehule@gmail.com
In reply to: 曾文旌(义从) (#71)
Re: [Proposal] Global temporary tables

Hi

I have a free time this evening, so I will check this patch

I have a one question

+ /* 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;
+ }

Isbn't possible to fill the rd_rel structure too, so this branching can be
reduced?

Regards

Pavel

po 20. 1. 2020 v 17:27 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
napsal:

Show quoted text

2020年1月20日 上午1:32,Erik Rijkers <er@xs4all.nl> 写道:

On 2020-01-19 18:04, 曾文旌(义从) wrote:

2020年1月14日 下午9:20,Pavel Stehule <pavel.stehule@gmail.com> 写道:
út 14. 1. 2020 v 14:09 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com

<mailto:wenjing.zwj@alibaba-inc.com>> napsal:

[global_temporary_table_v4-pg13.patch ]

Hi,

This patch doesn't quiet apply for me:

patching file src/backend/access/common/reloptions.c
patching file src/backend/access/gist/gistutil.c
patching file src/backend/access/hash/hash.c
Hunk #1 succeeded at 149 (offset 3 lines).
patching file src/backend/access/heap/heapam_handler.c
patching file src/backend/access/heap/vacuumlazy.c
patching file src/backend/access/nbtree/nbtpage.c
patching file src/backend/access/table/tableam.c
patching file src/backend/access/transam/xlog.c
patching file src/backend/catalog/Makefile
Hunk #1 FAILED at 44.
1 out of 1 hunk FAILED -- saving rejects to file

src/backend/catalog/Makefile.rej

[...]
(The rest applies without errors)

src/backend/catalog/Makefile.rej contains:

------------------------
--- src/backend/catalog/Makefile
+++ 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
------------------------

Can you have a look?

I updated the code and remade the patch.
Please give me feedback if you have any more questions.

thanks,

Erik Rijkers

#73曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Pavel Stehule (#56)
1 attachment(s)
Re: [Proposal] Global temporary tables

2020年1月12日 上午4:27,Pavel Stehule <pavel.stehule@gmail.com> 写道:

Hi

so 11. 1. 2020 v 15:00 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:
Hi all

This is the latest patch

The updates are as follows:
1. Support global temp Inherit table global temp partition table
2. Support serial column in GTT
3. Provide views pg_gtt_relstats pg_gtt_stats for GTT’s statistics
4. Provide view pg_gtt_attached_pids to manage GTT
5. Provide function pg_list_gtt_relfrozenxids() to manage GTT
6. Alter GTT or rename GTT is allowed under some conditions

Please give me feedback.

I tested the functionality

1. i think so "ON COMMIT PRESERVE ROWS" should be default mode (like local temp tables).

ON COMMIT PRESERVE ROWS is default mode now.

Wenjing

Show quoted text

I tested some simple scripts

test01.sql

CREATE TEMP TABLE foo(a int, b int);
INSERT INTO foo SELECT random()*100, random()*1000 FROM generate_series(1,1000);
ANALYZE foo;
SELECT sum(a), sum(b) FROM foo;
DROP TABLE foo; -- simulate disconnect

after 100 sec, the table pg_attribute has 3.2MB
and 64 tps, 6446 transaction

test02.sql

INSERT INTO foo SELECT random()*100, random()*1000 FROM generate_series(1,1000);
ANALYZE foo;
SELECT sum(a), sum(b) FROM foo;
DELETE FROM foo; -- simulate disconnect

after 100 sec, 1688 tps, 168830 transactions

So performance is absolutely different as we expected.

From my perspective, this functionality is great.

Todo:

pg_table_size function doesn't work

Regards

Pavel

Wenjing

2020年1月6日 上午4:06,Tomas Vondra <tomas.vondra@2ndquadrant.com <mailto:tomas.vondra@2ndquadrant.com>> 写道:

Hi,

I think we need to do something with having two patches aiming to add
global temporary tables:

[1] https://commitfest.postgresql.org/26/2349/ <https://commitfest.postgresql.org/26/2349/&gt;

[2] https://commitfest.postgresql.org/26/2233/ <https://commitfest.postgresql.org/26/2233/&gt;

As a reviewer I have no idea which of the threads to look at - certainly
not without reading both threads, which I doubt anyone will really do.
The reviews and discussions are somewhat intermixed between those two
threads, which makes it even more confusing.

I think we should agree on a minimal patch combining the necessary/good
bits from the various patches, and terminate one of the threads (i.e.
mark it as rejected or RWF). And we need to do that now, otherwise
there's about 0% chance of getting this into v13.

In general, I agree with the sentiment Rober expressed in [1] - the
patch needs to be as small as possible, not adding "nice to have"
features (like support for parallel queries - I very much doubt just
using shared instead of local buffers is enough to make it work.)

regards

--
Tomas Vondra http://www.2ndQuadrant.com <http://www.2ndquadrant.com/&gt;
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

global_temporary_table_v6-pg13.patchapplication/octet-stream; name=global_temporary_table_v6-pg13.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 79430d2..b7173c7 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 | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},	
 	/* list terminator */
 	{{NULL}}
 };
@@ -1486,6 +1499,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1586,13 +1601,18 @@ build_reloptions(Datum reloptions, bool validate,
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	/*
 	 * There are no options for partitioned tables yet, but this is able to do
 	 * some validation.
 	 */
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index dd975b1..1610e7d 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1013,7 +1013,9 @@ gistGetFakeLSN(Relation rel)
 {
 	static XLogRecPtr counter = FirstNormalUnloggedLSN;
 
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	/* global temp is same as local temp table */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 4871b7f..16b00c9 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -149,7 +149,9 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	/* global temp table is same as local temp table */
+	if (!(index->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		index->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 1f6f6d0..5727ccd 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -598,7 +598,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -651,7 +651,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index b331f4c..5bd681f 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -79,6 +79,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?
@@ -397,8 +398,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)
@@ -457,8 +460,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 f05cbe7..946c9d2 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -763,7 +763,14 @@ _bt_getbuf(Relation rel, BlockNumber blkno, int access)
 		/* Read an existing block of the relation */
 		buf = ReadBuffer(rel, blkno);
 		LockBuffer(buf, access);
-		_bt_checkpage(rel, buf);
+
+		/* global temp table may be not yet initialized for this backend. */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			blkno == BTREE_METAPAGE &&
+			PageIsNew(BufferGetPage(buf)))
+			_bt_initmetapage(BufferGetPage(buf), P_NONE, 0);
+		else
+			_bt_checkpage(rel, buf);
 	}
 	else
 	{
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index c814733..ff51840 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -24,6 +24,7 @@
 #include "access/heapam.h"		/* for ss_* */
 #include "access/tableam.h"
 #include "access/xact.h"
+#include "catalog/storage_gtt.h"
 #include "optimizer/plancat.h"
 #include "storage/bufmgr.h"
 #include "storage/shmem.h"
@@ -560,10 +561,20 @@ table_block_relation_estimate_size(Relation rel, int32 *attr_widths,
 	/* it should have storage, so we can 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;
+	/* 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;
+	}
 
 	/*
 	 * HACK: if the relation has never yet been vacuumed, use a minimum size
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 7f4f784..aba8a9f 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6327,6 +6327,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 d5da81c..c753b0e 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -42,6 +42,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 16cb6d8..f28f2c2 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -398,7 +398,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/* global temp table is same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 0fdff29..54a5243 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -404,6 +406,10 @@ heap_create(const char *relname,
 									 relpersistence,
 									 relkind);
 
+	/* global temp table not create storage file when catalog create */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		create_storage = false;
+
 	/*
 	 * Have the storage manager create the relation's disk file, if needed.
 	 *
@@ -427,7 +433,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -956,6 +962,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -994,8 +1001,18 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/* global temp table not remember transaction info in catalog */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1357,6 +1374,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1441,11 +1459,15 @@ heap_create_with_catalog(const char *relname,
 	 */
 	StoreConstraints(new_rel_desc, cooked_constraints, is_internal);
 
-	/*
-	 * If there's a special on-commit action, remember it
-	 */
-	if (oncommit != ONCOMMIT_NOOP)
-		register_on_commit_action(relid, oncommit);
+	/* global temp table register action when storage init */
+	if (relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		/*
+		 * If there's a special on-commit action, remember it
+		 */
+		if (oncommit != ONCOMMIT_NOOP)
+			register_on_commit_action(relid, oncommit);
+	}
 
 	/*
 	 * ok, the relation has been cataloged, so close our relations and return
@@ -1937,6 +1959,13 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/* We allow to drop global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(rel->rd_node))
+			elog(ERROR, "can not drop relation when other backend attached this global temp table");
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3163,9 +3192,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;
 
@@ -3177,7 +3207,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3223,8 +3253,13 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		/* truncate global temp table only need RowExclusiveLock */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3257,6 +3292,8 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
+	bool		truncate_toastrel = false;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3265,23 +3302,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 3e59e64..9867d6f 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -52,6 +52,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -877,6 +878,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))
+			elog(ERROR, "can not create index when have other 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 */
+		if (!gtt_storage_attached(heapRelation->rd_node.relNode))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -2045,6 +2066,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.
@@ -2736,20 +2764,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;
+			}
 		}
 	}
 
@@ -2864,6 +2901,12 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, index, val);
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		if (!gtt_storage_attached(indexRelation->rd_node.relNode))
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3196,12 +3239,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;
 
 	/*
@@ -3459,6 +3512,15 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 				 errmsg("cannot reindex temporary tables of other sessions")));
 
 	/*
+	 * Because global temp table cannot change relfilenode
+	 * no support reindex on global temp table yet.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(iRel))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot reindex global temporary tables")));
+
+	/*
 	 * Also check for active uses of the index in the current transaction; we
 	 * don't want to reindex underneath an open indexscan.
 	 */
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index e70243a..301da79 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -647,6 +647,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		/* global temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp relations in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index fddfbf1..03806ae 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -26,6 +26,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -73,9 +74,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;
@@ -85,6 +87,8 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		/* global temp table use same storage strategy as local temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -117,6 +121,10 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/* remember global temp table storage info to localhash */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel)
+		remember_gtt_storage_info(rnode, rel);
+
 	return srel;
 }
 
@@ -486,8 +494,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..f489cd3
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1129 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  code to create and destroy physical storage for global temparary table
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/table.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_relstats_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+typedef struct gtt_ctl_data
+{
+	LWLock			lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct
+{
+	RelFileNode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+	Oid			relid;
+
+	Oid			spcnode;
+	/* pg_class stat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+	char		relkind;
+	bool		on_commit_delete;
+
+	/* pg_statistic */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(RelFileNode rnode);
+static void gtt_storage_checkout(RelFileNode rnode, bool skiplock);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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 || entry->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		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;
+		int			i;
+
+		rnode.spcNode = entry->spcnode;
+		rnode.dbNode = MyDatabaseId;
+		rnode.relNode = entry->relid;
+
+		gtt_storage_checkout(rnode, false);
+
+		Assert(TransactionIdIsNormal(entry->relfrozenxid));
+		remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+
+		for (i = 0; i < entry->natts; i++)
+		{
+			if (entry->att_stat_tups[i])
+			{
+				heap_freetuple(entry->att_stat_tups[i]);
+				entry->att_stat_tups[i] = NULL;
+			}
+		}
+	}
+
+	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.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (gtt_storage_local_hash == NULL)
+		return false;
+
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_FIND, &found);
+
+	if (!found)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	if (relpages)
+		*relpages = entry->relpages;
+
+	if (reltuples)
+		*reltuples = entry->reltuples;
+
+	if (relallvisible)
+		*relallvisible = entry->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = entry->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = entry->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	MemoryContext			oldcontext;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+						(void *) &reloid, HASH_FIND, &found);
+
+	if (!found)
+		return;
+
+	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;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	HeapTuple	tuple;
+	Relation	rel = NULL;
+	int		attnum = PG_GETARG_INT32(1);
+	Oid		reloid = PG_GETARG_OID(0);
+	char		rel_persistence;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	  tupdesc;
+	MemoryContext oldcontext;
+	Tuplestorestate *tupstore;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = table_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+
+	table_close(rel, NoLock);
+	table_close(pg_tatistic, AccessShareLock);
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate	*tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Relation	rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	if (get_gtt_relstats(reloid,
+						&relpages, &reltuples, &relallvisible,
+						&relfrozenxid, &relminmxid))
+	{
+		Datum	values[5];
+		bool	isnull[5];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = Int32GetDatum(relpages);
+		values[1] = Float4GetDatum((float4)reltuples);
+		values[2] = Int32GetDatum(relallvisible);
+		values[3] = UInt32GetDatum(relfrozenxid);
+		values[4] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+
+	tuplestore_donestoring(tupstore);
+	table_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(rel->rd_node);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	table_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index c9e75f4..2adde62 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM 
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c4420dd..a789718 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -66,6 +66,7 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+#include "catalog/storage_gtt.h"
 
 /* Per-index data for ANALYZE */
 typedef struct AnlIndexData
@@ -103,7 +104,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -586,14 +587,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 		 * pg_statistic for columns we didn't process, we leave them alone.)
 		 */
 		update_attstats(RelationGetRelid(onerel), inh,
-						attr_cnt, vacattrstats);
+						attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
 
 		for (ind = 0; ind < nindexes; ind++)
 		{
 			AnlIndexData *thisdata = &indexdata[ind];
 
 			update_attstats(RelationGetRelid(Irel[ind]), false,
-							thisdata->attr_cnt, thisdata->vacattrstats);
+							thisdata->attr_cnt, thisdata->vacattrstats,
+							RelationGetRelPersistence(Irel[ind]));
 		}
 
 		/*
@@ -673,11 +675,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);
@@ -1456,7 +1467,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
  *		by taking a self-exclusive lock on the relation in analyze_rel().
  */
 static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
 {
 	Relation	sd;
 	int			attno;
@@ -1558,31 +1569,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 e9d7a7f..3088e26 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -391,6 +391,12 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/* not support cluster global temp table yet */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("not support cluster global temporary tables yet")));
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 52ce02f..3d3f7dc 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2583,6 +2583,16 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 			!isTempNamespace(classtuple->relnamespace))
 			continue;
 
+		/* not support reindex on global temp table, so skip it */
+		if (classtuple->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			ereport(WARNING,
+				(errmsg("global temp table \"%s.%s\" skip reindexed",
+					get_namespace_name(get_rel_namespace(relid)),
+					get_rel_name(relid))));
+			continue;
+		}
+
 		/* Check user/system classification, and optionally skip */
 		if (objectKind == REINDEX_OBJECT_SYSTEM &&
 			!IsSystemClass(relid, classtuple))
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 329ab84..dfa257c 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -96,7 +96,8 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 	 * transaction.
 	 */
 	relpersistence = get_rel_persistence(relid);
-	if (relpersistence == RELPERSISTENCE_TEMP)
+	if (relpersistence == RELPERSISTENCE_TEMP ||
+		relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
 
 	/* Check permissions. */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..44f350d 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -94,7 +94,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +108,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -222,7 +223,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* now initialize the sequence's data */
 	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -327,7 +328,7 @@ ResetSequence(Oid seq_relid)
 	/*
 	 * Insert the modified tuple into the new storage file.
 	 */
-	fill_seq_with_data(seq_rel, tuple);
+	fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -340,17 +341,21 @@ ResetSequence(Oid seq_relid)
  * Initialize a sequence's relation with the specified tuple as content
  */
 static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 	sequence_magic *sm;
 	OffsetNumber offnum;
+	bool lockBuffer = false;
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ReadBuffer(rel, P_NEW);
-	Assert(BufferGetBlockNumber(buf) == 0);
+	if (buf == InvalidBuffer)
+	{
+		buf = ReadBuffer(rel, P_NEW);
+		Assert(BufferGetBlockNumber(buf) == 0);
+		lockBuffer = true;
+	}
 
 	page = BufferGetPage(buf);
 
@@ -360,7 +365,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* Now insert sequence tuple */
 
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+	if (lockBuffer)
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +416,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	END_CRIT_SECTION();
 
-	UnlockReleaseBuffer(buf);
+	if (lockBuffer)
+		UnlockReleaseBuffer(buf);
 }
 
 /*
@@ -502,7 +509,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * Insert the modified tuple into the new storage file.
 		 */
-		fill_seq_with_data(seqrel, newdatatuple);
+		fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
 	}
 
 	/* process OWNED BY if given */
@@ -1178,6 +1185,25 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
 	LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
 
 	page = BufferGetPage(*buf);
+	if (GlobalTempRelationPageIsNotInitialized(rel, page))
+	{
+		/* Initialize sequence for global temporary tables */
+		Datum		value[SEQ_COL_LASTCOL] = {0};
+		bool		null[SEQ_COL_LASTCOL] = {false};
+		HeapTuple	tuple;
+		int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+		/*
+		 * last_value from pg_sequence.seqstart
+		 * log_cnt = 0
+		 * is_called = false
+		 */
+		value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+		tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+		fill_seq_with_data(rel, tuple, *buf);
+		heap_freetuple(tuple);
+	}
 	sm = (sequence_magic *) PageGetSpecialPointer(page);
 
 	if (sm->magic != SEQ_MAGIC)
@@ -1954,3 +1980,23 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 30b72b6..cc5b991 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -552,6 +553,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 								  Relation partitionTbl);
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static bool has_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -597,6 +599,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	bool		has_oncommit_clause = false;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -607,8 +610,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Check consistency of arguments
 	 */
+	/* global temp table same as local temp table */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !(stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+			 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -638,7 +643,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	/* global temp table same as local temp table */
+	if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+		 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -739,6 +746,57 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* global temp table */
+	has_oncommit_clause = has_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* inherit table or parition table inherit on commit property from parent table*/
+		if (inheritOids && stmt->oncommit == ONCOMMIT_NOOP && !has_oncommit_clause)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			if (RELATION_GTT_ON_COMMIT_DELETE(relation))
+				stmt->oncommit = ONCOMMIT_DELETE_ROWS;
+			else
+				stmt->oncommit = ONCOMMIT_PRESERVE_ROWS;
+			table_close(relation, NoLock);
+		}
+
+		if (has_oncommit_clause)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "can not defeine global temp table with on commit and with clause at same time");
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temp table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (has_oncommit_clause)
+		elog(ERROR, "regular table cannot specifie on_commit_delete_rows");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1800,7 +1858,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);
@@ -3359,6 +3418,13 @@ RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal, bo
 	 * specially.
 	 */
 	targetrelation = relation_open(myrelid, is_index ? ShareUpdateExclusiveLock : AccessExclusiveLock);
+
+	if (RELATION_IS_GLOBAL_TEMP(targetrelation))
+	{
+		if (is_other_backend_use_gtt(targetrelation->rd_node))
+			elog(ERROR, "can not rename relation when other backend attached this global temp table");
+	}
+
 	namespaceId = RelationGetNamespace(targetrelation);
 
 	/*
@@ -3534,6 +3600,13 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(rel->rd_node))
+			elog(ERROR, "can not alter relation when other backend attached this global temp table");
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -8099,6 +8172,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),
@@ -8138,6 +8218,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;
 	}
 
 	/*
@@ -13174,7 +13260,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,
@@ -14581,7 +14667,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",
@@ -17221,3 +17309,20 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
 		table_close(rel, NoLock);
 	}
 }
+
+static bool
+has_oncommit_option(List *options)
+{
+	ListCell   *listptr;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (pg_strcasecmp(def->defname, "on_commit_delete_rows") == 0)
+			return true;
+	}
+
+	return false;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index d625d17..db966e8 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"
@@ -1125,12 +1126,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;
@@ -1230,24 +1244,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)
 	{
 		/*
@@ -1272,37 +1270,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. */
@@ -1394,6 +1419,10 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/* global temp table relstats not in pg_class */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1451,6 +1480,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 8286d9c..c3992a4 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -591,6 +591,8 @@ static void
 set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 						  RangeTblEntry *rte)
 {
+	char relpersistence;
+
 	/*
 	 * The flag has previously been initialized to false, so we can just
 	 * return if it becomes clear that we can't safely set it.
@@ -618,7 +620,11 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			/* global temp table is same as local temp table */
+			relpersistence = get_rel_persistence(rte->relid);
+
+			if (relpersistence == RELPERSISTENCE_TEMP ||
+				relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index d6f2153..310a9e2 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6312,7 +6312,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
+	/* global temp table is same as local temp table */
 	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index d82fc5a..189286b 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -31,6 +31,7 @@
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -53,6 +54,7 @@
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
 
+
 /* GUC parameter */
 int			constraint_exclusion = CONSTRAINT_EXCLUSION_PARTITION;
 
@@ -946,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)
@@ -963,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.
@@ -971,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 */
@@ -985,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 748bebf..a5ddbcd 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2579,6 +2579,11 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("materialized views must not use temporary tables or views")));
 
+		if (is_query_using_gtt(query))
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("materialized views must not use global temporary tables or views")));
+
 		/*
 		 * A materialized view would either need to save parameters for use in
 		 * maintaining/loading the data or prohibit them entirely.  The latter
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ba5916b..0ee6931 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3290,17 +3290,11 @@ OptTemp:	TEMPORARY					{ $$ = RELPERSISTENCE_TEMP; }
 			| LOCAL TEMP				{ $$ = RELPERSISTENCE_TEMP; }
 			| GLOBAL TEMPORARY
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
-					$$ = RELPERSISTENCE_TEMP;
+					$$ = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
-					$$ = RELPERSISTENCE_TEMP;
+					$$ = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED					{ $$ = RELPERSISTENCE_UNLOGGED; }
 			| /*EMPTY*/					{ $$ = RELPERSISTENCE_PERMANENT; }
@@ -11588,19 +11582,13 @@ OptTempTableName:
 				}
 			| GLOBAL TEMPORARY opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED opt_table qualified_name
 				{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b875a50..123945b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index ee2d2b5..9c9abaa 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 6d1f28c..ed837d1 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2086,6 +2086,11 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* autovacuum skip vacuum global temp table */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2152,7 +2157,9 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		/* autovacuum skip vacuum global temp table */
+		if (classForm->relpersistence == RELPERSISTENCE_TEMP || 
+			classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index aba3960..68adcd5 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,
@@ -2809,6 +2822,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(relation->rd_node.relNode))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d5..7adb969 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -21,6 +21,7 @@
 #include "access/nbtree.h"
 #include "access/subtrans.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, active_gtt_shared_hash_size());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* global temporary table */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index c3adb2e..eb95e5f 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -62,6 +62,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -4087,3 +4088,77 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temp table.
+ */
+int
+list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct *arrayP = procArray;
+	TransactionId result = InvalidTransactionId;
+	int			index;
+	int			flags = 0;
+	int			i = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+		*n = 0;
+	}
+
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		volatile PGXACT *pgxact = &allPgXact[pgprocno];
+
+		if (pgxact->vacuumFlags & flags)
+			continue;
+
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->session_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->session_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->session_gtt_frozenxid, result))
+				result = proc->session_gtt_frozenxid;
+
+			if (max_size > 0)
+			{
+				pids[i] = proc->pid;
+				xids[i] = proc->session_gtt_frozenxid;
+				i++;
+			}
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (max_size > 0)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 32df8c8..e4e1125 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c
index 85b7115..f5eae8c 100644
--- a/src/backend/storage/smgr/md.c
+++ b/src/backend/storage/smgr/md.c
@@ -646,6 +646,12 @@ mdread(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
 		 */
 		if (zero_damaged_pages || InRecovery)
 			MemSet(buffer, 0, BLCKSZ);
+		else if(SmgrIsTemp(reln) && blocknum == 0 && forknum == MAIN_FORKNUM)
+		{
+			/* global temp table init btree meta page */
+			MemSet(buffer, 0, BLCKSZ);
+			mdwrite(reln, forknum, blocknum, buffer, true);
+		}
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_DATA_CORRUPTED),
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 8406644..92f028e 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -19,6 +19,8 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_tablespace.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/index.h"
 #include "commands/dbcommands.h"
 #include "commands/tablespace.h"
 #include "miscadmin.h"
@@ -262,7 +264,6 @@ pg_tablespace_size_name(PG_FUNCTION_ARGS)
 	PG_RETURN_INT64(size);
 }
 
-
 /*
  * calculate size of (one fork of) a relation
  *
@@ -307,6 +308,41 @@ calculate_relation_size(RelFileNode *rfn, BackendId backend, ForkNumber forknum)
 	return totalsize;
 }
 
+static int64
+calculate_relation_size_all_file(Relation rel, const Bitmapset *gtt_map)
+{
+	int64		size = 0;
+	ForkNumber	forkNum;
+	BackendId	backendid;
+
+	/* For global temp table */
+	if (gtt_map)
+	{
+		Bitmapset *map_copy = bms_copy(gtt_map);
+
+		backendid = bms_first_member(map_copy);
+
+		do
+		{
+			for (forkNum = 0; forkNum <= MAX_FORKNUM; forkNum++)
+				size += calculate_relation_size(&(rel->rd_node),
+										backendid,
+										forkNum);
+			backendid = bms_next_member(map_copy, backendid);
+		} while (backendid > 0);
+
+		pfree(map_copy);
+	}
+	else
+	{
+		for (forkNum = 0; forkNum <= MAX_FORKNUM; forkNum++)
+			size += calculate_relation_size(&(rel->rd_node),
+											rel->rd_backend, forkNum);
+	}
+
+	return size;
+}
+
 Datum
 pg_relation_size(PG_FUNCTION_ARGS)
 {
@@ -340,20 +376,17 @@ pg_relation_size(PG_FUNCTION_ARGS)
  * Must not be applied to non-TOAST relations.
  */
 static int64
-calculate_toast_table_size(Oid toastrelid)
+calculate_toast_table_size(Oid toastrelid, Bitmapset *gtt_map)
 {
 	int64		size = 0;
 	Relation	toastRel;
-	ForkNumber	forkNum;
 	ListCell   *lc;
 	List	   *indexlist;
 
 	toastRel = relation_open(toastrelid, AccessShareLock);
 
 	/* toast heap size, including FSM and VM size */
-	for (forkNum = 0; forkNum <= MAX_FORKNUM; forkNum++)
-		size += calculate_relation_size(&(toastRel->rd_node),
-										toastRel->rd_backend, forkNum);
+	size += calculate_relation_size_all_file(toastRel, gtt_map);
 
 	/* toast index size, including FSM and VM size */
 	indexlist = RelationGetIndexList(toastRel);
@@ -365,9 +398,8 @@ calculate_toast_table_size(Oid toastrelid)
 
 		toastIdxRel = relation_open(lfirst_oid(lc),
 									AccessShareLock);
-		for (forkNum = 0; forkNum <= MAX_FORKNUM; forkNum++)
-			size += calculate_relation_size(&(toastIdxRel->rd_node),
-											toastIdxRel->rd_backend, forkNum);
+
+		size += calculate_relation_size_all_file(toastIdxRel, gtt_map);
 
 		relation_close(toastIdxRel, AccessShareLock);
 	}
@@ -386,23 +418,20 @@ calculate_toast_table_size(Oid toastrelid)
  * those won't have attached toast tables, but they can have multiple forks.
  */
 static int64
-calculate_table_size(Relation rel)
+calculate_table_size(Relation rel, Bitmapset *gtt_map)
 {
 	int64		size = 0;
-	ForkNumber	forkNum;
 
 	/*
 	 * heap size, including FSM and VM
 	 */
-	for (forkNum = 0; forkNum <= MAX_FORKNUM; forkNum++)
-		size += calculate_relation_size(&(rel->rd_node), rel->rd_backend,
-										forkNum);
+	size += calculate_relation_size_all_file(rel, gtt_map);
 
 	/*
 	 * Size of toast relation
 	 */
 	if (OidIsValid(rel->rd_rel->reltoastrelid))
-		size += calculate_toast_table_size(rel->rd_rel->reltoastrelid);
+		size += calculate_toast_table_size(rel->rd_rel->reltoastrelid, gtt_map);
 
 	return size;
 }
@@ -413,7 +442,7 @@ calculate_table_size(Relation rel)
  * Can be applied safely to an index, but you'll just get zero.
  */
 static int64
-calculate_indexes_size(Relation rel)
+calculate_indexes_size(Relation rel, Bitmapset *gtt_map)
 {
 	int64		size = 0;
 
@@ -429,14 +458,10 @@ calculate_indexes_size(Relation rel)
 		{
 			Oid			idxOid = lfirst_oid(cell);
 			Relation	idxRel;
-			ForkNumber	forkNum;
 
 			idxRel = relation_open(idxOid, AccessShareLock);
 
-			for (forkNum = 0; forkNum <= MAX_FORKNUM; forkNum++)
-				size += calculate_relation_size(&(idxRel->rd_node),
-												idxRel->rd_backend,
-												forkNum);
+			size += calculate_relation_size_all_file(idxRel, gtt_map);
 
 			relation_close(idxRel, AccessShareLock);
 		}
@@ -453,16 +478,40 @@ pg_table_size(PG_FUNCTION_ARGS)
 	Oid			relOid = PG_GETARG_OID(0);
 	Relation	rel;
 	int64		size;
+	Bitmapset	*gtt_map = NULL;
 
 	rel = try_relation_open(relOid, AccessShareLock);
 
 	if (rel == NULL)
 		PG_RETURN_NULL();
 
-	size = calculate_table_size(rel);
+	/* For GTT, calculate the size of the data file in the session where the GTT has been initialized */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (rel->rd_rel->relkind == RELKIND_INDEX)
+		{
+			Relation	relation;
+			Oid			relid;
+
+			relid = IndexGetRelation(RelationGetRelid(rel), false);
+			relation = try_relation_open(relid, AccessShareLock);
+			if (relation)
+			{
+				gtt_map = copy_active_gtt_bitmap(relation->rd_node);
+				relation_close(relation, AccessShareLock);
+			}
+		}
+		else
+			gtt_map = copy_active_gtt_bitmap(rel->rd_node);
+	}
+
+	size = calculate_table_size(rel, gtt_map);
 
 	relation_close(rel, AccessShareLock);
 
+	if(gtt_map)
+		pfree(gtt_map);
+
 	PG_RETURN_INT64(size);
 }
 
@@ -472,16 +521,24 @@ pg_indexes_size(PG_FUNCTION_ARGS)
 	Oid			relOid = PG_GETARG_OID(0);
 	Relation	rel;
 	int64		size;
+	Bitmapset	*gtt_map = NULL;
 
 	rel = try_relation_open(relOid, AccessShareLock);
 
 	if (rel == NULL)
 		PG_RETURN_NULL();
 
-	size = calculate_indexes_size(rel);
+	/* For GTT, calculate the size of the data file in the session where the GTT has been initialized */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		gtt_map = copy_active_gtt_bitmap(rel->rd_node);
+
+	size = calculate_indexes_size(rel, gtt_map);
 
 	relation_close(rel, AccessShareLock);
 
+	if (gtt_map)
+		pfree(gtt_map);
+
 	PG_RETURN_INT64(size);
 }
 
@@ -493,17 +550,25 @@ static int64
 calculate_total_relation_size(Relation rel)
 {
 	int64		size;
+	Bitmapset	*gtt_map = NULL;
+
+	/* For GTT, calculate the size of the data file in the session where the GTT has been initialized */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		gtt_map = copy_active_gtt_bitmap(rel->rd_node);
 
 	/*
 	 * Aggregate the table size, this includes size of the heap, toast and
 	 * toast index with free space and visibility map
 	 */
-	size = calculate_table_size(rel);
+	size = calculate_table_size(rel, gtt_map);
 
 	/*
 	 * Add size of all attached indexes as well
 	 */
-	size += calculate_indexes_size(rel);
+	size += calculate_indexes_size(rel, gtt_map);
+
+	if (gtt_map)
+		pfree(gtt_map);
 
 	return size;
 }
@@ -1008,6 +1073,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 7c6f057..589bd38 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -142,6 +142,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;
@@ -4578,12 +4579,25 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -4708,15 +4722,27 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6098,6 +6124,7 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	{
 		/* Simple variable --- look to stats for the underlying table */
 		RangeTblEntry *rte = planner_rt_fetch(index->rel->relid, root);
+		char	rel_persistence = get_rel_persistence(rte->relid);
 
 		Assert(rte->rtekind == RTE_RELATION);
 		relid = rte->relid;
@@ -6115,6 +6142,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6126,6 +6160,8 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	}
 	else
 	{
+		char	rel_persistence = get_rel_persistence(index->indexoid);
+
 		/* Expression --- maybe there are stats for the index itself */
 		relid = index->indexoid;
 		colnum = 1;
@@ -6141,6 +6177,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7054,6 +7097,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		/* attempt to lookup stats in relation for this index column */
 		if (attnum != 0)
 		{
+			char	rel_persistence = get_rel_persistence(rte->relid);
+
 			/* Simple variable -- look to stats for the underlying table */
 			if (get_relation_stats_hook &&
 				(*get_relation_stats_hook) (root, rte, attnum, &vardata))
@@ -7066,6 +7111,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7078,6 +7131,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		}
 		else
 		{
+			char	rel_persistence = get_rel_persistence(index->indexoid);
+
 			/*
 			 * Looks like we've found an expression column in the index. Let's
 			 * see if there's any stats for it.
@@ -7097,6 +7152,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 1e3e6d3..44686ce 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 df025a5..1c30d86 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1124,6 +1124,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);
@@ -3313,6 +3323,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;
@@ -3427,6 +3441,9 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
 
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+		elog(ERROR, "global temp table does not allow setting new relfilenode");
+
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
@@ -3467,7 +3484,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 e44f71e..44c86c8 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -139,6 +139,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,
@@ -1992,6 +2004,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 799b698..b98d396 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15583,6 +15583,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	{
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
+		char		*table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15634,9 +15635,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 			binary_upgrade_set_pg_class_oids(fout, q,
 											 tbinfo->dobj.catId.oid, false);
 
+		if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED)
+			table_type = "UNLOGGED ";
+		else if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			table_type = "GLOBAL TEMPORARY ";
+		else
+			table_type = "";
+
 		appendPQExpBuffer(q, "CREATE %s%s %s",
-						  tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ?
-						  "UNLOGGED " : "",
+						  table_type,
 						  reltypename,
 						  qualrelname);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index f3c7eb9..28134e2 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3723,7 +3723,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index a12fc1f..78958e4 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -165,6 +165,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index fcf2a12..ba0bd5f 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5484,6 +5484,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4191',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4192',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{relid,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4193',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4194',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 048003c..af48cdf 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -19,7 +19,7 @@
 #include "storage/smgr.h"
 #include "utils/relcache.h"
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationTruncate(Relation rel, BlockNumber nblocks);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..eacc214
--- /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 bool 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 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 870ecb5..92c590e 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 8fda8e4..172eae9 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_TBM,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_SXACT,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 5b407e6..f13250a 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -117,6 +117,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId session_gtt_frozenxid;	/* session level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ce93ace..0f7262e 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -281,6 +281,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 44ed04d..3f9720d 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -277,6 +277,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -535,7 +536,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
@@ -602,6 +604,17 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..50ca9ac
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,7 @@
+reset search_path;
+drop schema gtt cascade;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..a477917
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,197 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+ERROR:  cannot create indexes on global temporary tables using concurrent mode
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+ERROR:  not support cluster global temporary tables yet
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ok
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  regular table cannot specifie on_commit_delete_rows
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  can not defeine global temp table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test;
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  referenced relation "products" is not a global temp table
+-- ERROR
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  referenced relation "products" is not a global temp table
+-- ERROR
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ALL ERROR
+create index idx_err on gtt1 using hash (a);
+ERROR:  only support btree index on global temp table
+create index idx_err on gtt1 using gist (a);
+ERROR:  data type integer has no default operator class for access method "gist"
+HINT:  You must specify an operator class for the index or define a default operator class for the data type.
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+ERROR:  only support btree index on global temp table
+create index idx_tmp_t0_1 on tmp_t0 using gist (c0);
+ERROR:  only support btree index on global temp table
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 15 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..30d8a7b
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,84 @@
+set search_path=gtt,sys;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..850ef3e
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,161 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          475136 |                 974848
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |           409600 |        409600 |                 409600
+(2 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..9fe5fd4
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,9 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..b258b7c
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,76 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+(1 row)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+(1 row)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 70e1e2f..30cf4bd 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1348,6 +1348,93 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d33a4e1..643afe4 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..f3cf710
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,6 @@
+
+
+reset search_path;
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..cfc1879
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,163 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ok
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test;
+
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ERROR
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ERROR
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ALL ERROR
+create index idx_err on gtt1 using hash (a);
+create index idx_err on gtt1 using gist (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_1 on tmp_t0 using gist (c0);
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d7d81de
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,42 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..5203c2b
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,76 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..2f4d883
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,16 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..f041892
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,42 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
#74Pavel Stehule
pavel.stehule@gmail.com
In reply to: 曾文旌(义从) (#73)
Re: [Proposal] Global temporary tables

út 21. 1. 2020 v 9:46 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
napsal:

2020年1月12日 上午4:27,Pavel Stehule <pavel.stehule@gmail.com> 写道:

Hi

so 11. 1. 2020 v 15:00 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
napsal:

Hi all

This is the latest patch

The updates are as follows:
1. Support global temp Inherit table global temp partition table
2. Support serial column in GTT
3. Provide views pg_gtt_relstats pg_gtt_stats for GTT’s statistics
4. Provide view pg_gtt_attached_pids to manage GTT
5. Provide function pg_list_gtt_relfrozenxids() to manage GTT
6. Alter GTT or rename GTT is allowed under some conditions

Please give me feedback.

I tested the functionality

1. i think so "ON COMMIT PRESERVE ROWS" should be default mode (like local
temp tables).

ON COMMIT PRESERVE ROWS is default mode now.

Thank you

* I tried to create global temp table with index. When I tried to drop this
table (and this table was used by second instance), then I got error message

postgres=# drop table foo;
ERROR: can not drop index when other backend attached this global temp
table

It is expected, but it is not too much user friendly. Is better to check if
you can drop table, then lock it, and then drop all objects.

* tab complete can be nice for CREATE GLOBAL TEMP table

\dt+ \di+ doesn't work correctly, or maybe I don't understand to the
implementation.

I see same size in all sessions. Global temp tables shares same files?

Regards

Pavel

Show quoted text

Wenjing

I tested some simple scripts

test01.sql

CREATE TEMP TABLE foo(a int, b int);
INSERT INTO foo SELECT random()*100, random()*1000 FROM
generate_series(1,1000);
ANALYZE foo;
SELECT sum(a), sum(b) FROM foo;
DROP TABLE foo; -- simulate disconnect

after 100 sec, the table pg_attribute has 3.2MB
and 64 tps, 6446 transaction

test02.sql

INSERT INTO foo SELECT random()*100, random()*1000 FROM
generate_series(1,1000);
ANALYZE foo;
SELECT sum(a), sum(b) FROM foo;
DELETE FROM foo; -- simulate disconnect

after 100 sec, 1688 tps, 168830 transactions

So performance is absolutely different as we expected.

From my perspective, this functionality is great.

Todo:

pg_table_size function doesn't work

Regards

Pavel

Wenjing

2020年1月6日 上午4:06,Tomas Vondra <tomas.vondra@2ndquadrant.com> 写道:

Hi,

I think we need to do something with having two patches aiming to add
global temporary tables:

[1] https://commitfest.postgresql.org/26/2349/

[2] https://commitfest.postgresql.org/26/2233/

As a reviewer I have no idea which of the threads to look at - certainly
not without reading both threads, which I doubt anyone will really do.
The reviews and discussions are somewhat intermixed between those two
threads, which makes it even more confusing.

I think we should agree on a minimal patch combining the necessary/good
bits from the various patches, and terminate one of the threads (i.e.
mark it as rejected or RWF). And we need to do that now, otherwise
there's about 0% chance of getting this into v13.

In general, I agree with the sentiment Rober expressed in [1] - the
patch needs to be as small as possible, not adding "nice to have"
features (like support for parallel queries - I very much doubt just
using shared instead of local buffers is enough to make it work.)

regards

--
Tomas Vondra http://www.2ndQuadrant.com
<http://www.2ndquadrant.com/&gt;
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#75曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Pavel Stehule (#72)
Re: [Proposal] Global temporary tables

2020年1月21日 下午1:43,Pavel Stehule <pavel.stehule@gmail.com> 写道:

Hi

I have a free time this evening, so I will check this patch

I have a one question

+	/* 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;
+	}

Isbn't possible to fill the rd_rel structure too, so this branching can be reduced?

I'll make some improvements to optimize this part of the code.

Show quoted text

Regards

Pavel

po 20. 1. 2020 v 17:27 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:

2020年1月20日 上午1:32,Erik Rijkers <er@xs4all.nl <mailto:er@xs4all.nl>> 写道:

On 2020-01-19 18:04, 曾文旌(义从) wrote:

2020年1月14日 下午9:20,Pavel Stehule <pavel.stehule@gmail.com <mailto:pavel.stehule@gmail.com>> 写道:
út 14. 1. 2020 v 14:09 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com> <mailto:wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>>> napsal:

[global_temporary_table_v4-pg13.patch ]

Hi,

This patch doesn't quiet apply for me:

patching file src/backend/access/common/reloptions.c
patching file src/backend/access/gist/gistutil.c
patching file src/backend/access/hash/hash.c
Hunk #1 succeeded at 149 (offset 3 lines).
patching file src/backend/access/heap/heapam_handler.c
patching file src/backend/access/heap/vacuumlazy.c
patching file src/backend/access/nbtree/nbtpage.c
patching file src/backend/access/table/tableam.c
patching file src/backend/access/transam/xlog.c
patching file src/backend/catalog/Makefile
Hunk #1 FAILED at 44.
1 out of 1 hunk FAILED -- saving rejects to file src/backend/catalog/Makefile.rej
[...]
(The rest applies without errors)

src/backend/catalog/Makefile.rej contains:

------------------------
--- src/backend/catalog/Makefile
+++ 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 <http://common.mk/&gt;
------------------------

Can you have a look?

I updated the code and remade the patch.
Please give me feedback if you have any more questions.

thanks,

Erik Rijkers

#76曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Pavel Stehule (#74)
Re: [Proposal] Global temporary tables

2020年1月22日 上午2:51,Pavel Stehule <pavel.stehule@gmail.com> 写道:

út 21. 1. 2020 v 9:46 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:

2020年1月12日 上午4:27,Pavel Stehule <pavel.stehule@gmail.com <mailto:pavel.stehule@gmail.com>> 写道:

Hi

so 11. 1. 2020 v 15:00 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:
Hi all

This is the latest patch

The updates are as follows:
1. Support global temp Inherit table global temp partition table
2. Support serial column in GTT
3. Provide views pg_gtt_relstats pg_gtt_stats for GTT’s statistics
4. Provide view pg_gtt_attached_pids to manage GTT
5. Provide function pg_list_gtt_relfrozenxids() to manage GTT
6. Alter GTT or rename GTT is allowed under some conditions

Please give me feedback.

I tested the functionality

1. i think so "ON COMMIT PRESERVE ROWS" should be default mode (like local temp tables).

ON COMMIT PRESERVE ROWS is default mode now.

Thank you

* I tried to create global temp table with index. When I tried to drop this table (and this table was used by second instance), then I got error message

postgres=# drop table foo;
ERROR: can not drop index when other backend attached this global temp table

It is expected, but it is not too much user friendly. Is better to check if you can drop table, then lock it, and then drop all objects.

I don't understand what needs to be improved. Could you describe it in detail?

* tab complete can be nice for CREATE GLOBAL TEMP table

Yes, I will improve it.

\dt+ \di+ doesn't work correctly, or maybe I don't understand to the implementation.

postgres=# create table t(a int primary key);
CREATE TABLE
postgres=# create global temp table gt(a int primary key);
CREATE TABLE
postgres=# insert into t values(generate_series(1,10000));
INSERT 0 10000
postgres=# insert into gt values(generate_series(1,10000));
INSERT 0 10000

postgres=# \dt+
List of relations
Schema | Name | Type | Owner | Persistence | Size | Description
--------+------+-------+-------------+-------------+--------+-------------
public | gt | table | wenjing.zwj | session | 384 kB |
public | t | table | wenjing.zwj | permanent | 384 kB |
(2 rows)

postgres=# \di+
List of relations
Schema | Name | Type | Owner | Table | Persistence | Size | Description
--------+---------+-------+-------------+-------+-------------+--------+-------------
public | gt_pkey | index | wenjing.zwj | gt | session | 240 kB |
public | t_pkey | index | wenjing.zwj | t | permanent | 240 kB |
(2 rows)

I see same size in all sessions. Global temp tables shares same files?

No, they use their own files.
But \dt+ \di+ counts the total file sizes in all sessions for each GTT.

Wenjing

Show quoted text

Regards

Pavel

Wenjing

I tested some simple scripts

test01.sql

CREATE TEMP TABLE foo(a int, b int);
INSERT INTO foo SELECT random()*100, random()*1000 FROM generate_series(1,1000);
ANALYZE foo;
SELECT sum(a), sum(b) FROM foo;
DROP TABLE foo; -- simulate disconnect

after 100 sec, the table pg_attribute has 3.2MB
and 64 tps, 6446 transaction

test02.sql

INSERT INTO foo SELECT random()*100, random()*1000 FROM generate_series(1,1000);
ANALYZE foo;
SELECT sum(a), sum(b) FROM foo;
DELETE FROM foo; -- simulate disconnect

after 100 sec, 1688 tps, 168830 transactions

So performance is absolutely different as we expected.

From my perspective, this functionality is great.

Todo:

pg_table_size function doesn't work

Regards

Pavel

Wenjing

2020年1月6日 上午4:06,Tomas Vondra <tomas.vondra@2ndquadrant.com <mailto:tomas.vondra@2ndquadrant.com>> 写道:

Hi,

I think we need to do something with having two patches aiming to add
global temporary tables:

[1] https://commitfest.postgresql.org/26/2349/ <https://commitfest.postgresql.org/26/2349/&gt;

[2] https://commitfest.postgresql.org/26/2233/ <https://commitfest.postgresql.org/26/2233/&gt;

As a reviewer I have no idea which of the threads to look at - certainly
not without reading both threads, which I doubt anyone will really do.
The reviews and discussions are somewhat intermixed between those two
threads, which makes it even more confusing.

I think we should agree on a minimal patch combining the necessary/good
bits from the various patches, and terminate one of the threads (i.e.
mark it as rejected or RWF). And we need to do that now, otherwise
there's about 0% chance of getting this into v13.

In general, I agree with the sentiment Rober expressed in [1] - the
patch needs to be as small as possible, not adding "nice to have"
features (like support for parallel queries - I very much doubt just
using shared instead of local buffers is enough to make it work.)

regards

--
Tomas Vondra http://www.2ndQuadrant.com <http://www.2ndquadrant.com/&gt;
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#77Pavel Stehule
pavel.stehule@gmail.com
In reply to: 曾文旌(义从) (#76)
Re: [Proposal] Global temporary tables

st 22. 1. 2020 v 7:16 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
napsal:

2020年1月22日 上午2:51,Pavel Stehule <pavel.stehule@gmail.com> 写道:

út 21. 1. 2020 v 9:46 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
napsal:

2020年1月12日 上午4:27,Pavel Stehule <pavel.stehule@gmail.com> 写道:

Hi

so 11. 1. 2020 v 15:00 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
napsal:

Hi all

This is the latest patch

The updates are as follows:
1. Support global temp Inherit table global temp partition table
2. Support serial column in GTT
3. Provide views pg_gtt_relstats pg_gtt_stats for GTT’s statistics
4. Provide view pg_gtt_attached_pids to manage GTT
5. Provide function pg_list_gtt_relfrozenxids() to manage GTT
6. Alter GTT or rename GTT is allowed under some conditions

Please give me feedback.

I tested the functionality

1. i think so "ON COMMIT PRESERVE ROWS" should be default mode (like
local temp tables).

ON COMMIT PRESERVE ROWS is default mode now.

Thank you

* I tried to create global temp table with index. When I tried to drop
this table (and this table was used by second instance), then I got error
message

postgres=# drop table foo;
ERROR: can not drop index when other backend attached this global temp
table

It is expected, but it is not too much user friendly. Is better to check
if you can drop table, then lock it, and then drop all objects.

I don't understand what needs to be improved. Could you describe it in
detail?

the error messages should be some like

can not drop table when other backend attached this global temp table.

It is little bit messy, when you try to drop table and you got message
about index

* tab complete can be nice for CREATE GLOBAL TEMP table

Yes, I will improve it.

\dt+ \di+ doesn't work correctly, or maybe I don't understand to the
implementation.

postgres=# create table t(a int primary key);
CREATE TABLE
postgres=# create global temp table gt(a int primary key);
CREATE TABLE
postgres=# insert into t values(generate_series(1,10000));
INSERT 0 10000
postgres=# insert into gt values(generate_series(1,10000));
INSERT 0 10000

postgres=# \dt+
List of relations
Schema | Name | Type | Owner | Persistence | Size | Description
--------+------+-------+-------------+-------------+--------+-------------
public | gt | table | wenjing.zwj | session | 384 kB |
public | t | table | wenjing.zwj | permanent | 384 kB |
(2 rows)

postgres=# \di+
List of relations
Schema | Name | Type | Owner | Table | Persistence | Size |
Description

--------+---------+-------+-------------+-------+-------------+--------+-------------
public | gt_pkey | index | wenjing.zwj | gt | session | 240 kB |
public | t_pkey | index | wenjing.zwj | t | permanent | 240 kB |
(2 rows)

I see same size in all sessions. Global temp tables shares same files?

No, they use their own files.
But \dt+ \di+ counts the total file sizes in all sessions for each GTT.

I think so it is wrong. The data are independent and the sizes should be
independent too

Show quoted text

Wenjing

Regards

Pavel

Wenjing

I tested some simple scripts

test01.sql

CREATE TEMP TABLE foo(a int, b int);
INSERT INTO foo SELECT random()*100, random()*1000 FROM
generate_series(1,1000);
ANALYZE foo;
SELECT sum(a), sum(b) FROM foo;
DROP TABLE foo; -- simulate disconnect

after 100 sec, the table pg_attribute has 3.2MB
and 64 tps, 6446 transaction

test02.sql

INSERT INTO foo SELECT random()*100, random()*1000 FROM
generate_series(1,1000);
ANALYZE foo;
SELECT sum(a), sum(b) FROM foo;
DELETE FROM foo; -- simulate disconnect

after 100 sec, 1688 tps, 168830 transactions

So performance is absolutely different as we expected.

From my perspective, this functionality is great.

Todo:

pg_table_size function doesn't work

Regards

Pavel

Wenjing

2020年1月6日 上午4:06,Tomas Vondra <tomas.vondra@2ndquadrant.com> 写道:

Hi,

I think we need to do something with having two patches aiming to add
global temporary tables:

[1] https://commitfest.postgresql.org/26/2349/

[2] https://commitfest.postgresql.org/26/2233/

As a reviewer I have no idea which of the threads to look at - certainly
not without reading both threads, which I doubt anyone will really do.
The reviews and discussions are somewhat intermixed between those two
threads, which makes it even more confusing.

I think we should agree on a minimal patch combining the necessary/good
bits from the various patches, and terminate one of the threads (i.e.
mark it as rejected or RWF). And we need to do that now, otherwise
there's about 0% chance of getting this into v13.

In general, I agree with the sentiment Rober expressed in [1] - the
patch needs to be as small as possible, not adding "nice to have"
features (like support for parallel queries - I very much doubt just
using shared instead of local buffers is enough to make it work.)

regards

--
Tomas Vondra http://www.2ndQuadrant.com
<http://www.2ndquadrant.com/&gt;
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#78曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Pavel Stehule (#77)
1 attachment(s)
Re: [Proposal] Global temporary tables

2020年1月22日 下午2:31,Pavel Stehule <pavel.stehule@gmail.com> 写道:

st 22. 1. 2020 v 7:16 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:

2020年1月22日 上午2:51,Pavel Stehule <pavel.stehule@gmail.com <mailto:pavel.stehule@gmail.com>> 写道:

út 21. 1. 2020 v 9:46 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:

2020年1月12日 上午4:27,Pavel Stehule <pavel.stehule@gmail.com <mailto:pavel.stehule@gmail.com>> 写道:

Hi

so 11. 1. 2020 v 15:00 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:
Hi all

This is the latest patch

The updates are as follows:
1. Support global temp Inherit table global temp partition table
2. Support serial column in GTT
3. Provide views pg_gtt_relstats pg_gtt_stats for GTT’s statistics
4. Provide view pg_gtt_attached_pids to manage GTT
5. Provide function pg_list_gtt_relfrozenxids() to manage GTT
6. Alter GTT or rename GTT is allowed under some conditions

Please give me feedback.

I tested the functionality

1. i think so "ON COMMIT PRESERVE ROWS" should be default mode (like local temp tables).

ON COMMIT PRESERVE ROWS is default mode now.

Thank you

* I tried to create global temp table with index. When I tried to drop this table (and this table was used by second instance), then I got error message

postgres=# drop table foo;
ERROR: can not drop index when other backend attached this global temp table

It is expected, but it is not too much user friendly. Is better to check if you can drop table, then lock it, and then drop all objects.

I don't understand what needs to be improved. Could you describe it in detail?

the error messages should be some like

can not drop table when other backend attached this global temp table.

It is little bit messy, when you try to drop table and you got message about index

It has been repaired in global_temporary_table_v7-pg13.patch

* tab complete can be nice for CREATE GLOBAL TEMP table

Yes, I will improve it.

It has been repaired in global_temporary_table_v7-pg13.patch

\dt+ \di+ doesn't work correctly, or maybe I don't understand to the implementation.

postgres=# create table t(a int primary key);
CREATE TABLE
postgres=# create global temp table gt(a int primary key);
CREATE TABLE
postgres=# insert into t values(generate_series(1,10000));
INSERT 0 10000
postgres=# insert into gt values(generate_series(1,10000));
INSERT 0 10000

postgres=# \dt+
List of relations
Schema | Name | Type | Owner | Persistence | Size | Description
--------+------+-------+-------------+-------------+--------+-------------
public | gt | table | wenjing.zwj | session | 384 kB |
public | t | table | wenjing.zwj | permanent | 384 kB |
(2 rows)

postgres=# \di+
List of relations
Schema | Name | Type | Owner | Table | Persistence | Size | Description
--------+---------+-------+-------------+-------+-------------+--------+-------------
public | gt_pkey | index | wenjing.zwj | gt | session | 240 kB |
public | t_pkey | index | wenjing.zwj | t | permanent | 240 kB |
(2 rows)

I see same size in all sessions. Global temp tables shares same files?

No, they use their own files.
But \dt+ \di+ counts the total file sizes in all sessions for each GTT.

I think so it is wrong. The data are independent and the sizes should be independent too

It has been repaired in global_temporary_table_v7-pg13.patch.

Wenjing

Show quoted text

Wenjing

Regards

Pavel

Wenjing

I tested some simple scripts

test01.sql

CREATE TEMP TABLE foo(a int, b int);
INSERT INTO foo SELECT random()*100, random()*1000 FROM generate_series(1,1000);
ANALYZE foo;
SELECT sum(a), sum(b) FROM foo;
DROP TABLE foo; -- simulate disconnect

after 100 sec, the table pg_attribute has 3.2MB
and 64 tps, 6446 transaction

test02.sql

INSERT INTO foo SELECT random()*100, random()*1000 FROM generate_series(1,1000);
ANALYZE foo;
SELECT sum(a), sum(b) FROM foo;
DELETE FROM foo; -- simulate disconnect

after 100 sec, 1688 tps, 168830 transactions

So performance is absolutely different as we expected.

From my perspective, this functionality is great.

Todo:

pg_table_size function doesn't work

Regards

Pavel

Wenjing

2020年1月6日 上午4:06,Tomas Vondra <tomas.vondra@2ndquadrant.com <mailto:tomas.vondra@2ndquadrant.com>> 写道:

Hi,

I think we need to do something with having two patches aiming to add
global temporary tables:

[1] https://commitfest.postgresql.org/26/2349/ <https://commitfest.postgresql.org/26/2349/&gt;

[2] https://commitfest.postgresql.org/26/2233/ <https://commitfest.postgresql.org/26/2233/&gt;

As a reviewer I have no idea which of the threads to look at - certainly
not without reading both threads, which I doubt anyone will really do.
The reviews and discussions are somewhat intermixed between those two
threads, which makes it even more confusing.

I think we should agree on a minimal patch combining the necessary/good
bits from the various patches, and terminate one of the threads (i.e.
mark it as rejected or RWF). And we need to do that now, otherwise
there's about 0% chance of getting this into v13.

In general, I agree with the sentiment Rober expressed in [1] - the
patch needs to be as small as possible, not adding "nice to have"
features (like support for parallel queries - I very much doubt just
using shared instead of local buffers is enough to make it work.)

regards

--
Tomas Vondra http://www.2ndQuadrant.com <http://www.2ndquadrant.com/&gt;
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

global_temporary_table_v7-pg13.patchapplication/octet-stream; name=global_temporary_table_v7-pg13.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 79430d2..b7173c7 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 | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},	
 	/* list terminator */
 	{{NULL}}
 };
@@ -1486,6 +1499,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1586,13 +1601,18 @@ build_reloptions(Datum reloptions, bool validate,
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	/*
 	 * There are no options for partitioned tables yet, but this is able to do
 	 * some validation.
 	 */
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index dd975b1..1610e7d 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1013,7 +1013,9 @@ gistGetFakeLSN(Relation rel)
 {
 	static XLogRecPtr counter = FirstNormalUnloggedLSN;
 
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	/* global temp is same as local temp table */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 4871b7f..16b00c9 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -149,7 +149,9 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	/* global temp table is same as local temp table */
+	if (!(index->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		index->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 1f6f6d0..5727ccd 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -598,7 +598,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -651,7 +651,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index b331f4c..cd02c6b 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -62,6 +62,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -397,8 +398,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)
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index f05cbe7..946c9d2 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -763,7 +763,14 @@ _bt_getbuf(Relation rel, BlockNumber blkno, int access)
 		/* Read an existing block of the relation */
 		buf = ReadBuffer(rel, blkno);
 		LockBuffer(buf, access);
-		_bt_checkpage(rel, buf);
+
+		/* global temp table may be not yet initialized for this backend. */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			blkno == BTREE_METAPAGE &&
+			PageIsNew(BufferGetPage(buf)))
+			_bt_initmetapage(BufferGetPage(buf), P_NONE, 0);
+		else
+			_bt_checkpage(rel, buf);
 	}
 	else
 	{
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 7f4f784..aba8a9f 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6327,6 +6327,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 d5da81c..c753b0e 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -42,6 +42,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 16cb6d8..f28f2c2 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -398,7 +398,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/* global temp table is same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 0fdff29..bd3cd9d 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -404,6 +406,10 @@ heap_create(const char *relname,
 									 relpersistence,
 									 relkind);
 
+	/* global temp table not create storage file when catalog create */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		create_storage = false;
+
 	/*
 	 * Have the storage manager create the relation's disk file, if needed.
 	 *
@@ -427,7 +433,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -956,6 +962,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -994,8 +1001,18 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/* global temp table not remember transaction info in catalog */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1357,6 +1374,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1441,11 +1459,15 @@ heap_create_with_catalog(const char *relname,
 	 */
 	StoreConstraints(new_rel_desc, cooked_constraints, is_internal);
 
-	/*
-	 * If there's a special on-commit action, remember it
-	 */
-	if (oncommit != ONCOMMIT_NOOP)
-		register_on_commit_action(relid, oncommit);
+	/* global temp table register action when storage init */
+	if (relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		/*
+		 * If there's a special on-commit action, remember it
+		 */
+		if (oncommit != ONCOMMIT_NOOP)
+			register_on_commit_action(relid, oncommit);
+	}
 
 	/*
 	 * ok, the relation has been cataloged, so close our relations and return
@@ -1937,6 +1959,13 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/* We allow to drop global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(rel->rd_node))
+			elog(ERROR, "can not drop relation when other backend attached this global temp table");
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3165,7 +3194,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3177,7 +3206,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3223,8 +3252,13 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
+
+		/* truncate global temp table only need RowExclusiveLock */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3257,6 +3291,8 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
+	bool		truncate_toastrel = false;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3265,23 +3301,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 3e59e64..9705b1d 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -52,6 +52,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -877,6 +878,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))
+			elog(ERROR, "can not create index when have other 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 */
+		if (!gtt_storage_attached(heapRelation->rd_node.relNode))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -2045,6 +2066,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.
@@ -2651,6 +2679,11 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	/* update index stats into localhash and rel_rd_rel for global temp table */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		is_gtt = true;
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2736,21 +2769,35 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
+		if (is_gtt)
+			rel->rd_rel->relpages = (int32) relpages;
+		else if (rd_rel->relpages != (int32) relpages)
 		{
 			rd_rel->relpages = (int32) relpages;
 			dirty = true;
 		}
-		if (rd_rel->reltuples != (float4) reltuples)
+
+		if (is_gtt)
+			rel->rd_rel->reltuples = (float4) reltuples;
+		else if (rd_rel->reltuples != (float4) reltuples)
 		{
 			rd_rel->reltuples = (float4) reltuples;
 			dirty = true;
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+
+		if (is_gtt)
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+		else if (rd_rel->relallvisible != (int32) relallvisible)
 		{
 			rd_rel->relallvisible = (int32) relallvisible;
 			dirty = true;
 		}
+
+		if (is_gtt)
+		{
+			up_gtt_relstats(rel, relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
+		}
 	}
 
 	/*
@@ -2864,6 +2911,12 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, index, val);
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		if (!gtt_storage_attached(indexRelation->rd_node.relNode))
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3459,6 +3512,15 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 				 errmsg("cannot reindex temporary tables of other sessions")));
 
 	/*
+	 * Because global temp table cannot change relfilenode
+	 * no support reindex on global temp table yet.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(iRel))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot reindex global temporary tables")));
+
+	/*
 	 * Also check for active uses of the index in the current transaction; we
 	 * don't want to reindex underneath an open indexscan.
 	 */
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index e70243a..301da79 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -647,6 +647,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		/* global temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp relations in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index fddfbf1..671c614 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -26,6 +26,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -75,7 +76,7 @@ static PendingRelDelete *pendingDeletes = NULL; /* head of linked list */
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -85,6 +86,8 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		/* global temp table use same storage strategy as local temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -117,6 +120,10 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/* remember global temp table storage info to localhash */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel)
+		remember_gtt_storage_info(rnode, rel);
+
 	return srel;
 }
 
@@ -486,8 +493,15 @@ smgrDoPendingDeletes(bool isCommit)
 		smgrdounlinkall(srels, nrels, false);
 
 		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* clean global temp table flags when transaction commit or rollback */
+			if (SmgrIsTemp(srels[i]) && 
+				gtt_storage_attached(srels[i]->smgr_rnode.node.relNode))
+				forget_gtt_storage_info(srels[i]->smgr_rnode.node.relNode);
+		}
+
 		pfree(srels);
 	}
 }
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..f489cd3
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1129 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  code to create and destroy physical storage for global temparary table
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/table.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_relstats_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+typedef struct gtt_ctl_data
+{
+	LWLock			lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct
+{
+	RelFileNode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+	Oid			relid;
+
+	Oid			spcnode;
+	/* pg_class stat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+	char		relkind;
+	bool		on_commit_delete;
+
+	/* pg_statistic */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(RelFileNode rnode);
+static void gtt_storage_checkout(RelFileNode rnode, bool skiplock);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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 || entry->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		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;
+		int			i;
+
+		rnode.spcNode = entry->spcnode;
+		rnode.dbNode = MyDatabaseId;
+		rnode.relNode = entry->relid;
+
+		gtt_storage_checkout(rnode, false);
+
+		Assert(TransactionIdIsNormal(entry->relfrozenxid));
+		remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+
+		for (i = 0; i < entry->natts; i++)
+		{
+			if (entry->att_stat_tups[i])
+			{
+				heap_freetuple(entry->att_stat_tups[i]);
+				entry->att_stat_tups[i] = NULL;
+			}
+		}
+	}
+
+	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.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (gtt_storage_local_hash == NULL)
+		return false;
+
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_FIND, &found);
+
+	if (!found)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	if (relpages)
+		*relpages = entry->relpages;
+
+	if (reltuples)
+		*reltuples = entry->reltuples;
+
+	if (relallvisible)
+		*relallvisible = entry->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = entry->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = entry->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	MemoryContext			oldcontext;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+						(void *) &reloid, HASH_FIND, &found);
+
+	if (!found)
+		return;
+
+	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;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	HeapTuple	tuple;
+	Relation	rel = NULL;
+	int		attnum = PG_GETARG_INT32(1);
+	Oid		reloid = PG_GETARG_OID(0);
+	char		rel_persistence;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	  tupdesc;
+	MemoryContext oldcontext;
+	Tuplestorestate *tupstore;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = table_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+
+	table_close(rel, NoLock);
+	table_close(pg_tatistic, AccessShareLock);
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate	*tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Relation	rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	if (get_gtt_relstats(reloid,
+						&relpages, &reltuples, &relallvisible,
+						&relfrozenxid, &relminmxid))
+	{
+		Datum	values[5];
+		bool	isnull[5];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = Int32GetDatum(relpages);
+		values[1] = Float4GetDatum((float4)reltuples);
+		values[2] = Int32GetDatum(relallvisible);
+		values[3] = UInt32GetDatum(relfrozenxid);
+		values[4] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+
+	tuplestore_donestoring(tupstore);
+	table_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(rel->rd_node);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	table_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index c9e75f4..2adde62 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM 
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c4420dd..977a984 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -586,14 +587,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 		 * pg_statistic for columns we didn't process, we leave them alone.)
 		 */
 		update_attstats(RelationGetRelid(onerel), inh,
-						attr_cnt, vacattrstats);
+						attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
 
 		for (ind = 0; ind < nindexes; ind++)
 		{
 			AnlIndexData *thisdata = &indexdata[ind];
 
 			update_attstats(RelationGetRelid(Irel[ind]), false,
-							thisdata->attr_cnt, thisdata->vacattrstats);
+							thisdata->attr_cnt, thisdata->vacattrstats,
+							RelationGetRelPersistence(Irel[ind]));
 		}
 
 		/*
@@ -1456,7 +1458,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
  *		by taking a self-exclusive lock on the relation in analyze_rel().
  */
 static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
 {
 	Relation	sd;
 	int			attno;
@@ -1558,31 +1560,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/* Update column statistic to localhash, not catalog */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index e9d7a7f..3088e26 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -391,6 +391,12 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/* not support cluster global temp table yet */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("not support cluster global temporary tables yet")));
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 52ce02f..3d3f7dc 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2583,6 +2583,16 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 			!isTempNamespace(classtuple->relnamespace))
 			continue;
 
+		/* not support reindex on global temp table, so skip it */
+		if (classtuple->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			ereport(WARNING,
+				(errmsg("global temp table \"%s.%s\" skip reindexed",
+					get_namespace_name(get_rel_namespace(relid)),
+					get_rel_name(relid))));
+			continue;
+		}
+
 		/* Check user/system classification, and optionally skip */
 		if (objectKind == REINDEX_OBJECT_SYSTEM &&
 			!IsSystemClass(relid, classtuple))
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 329ab84..dfa257c 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -96,7 +96,8 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 	 * transaction.
 	 */
 	relpersistence = get_rel_persistence(relid);
-	if (relpersistence == RELPERSISTENCE_TEMP)
+	if (relpersistence == RELPERSISTENCE_TEMP ||
+		relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
 
 	/* Check permissions. */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..44f350d 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -94,7 +94,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +108,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -222,7 +223,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* now initialize the sequence's data */
 	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -327,7 +328,7 @@ ResetSequence(Oid seq_relid)
 	/*
 	 * Insert the modified tuple into the new storage file.
 	 */
-	fill_seq_with_data(seq_rel, tuple);
+	fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -340,17 +341,21 @@ ResetSequence(Oid seq_relid)
  * Initialize a sequence's relation with the specified tuple as content
  */
 static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 	sequence_magic *sm;
 	OffsetNumber offnum;
+	bool lockBuffer = false;
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ReadBuffer(rel, P_NEW);
-	Assert(BufferGetBlockNumber(buf) == 0);
+	if (buf == InvalidBuffer)
+	{
+		buf = ReadBuffer(rel, P_NEW);
+		Assert(BufferGetBlockNumber(buf) == 0);
+		lockBuffer = true;
+	}
 
 	page = BufferGetPage(buf);
 
@@ -360,7 +365,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* Now insert sequence tuple */
 
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+	if (lockBuffer)
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +416,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	END_CRIT_SECTION();
 
-	UnlockReleaseBuffer(buf);
+	if (lockBuffer)
+		UnlockReleaseBuffer(buf);
 }
 
 /*
@@ -502,7 +509,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * Insert the modified tuple into the new storage file.
 		 */
-		fill_seq_with_data(seqrel, newdatatuple);
+		fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
 	}
 
 	/* process OWNED BY if given */
@@ -1178,6 +1185,25 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
 	LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
 
 	page = BufferGetPage(*buf);
+	if (GlobalTempRelationPageIsNotInitialized(rel, page))
+	{
+		/* Initialize sequence for global temporary tables */
+		Datum		value[SEQ_COL_LASTCOL] = {0};
+		bool		null[SEQ_COL_LASTCOL] = {false};
+		HeapTuple	tuple;
+		int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+		/*
+		 * last_value from pg_sequence.seqstart
+		 * log_cnt = 0
+		 * is_called = false
+		 */
+		value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+		tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+		fill_seq_with_data(rel, tuple, *buf);
+		heap_freetuple(tuple);
+	}
 	sm = (sequence_magic *) PageGetSpecialPointer(page);
 
 	if (sm->magic != SEQ_MAGIC)
@@ -1954,3 +1980,23 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 30b72b6..cc5b991 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -552,6 +553,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 								  Relation partitionTbl);
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static bool has_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -597,6 +599,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	bool		has_oncommit_clause = false;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -607,8 +610,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Check consistency of arguments
 	 */
+	/* global temp table same as local temp table */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !(stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+			 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -638,7 +643,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	/* global temp table same as local temp table */
+	if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+		 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -739,6 +746,57 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* global temp table */
+	has_oncommit_clause = has_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* inherit table or parition table inherit on commit property from parent table*/
+		if (inheritOids && stmt->oncommit == ONCOMMIT_NOOP && !has_oncommit_clause)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			if (RELATION_GTT_ON_COMMIT_DELETE(relation))
+				stmt->oncommit = ONCOMMIT_DELETE_ROWS;
+			else
+				stmt->oncommit = ONCOMMIT_PRESERVE_ROWS;
+			table_close(relation, NoLock);
+		}
+
+		if (has_oncommit_clause)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "can not defeine global temp table with on commit and with clause at same time");
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temp table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (has_oncommit_clause)
+		elog(ERROR, "regular table cannot specifie on_commit_delete_rows");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1800,7 +1858,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);
@@ -3359,6 +3418,13 @@ RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal, bo
 	 * specially.
 	 */
 	targetrelation = relation_open(myrelid, is_index ? ShareUpdateExclusiveLock : AccessExclusiveLock);
+
+	if (RELATION_IS_GLOBAL_TEMP(targetrelation))
+	{
+		if (is_other_backend_use_gtt(targetrelation->rd_node))
+			elog(ERROR, "can not rename relation when other backend attached this global temp table");
+	}
+
 	namespaceId = RelationGetNamespace(targetrelation);
 
 	/*
@@ -3534,6 +3600,13 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(rel->rd_node))
+			elog(ERROR, "can not alter relation when other backend attached this global temp table");
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -8099,6 +8172,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),
@@ -8138,6 +8218,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;
 	}
 
 	/*
@@ -13174,7 +13260,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,
@@ -14581,7 +14667,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",
@@ -17221,3 +17309,20 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
 		table_close(rel, NoLock);
 	}
 }
+
+static bool
+has_oncommit_option(List *options)
+{
+	ListCell   *listptr;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (pg_strcasecmp(def->defname, "on_commit_delete_rows") == 0)
+			return true;
+	}
+
+	return false;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index d625d17..69ad24f 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/vacuum.h"
@@ -1217,6 +1218,17 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	 /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		is_gtt = true;
+		up_gtt_relstats(relation,
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1230,17 +1242,26 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relpages = (int32) num_pages;
+	else if (pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (is_gtt)
+		relation->rd_rel->reltuples = (float4) num_tuples;
+	else if (pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	else if (pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1285,7 +1306,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1296,7 +1318,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1394,6 +1417,10 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/* global temp table relstats not in pg_class */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1451,6 +1478,25 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/*
+	 * Global temp table get frozenxid from MyProc
+	 * to avoid the vacuum truncate clog that gtt need.
+	 */
+	if (max_active_gtt > 0)
+	{
+		TransactionId oldest_gtt_frozenxid =
+			list_all_session_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid) &&
+			TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+		{
+			ereport(WARNING,
+				(errmsg("global temp table oldest FrozenXid is far in the past"),
+				 errhint("please truncate them or kill those sessions that use them.")));
+			newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 8286d9c..c3992a4 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -591,6 +591,8 @@ static void
 set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 						  RangeTblEntry *rte)
 {
+	char relpersistence;
+
 	/*
 	 * The flag has previously been initialized to false, so we can just
 	 * return if it becomes clear that we can't safely set it.
@@ -618,7 +620,11 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			/* global temp table is same as local temp table */
+			relpersistence = get_rel_persistence(rte->relid);
+
+			if (relpersistence == RELPERSISTENCE_TEMP ||
+				relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index d6f2153..310a9e2 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6312,7 +6312,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
+	/* global temp table is same as local temp table */
 	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 748bebf..a5ddbcd 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2579,6 +2579,11 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("materialized views must not use temporary tables or views")));
 
+		if (is_query_using_gtt(query))
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("materialized views must not use global temporary tables or views")));
+
 		/*
 		 * A materialized view would either need to save parameters for use in
 		 * maintaining/loading the data or prohibit them entirely.  The latter
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ba5916b..0ee6931 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3290,17 +3290,11 @@ OptTemp:	TEMPORARY					{ $$ = RELPERSISTENCE_TEMP; }
 			| LOCAL TEMP				{ $$ = RELPERSISTENCE_TEMP; }
 			| GLOBAL TEMPORARY
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
-					$$ = RELPERSISTENCE_TEMP;
+					$$ = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
-					$$ = RELPERSISTENCE_TEMP;
+					$$ = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED					{ $$ = RELPERSISTENCE_UNLOGGED; }
 			| /*EMPTY*/					{ $$ = RELPERSISTENCE_PERMANENT; }
@@ -11588,19 +11582,13 @@ OptTempTableName:
 				}
 			| GLOBAL TEMPORARY opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED opt_table qualified_name
 				{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b875a50..123945b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index ee2d2b5..9c9abaa 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 6d1f28c..ed837d1 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2086,6 +2086,11 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* autovacuum skip vacuum global temp table */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2152,7 +2157,9 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		/* autovacuum skip vacuum global temp table */
+		if (classForm->relpersistence == RELPERSISTENCE_TEMP || 
+			classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index aba3960..3c4b96c 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -52,6 +53,7 @@
 #include "utils/rel.h"
 #include "utils/resowner_private.h"
 #include "utils/timestamp.h"
+#include "utils/guc.h"
 
 
 /* Note: these two macros only work on shared buffers, not local ones! */
@@ -432,7 +434,7 @@ ForgetPrivateRefCountEntry(PrivateRefCountEntry *ref)
 static Buffer ReadBuffer_common(SMgrRelation reln, char relpersistence,
 								ForkNumber forkNum, BlockNumber blockNum,
 								ReadBufferMode mode, BufferAccessStrategy strategy,
-								bool *hit);
+								bool *hit, Relation rel);
 static bool PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy);
 static void PinBuffer_Locked(BufferDesc *buf);
 static void UnpinBuffer(BufferDesc *buf, bool fixOwner);
@@ -664,7 +666,8 @@ ReadBufferExtended(Relation reln, ForkNumber forkNum, BlockNumber blockNum,
 	 */
 	pgstat_count_buffer_read(reln);
 	buf = ReadBuffer_common(reln->rd_smgr, reln->rd_rel->relpersistence,
-							forkNum, blockNum, mode, strategy, &hit);
+							forkNum, blockNum, mode, strategy, &hit,
+							reln);
 	if (hit)
 		pgstat_count_buffer_hit(reln);
 	return buf;
@@ -692,7 +695,7 @@ ReadBufferWithoutRelcache(RelFileNode rnode, ForkNumber forkNum,
 	Assert(InRecovery);
 
 	return ReadBuffer_common(smgr, RELPERSISTENCE_PERMANENT, forkNum, blockNum,
-							 mode, strategy, &hit);
+							 mode, strategy, &hit, NULL);
 }
 
 
@@ -704,7 +707,8 @@ ReadBufferWithoutRelcache(RelFileNode rnode, ForkNumber forkNum,
 static Buffer
 ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 				  BlockNumber blockNum, ReadBufferMode mode,
-				  BufferAccessStrategy strategy, bool *hit)
+				  BufferAccessStrategy strategy, bool *hit,
+				  Relation rel)
 {
 	BufferDesc *bufHdr;
 	Block		bufBlock;
@@ -719,6 +723,15 @@ ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 
 	isExtend = (blockNum == P_NEW);
 
+	/* create storage when first read data page for gtt */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(isExtend || blockNum == 0) &&
+		forkNum == MAIN_FORKNUM &&
+		!gtt_storage_attached(smgr->smgr_rnode.node.relNode))
+	{
+		RelationCreateStorage(smgr->smgr_rnode.node, relpersistence, rel);
+	}
+
 	TRACE_POSTGRESQL_BUFFER_READ_START(forkNum, blockNum,
 									   smgr->smgr_rnode.node.spcNode,
 									   smgr->smgr_rnode.node.dbNode,
@@ -2809,6 +2822,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(relation->rd_node.relNode))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d5..7adb969 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -21,6 +21,7 @@
 #include "access/nbtree.h"
 #include "access/subtrans.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, active_gtt_shared_hash_size());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* global temporary table */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index c3adb2e..eb95e5f 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -62,6 +62,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -4087,3 +4088,77 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temp table.
+ */
+int
+list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct *arrayP = procArray;
+	TransactionId result = InvalidTransactionId;
+	int			index;
+	int			flags = 0;
+	int			i = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+		*n = 0;
+	}
+
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		volatile PGXACT *pgxact = &allPgXact[pgprocno];
+
+		if (pgxact->vacuumFlags & flags)
+			continue;
+
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->session_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->session_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->session_gtt_frozenxid, result))
+				result = proc->session_gtt_frozenxid;
+
+			if (max_size > 0)
+			{
+				pids[i] = proc->pid;
+				xids[i] = proc->session_gtt_frozenxid;
+				i++;
+			}
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (max_size > 0)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 32df8c8..e4e1125 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c
index 85b7115..f5eae8c 100644
--- a/src/backend/storage/smgr/md.c
+++ b/src/backend/storage/smgr/md.c
@@ -646,6 +646,12 @@ mdread(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
 		 */
 		if (zero_damaged_pages || InRecovery)
 			MemSet(buffer, 0, BLCKSZ);
+		else if(SmgrIsTemp(reln) && blocknum == 0 && forknum == MAIN_FORKNUM)
+		{
+			/* global temp table init btree meta page */
+			MemSet(buffer, 0, BLCKSZ);
+			mdwrite(reln, forknum, blocknum, buffer, true);
+		}
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_DATA_CORRUPTED),
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 7c6f057..eb10cf2 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -112,6 +112,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -4578,12 +4579,25 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -4708,15 +4722,27 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6098,6 +6124,7 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	{
 		/* Simple variable --- look to stats for the underlying table */
 		RangeTblEntry *rte = planner_rt_fetch(index->rel->relid, root);
+		char	rel_persistence = get_rel_persistence(rte->relid);
 
 		Assert(rte->rtekind == RTE_RELATION);
 		relid = rte->relid;
@@ -6115,6 +6142,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6126,6 +6160,8 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	}
 	else
 	{
+		char	rel_persistence = get_rel_persistence(index->indexoid);
+
 		/* Expression --- maybe there are stats for the index itself */
 		relid = index->indexoid;
 		colnum = 1;
@@ -6141,6 +6177,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7054,6 +7097,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		/* attempt to lookup stats in relation for this index column */
 		if (attnum != 0)
 		{
+			char	rel_persistence = get_rel_persistence(rte->relid);
+
 			/* Simple variable -- look to stats for the underlying table */
 			if (get_relation_stats_hook &&
 				(*get_relation_stats_hook) (root, rte, attnum, &vardata))
@@ -7066,6 +7111,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7078,6 +7131,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		}
 		else
 		{
+			char	rel_persistence = get_rel_persistence(index->indexoid);
+
 			/*
 			 * Looks like we've found an expression column in the index. Let's
 			 * see if there's any stats for it.
@@ -7097,6 +7152,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 1e3e6d3..345e3e9 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -2878,6 +2879,18 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index df025a5..1f733f4 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -63,6 +63,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1124,6 +1125,28 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				/*
+				 * For global temp table, all backend can use
+				 * this relation, so rd_islocaltemp always true.
+				 */
+				relation->rd_islocaltemp = true;
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -3313,6 +3336,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;
@@ -3427,6 +3454,9 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
 
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+		elog(ERROR, "global temp table does not allow setting new relfilenode");
+
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
@@ -3467,7 +3497,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 e44f71e..44c86c8 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -139,6 +139,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,
@@ -1992,6 +2004,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 799b698..b98d396 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15583,6 +15583,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	{
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
+		char		*table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15634,9 +15635,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 			binary_upgrade_set_pg_class_oids(fout, q,
 											 tbinfo->dobj.catId.oid, false);
 
+		if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED)
+			table_type = "UNLOGGED ";
+		else if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			table_type = "GLOBAL TEMPORARY ";
+		else
+			table_type = "";
+
 		appendPQExpBuffer(q, "CREATE %s%s %s",
-						  tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ?
-						  "UNLOGGED " : "",
+						  table_type,
 						  reltypename,
 						  qualrelname);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index f3c7eb9..28134e2 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3723,7 +3723,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 052d98b..f35d9bf 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1003,6 +1003,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2326,6 +2328,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2534,6 +2539,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index a12fc1f..78958e4 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -165,6 +165,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index fcf2a12..ba0bd5f 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5484,6 +5484,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4191',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4192',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{relid,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4193',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4194',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 048003c..af48cdf 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -19,7 +19,7 @@
 #include "storage/smgr.h"
 #include "utils/relcache.h"
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationTruncate(Relation rel, BlockNumber nblocks);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..eacc214
--- /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 bool 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 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 870ecb5..92c590e 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 8fda8e4..172eae9 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_TBM,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_SXACT,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 5b407e6..f13250a 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -117,6 +117,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId session_gtt_frozenxid;	/* session level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ce93ace..0f7262e 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -281,6 +281,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 44ed04d..3f9720d 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -277,6 +277,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -535,7 +536,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
@@ -602,6 +604,17 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..50ca9ac
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,7 @@
+reset search_path;
+drop schema gtt cascade;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..a477917
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,197 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+ERROR:  cannot create indexes on global temporary tables using concurrent mode
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+ERROR:  not support cluster global temporary tables yet
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ok
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  regular table cannot specifie on_commit_delete_rows
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  can not defeine global temp table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test;
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  referenced relation "products" is not a global temp table
+-- ERROR
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  referenced relation "products" is not a global temp table
+-- ERROR
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ALL ERROR
+create index idx_err on gtt1 using hash (a);
+ERROR:  only support btree index on global temp table
+create index idx_err on gtt1 using gist (a);
+ERROR:  data type integer has no default operator class for access method "gist"
+HINT:  You must specify an operator class for the index or define a default operator class for the data type.
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+ERROR:  only support btree index on global temp table
+create index idx_tmp_t0_1 on tmp_t0 using gist (c0);
+ERROR:  only support btree index on global temp table
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 15 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..30d8a7b
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,84 @@
+set search_path=gtt,sys;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..850ef3e
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,161 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          475136 |                 974848
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |           409600 |        409600 |                 409600
+(2 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..9fe5fd4
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,9 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..b258b7c
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,76 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+(1 row)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+(1 row)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 70e1e2f..30cf4bd 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1348,6 +1348,93 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d33a4e1..643afe4 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..f3cf710
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,6 @@
+
+
+reset search_path;
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..cfc1879
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,163 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ok
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test;
+
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ERROR
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ERROR
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ALL ERROR
+create index idx_err on gtt1 using hash (a);
+create index idx_err on gtt1 using gist (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_1 on tmp_t0 using gist (c0);
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d7d81de
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,42 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..5203c2b
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,76 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..2f4d883
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,16 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..f041892
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,42 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
#79曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: 曾文旌(义从) (#75)
1 attachment(s)
Re: [Proposal] Global temporary tables

2020年1月22日 下午1:29,曾文旌(义从) <wenjing.zwj@alibaba-inc.com> 写道:

2020年1月21日 下午1:43,Pavel Stehule <pavel.stehule@gmail.com <mailto:pavel.stehule@gmail.com>> 写道:

Hi

I have a free time this evening, so I will check this patch

I have a one question

+	/* 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;
+	}

Isbn't possible to fill the rd_rel structure too, so this branching can be reduced?

I'll make some improvements to optimize this part of the code.

I'm trying to improve this part of the implementation in global_temporary_table_v7-pg13.patch
Please check my patch and give me feedback.

Thanks

Wenjing

Show quoted text

Regards

Pavel

po 20. 1. 2020 v 17:27 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:

2020年1月20日 上午1:32,Erik Rijkers <er@xs4all.nl <mailto:er@xs4all.nl>> 写道:

On 2020-01-19 18:04, 曾文旌(义从) wrote:

2020年1月14日 下午9:20,Pavel Stehule <pavel.stehule@gmail.com <mailto:pavel.stehule@gmail.com>> 写道:
út 14. 1. 2020 v 14:09 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com> <mailto:wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>>> napsal:

[global_temporary_table_v4-pg13.patch ]

Hi,

This patch doesn't quiet apply for me:

patching file src/backend/access/common/reloptions.c
patching file src/backend/access/gist/gistutil.c
patching file src/backend/access/hash/hash.c
Hunk #1 succeeded at 149 (offset 3 lines).
patching file src/backend/access/heap/heapam_handler.c
patching file src/backend/access/heap/vacuumlazy.c
patching file src/backend/access/nbtree/nbtpage.c
patching file src/backend/access/table/tableam.c
patching file src/backend/access/transam/xlog.c
patching file src/backend/catalog/Makefile
Hunk #1 FAILED at 44.
1 out of 1 hunk FAILED -- saving rejects to file src/backend/catalog/Makefile.rej
[...]
(The rest applies without errors)

src/backend/catalog/Makefile.rej contains:

------------------------
--- src/backend/catalog/Makefile
+++ 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 <http://common.mk/&gt;
------------------------

Can you have a look?

I updated the code and remade the patch.
Please give me feedback if you have any more questions.

thanks,

Erik Rijkers

Attachments:

global_temporary_table_v7-pg13.patchapplication/octet-stream; name=global_temporary_table_v7-pg13.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 79430d2..b7173c7 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 | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},	
 	/* list terminator */
 	{{NULL}}
 };
@@ -1486,6 +1499,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1586,13 +1601,18 @@ build_reloptions(Datum reloptions, bool validate,
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	/*
 	 * There are no options for partitioned tables yet, but this is able to do
 	 * some validation.
 	 */
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index dd975b1..1610e7d 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1013,7 +1013,9 @@ gistGetFakeLSN(Relation rel)
 {
 	static XLogRecPtr counter = FirstNormalUnloggedLSN;
 
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	/* global temp is same as local temp table */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 4871b7f..16b00c9 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -149,7 +149,9 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	/* global temp table is same as local temp table */
+	if (!(index->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		index->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 1f6f6d0..5727ccd 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -598,7 +598,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -651,7 +651,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index b331f4c..cd02c6b 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -62,6 +62,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -397,8 +398,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)
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index f05cbe7..946c9d2 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -763,7 +763,14 @@ _bt_getbuf(Relation rel, BlockNumber blkno, int access)
 		/* Read an existing block of the relation */
 		buf = ReadBuffer(rel, blkno);
 		LockBuffer(buf, access);
-		_bt_checkpage(rel, buf);
+
+		/* global temp table may be not yet initialized for this backend. */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			blkno == BTREE_METAPAGE &&
+			PageIsNew(BufferGetPage(buf)))
+			_bt_initmetapage(BufferGetPage(buf), P_NONE, 0);
+		else
+			_bt_checkpage(rel, buf);
 	}
 	else
 	{
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 7f4f784..aba8a9f 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6327,6 +6327,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 d5da81c..c753b0e 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -42,6 +42,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 16cb6d8..f28f2c2 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -398,7 +398,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/* global temp table is same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 0fdff29..bd3cd9d 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -404,6 +406,10 @@ heap_create(const char *relname,
 									 relpersistence,
 									 relkind);
 
+	/* global temp table not create storage file when catalog create */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		create_storage = false;
+
 	/*
 	 * Have the storage manager create the relation's disk file, if needed.
 	 *
@@ -427,7 +433,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -956,6 +962,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -994,8 +1001,18 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/* global temp table not remember transaction info in catalog */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1357,6 +1374,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1441,11 +1459,15 @@ heap_create_with_catalog(const char *relname,
 	 */
 	StoreConstraints(new_rel_desc, cooked_constraints, is_internal);
 
-	/*
-	 * If there's a special on-commit action, remember it
-	 */
-	if (oncommit != ONCOMMIT_NOOP)
-		register_on_commit_action(relid, oncommit);
+	/* global temp table register action when storage init */
+	if (relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		/*
+		 * If there's a special on-commit action, remember it
+		 */
+		if (oncommit != ONCOMMIT_NOOP)
+			register_on_commit_action(relid, oncommit);
+	}
 
 	/*
 	 * ok, the relation has been cataloged, so close our relations and return
@@ -1937,6 +1959,13 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/* We allow to drop global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(rel->rd_node))
+			elog(ERROR, "can not drop relation when other backend attached this global temp table");
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3165,7 +3194,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3177,7 +3206,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3223,8 +3252,13 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
+
+		/* truncate global temp table only need RowExclusiveLock */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3257,6 +3291,8 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
+	bool		truncate_toastrel = false;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3265,23 +3301,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 3e59e64..9705b1d 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -52,6 +52,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -877,6 +878,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))
+			elog(ERROR, "can not create index when have other 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 */
+		if (!gtt_storage_attached(heapRelation->rd_node.relNode))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -2045,6 +2066,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.
@@ -2651,6 +2679,11 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	/* update index stats into localhash and rel_rd_rel for global temp table */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		is_gtt = true;
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2736,21 +2769,35 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
+		if (is_gtt)
+			rel->rd_rel->relpages = (int32) relpages;
+		else if (rd_rel->relpages != (int32) relpages)
 		{
 			rd_rel->relpages = (int32) relpages;
 			dirty = true;
 		}
-		if (rd_rel->reltuples != (float4) reltuples)
+
+		if (is_gtt)
+			rel->rd_rel->reltuples = (float4) reltuples;
+		else if (rd_rel->reltuples != (float4) reltuples)
 		{
 			rd_rel->reltuples = (float4) reltuples;
 			dirty = true;
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+
+		if (is_gtt)
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+		else if (rd_rel->relallvisible != (int32) relallvisible)
 		{
 			rd_rel->relallvisible = (int32) relallvisible;
 			dirty = true;
 		}
+
+		if (is_gtt)
+		{
+			up_gtt_relstats(rel, relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
+		}
 	}
 
 	/*
@@ -2864,6 +2911,12 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, index, val);
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		if (!gtt_storage_attached(indexRelation->rd_node.relNode))
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3459,6 +3512,15 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 				 errmsg("cannot reindex temporary tables of other sessions")));
 
 	/*
+	 * Because global temp table cannot change relfilenode
+	 * no support reindex on global temp table yet.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(iRel))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot reindex global temporary tables")));
+
+	/*
 	 * Also check for active uses of the index in the current transaction; we
 	 * don't want to reindex underneath an open indexscan.
 	 */
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index e70243a..301da79 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -647,6 +647,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		/* global temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp relations in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index fddfbf1..671c614 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -26,6 +26,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -75,7 +76,7 @@ static PendingRelDelete *pendingDeletes = NULL; /* head of linked list */
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -85,6 +86,8 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		/* global temp table use same storage strategy as local temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -117,6 +120,10 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/* remember global temp table storage info to localhash */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel)
+		remember_gtt_storage_info(rnode, rel);
+
 	return srel;
 }
 
@@ -486,8 +493,15 @@ smgrDoPendingDeletes(bool isCommit)
 		smgrdounlinkall(srels, nrels, false);
 
 		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* clean global temp table flags when transaction commit or rollback */
+			if (SmgrIsTemp(srels[i]) && 
+				gtt_storage_attached(srels[i]->smgr_rnode.node.relNode))
+				forget_gtt_storage_info(srels[i]->smgr_rnode.node.relNode);
+		}
+
 		pfree(srels);
 	}
 }
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..f489cd3
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1129 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  code to create and destroy physical storage for global temparary table
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/table.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_relstats_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+typedef struct gtt_ctl_data
+{
+	LWLock			lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct
+{
+	RelFileNode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+	Oid			relid;
+
+	Oid			spcnode;
+	/* pg_class stat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+	char		relkind;
+	bool		on_commit_delete;
+
+	/* pg_statistic */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(RelFileNode rnode);
+static void gtt_storage_checkout(RelFileNode rnode, bool skiplock);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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 || entry->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		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;
+		int			i;
+
+		rnode.spcNode = entry->spcnode;
+		rnode.dbNode = MyDatabaseId;
+		rnode.relNode = entry->relid;
+
+		gtt_storage_checkout(rnode, false);
+
+		Assert(TransactionIdIsNormal(entry->relfrozenxid));
+		remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+
+		for (i = 0; i < entry->natts; i++)
+		{
+			if (entry->att_stat_tups[i])
+			{
+				heap_freetuple(entry->att_stat_tups[i]);
+				entry->att_stat_tups[i] = NULL;
+			}
+		}
+	}
+
+	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.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (gtt_storage_local_hash == NULL)
+		return false;
+
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_FIND, &found);
+
+	if (!found)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	if (relpages)
+		*relpages = entry->relpages;
+
+	if (reltuples)
+		*reltuples = entry->reltuples;
+
+	if (relallvisible)
+		*relallvisible = entry->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = entry->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = entry->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	MemoryContext			oldcontext;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+						(void *) &reloid, HASH_FIND, &found);
+
+	if (!found)
+		return;
+
+	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;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	HeapTuple	tuple;
+	Relation	rel = NULL;
+	int		attnum = PG_GETARG_INT32(1);
+	Oid		reloid = PG_GETARG_OID(0);
+	char		rel_persistence;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	  tupdesc;
+	MemoryContext oldcontext;
+	Tuplestorestate *tupstore;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = table_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+
+	table_close(rel, NoLock);
+	table_close(pg_tatistic, AccessShareLock);
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate	*tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Relation	rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	if (get_gtt_relstats(reloid,
+						&relpages, &reltuples, &relallvisible,
+						&relfrozenxid, &relminmxid))
+	{
+		Datum	values[5];
+		bool	isnull[5];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = Int32GetDatum(relpages);
+		values[1] = Float4GetDatum((float4)reltuples);
+		values[2] = Int32GetDatum(relallvisible);
+		values[3] = UInt32GetDatum(relfrozenxid);
+		values[4] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+
+	tuplestore_donestoring(tupstore);
+	table_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(rel->rd_node);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	table_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index c9e75f4..2adde62 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM 
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c4420dd..977a984 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -586,14 +587,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 		 * pg_statistic for columns we didn't process, we leave them alone.)
 		 */
 		update_attstats(RelationGetRelid(onerel), inh,
-						attr_cnt, vacattrstats);
+						attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
 
 		for (ind = 0; ind < nindexes; ind++)
 		{
 			AnlIndexData *thisdata = &indexdata[ind];
 
 			update_attstats(RelationGetRelid(Irel[ind]), false,
-							thisdata->attr_cnt, thisdata->vacattrstats);
+							thisdata->attr_cnt, thisdata->vacattrstats,
+							RelationGetRelPersistence(Irel[ind]));
 		}
 
 		/*
@@ -1456,7 +1458,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
  *		by taking a self-exclusive lock on the relation in analyze_rel().
  */
 static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
 {
 	Relation	sd;
 	int			attno;
@@ -1558,31 +1560,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/* Update column statistic to localhash, not catalog */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index e9d7a7f..3088e26 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -391,6 +391,12 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/* not support cluster global temp table yet */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("not support cluster global temporary tables yet")));
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 52ce02f..3d3f7dc 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2583,6 +2583,16 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 			!isTempNamespace(classtuple->relnamespace))
 			continue;
 
+		/* not support reindex on global temp table, so skip it */
+		if (classtuple->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			ereport(WARNING,
+				(errmsg("global temp table \"%s.%s\" skip reindexed",
+					get_namespace_name(get_rel_namespace(relid)),
+					get_rel_name(relid))));
+			continue;
+		}
+
 		/* Check user/system classification, and optionally skip */
 		if (objectKind == REINDEX_OBJECT_SYSTEM &&
 			!IsSystemClass(relid, classtuple))
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 329ab84..dfa257c 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -96,7 +96,8 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 	 * transaction.
 	 */
 	relpersistence = get_rel_persistence(relid);
-	if (relpersistence == RELPERSISTENCE_TEMP)
+	if (relpersistence == RELPERSISTENCE_TEMP ||
+		relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
 
 	/* Check permissions. */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..44f350d 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -94,7 +94,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +108,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -222,7 +223,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* now initialize the sequence's data */
 	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -327,7 +328,7 @@ ResetSequence(Oid seq_relid)
 	/*
 	 * Insert the modified tuple into the new storage file.
 	 */
-	fill_seq_with_data(seq_rel, tuple);
+	fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -340,17 +341,21 @@ ResetSequence(Oid seq_relid)
  * Initialize a sequence's relation with the specified tuple as content
  */
 static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 	sequence_magic *sm;
 	OffsetNumber offnum;
+	bool lockBuffer = false;
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ReadBuffer(rel, P_NEW);
-	Assert(BufferGetBlockNumber(buf) == 0);
+	if (buf == InvalidBuffer)
+	{
+		buf = ReadBuffer(rel, P_NEW);
+		Assert(BufferGetBlockNumber(buf) == 0);
+		lockBuffer = true;
+	}
 
 	page = BufferGetPage(buf);
 
@@ -360,7 +365,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* Now insert sequence tuple */
 
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+	if (lockBuffer)
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +416,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	END_CRIT_SECTION();
 
-	UnlockReleaseBuffer(buf);
+	if (lockBuffer)
+		UnlockReleaseBuffer(buf);
 }
 
 /*
@@ -502,7 +509,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * Insert the modified tuple into the new storage file.
 		 */
-		fill_seq_with_data(seqrel, newdatatuple);
+		fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
 	}
 
 	/* process OWNED BY if given */
@@ -1178,6 +1185,25 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
 	LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
 
 	page = BufferGetPage(*buf);
+	if (GlobalTempRelationPageIsNotInitialized(rel, page))
+	{
+		/* Initialize sequence for global temporary tables */
+		Datum		value[SEQ_COL_LASTCOL] = {0};
+		bool		null[SEQ_COL_LASTCOL] = {false};
+		HeapTuple	tuple;
+		int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+		/*
+		 * last_value from pg_sequence.seqstart
+		 * log_cnt = 0
+		 * is_called = false
+		 */
+		value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+		tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+		fill_seq_with_data(rel, tuple, *buf);
+		heap_freetuple(tuple);
+	}
 	sm = (sequence_magic *) PageGetSpecialPointer(page);
 
 	if (sm->magic != SEQ_MAGIC)
@@ -1954,3 +1980,23 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 30b72b6..cc5b991 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -552,6 +553,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 								  Relation partitionTbl);
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static bool has_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -597,6 +599,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	bool		has_oncommit_clause = false;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -607,8 +610,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Check consistency of arguments
 	 */
+	/* global temp table same as local temp table */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !(stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+			 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -638,7 +643,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	/* global temp table same as local temp table */
+	if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+		 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -739,6 +746,57 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* global temp table */
+	has_oncommit_clause = has_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* inherit table or parition table inherit on commit property from parent table*/
+		if (inheritOids && stmt->oncommit == ONCOMMIT_NOOP && !has_oncommit_clause)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			if (RELATION_GTT_ON_COMMIT_DELETE(relation))
+				stmt->oncommit = ONCOMMIT_DELETE_ROWS;
+			else
+				stmt->oncommit = ONCOMMIT_PRESERVE_ROWS;
+			table_close(relation, NoLock);
+		}
+
+		if (has_oncommit_clause)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "can not defeine global temp table with on commit and with clause at same time");
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temp table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (has_oncommit_clause)
+		elog(ERROR, "regular table cannot specifie on_commit_delete_rows");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1800,7 +1858,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);
@@ -3359,6 +3418,13 @@ RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal, bo
 	 * specially.
 	 */
 	targetrelation = relation_open(myrelid, is_index ? ShareUpdateExclusiveLock : AccessExclusiveLock);
+
+	if (RELATION_IS_GLOBAL_TEMP(targetrelation))
+	{
+		if (is_other_backend_use_gtt(targetrelation->rd_node))
+			elog(ERROR, "can not rename relation when other backend attached this global temp table");
+	}
+
 	namespaceId = RelationGetNamespace(targetrelation);
 
 	/*
@@ -3534,6 +3600,13 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(rel->rd_node))
+			elog(ERROR, "can not alter relation when other backend attached this global temp table");
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -8099,6 +8172,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),
@@ -8138,6 +8218,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;
 	}
 
 	/*
@@ -13174,7 +13260,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,
@@ -14581,7 +14667,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",
@@ -17221,3 +17309,20 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
 		table_close(rel, NoLock);
 	}
 }
+
+static bool
+has_oncommit_option(List *options)
+{
+	ListCell   *listptr;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (pg_strcasecmp(def->defname, "on_commit_delete_rows") == 0)
+			return true;
+	}
+
+	return false;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index d625d17..69ad24f 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/vacuum.h"
@@ -1217,6 +1218,17 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	 /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		is_gtt = true;
+		up_gtt_relstats(relation,
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1230,17 +1242,26 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relpages = (int32) num_pages;
+	else if (pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (is_gtt)
+		relation->rd_rel->reltuples = (float4) num_tuples;
+	else if (pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	else if (pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1285,7 +1306,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1296,7 +1318,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1394,6 +1417,10 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/* global temp table relstats not in pg_class */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1451,6 +1478,25 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/*
+	 * Global temp table get frozenxid from MyProc
+	 * to avoid the vacuum truncate clog that gtt need.
+	 */
+	if (max_active_gtt > 0)
+	{
+		TransactionId oldest_gtt_frozenxid =
+			list_all_session_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid) &&
+			TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+		{
+			ereport(WARNING,
+				(errmsg("global temp table oldest FrozenXid is far in the past"),
+				 errhint("please truncate them or kill those sessions that use them.")));
+			newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 8286d9c..c3992a4 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -591,6 +591,8 @@ static void
 set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 						  RangeTblEntry *rte)
 {
+	char relpersistence;
+
 	/*
 	 * The flag has previously been initialized to false, so we can just
 	 * return if it becomes clear that we can't safely set it.
@@ -618,7 +620,11 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			/* global temp table is same as local temp table */
+			relpersistence = get_rel_persistence(rte->relid);
+
+			if (relpersistence == RELPERSISTENCE_TEMP ||
+				relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index d6f2153..310a9e2 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6312,7 +6312,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
+	/* global temp table is same as local temp table */
 	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 748bebf..a5ddbcd 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2579,6 +2579,11 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("materialized views must not use temporary tables or views")));
 
+		if (is_query_using_gtt(query))
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("materialized views must not use global temporary tables or views")));
+
 		/*
 		 * A materialized view would either need to save parameters for use in
 		 * maintaining/loading the data or prohibit them entirely.  The latter
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ba5916b..0ee6931 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3290,17 +3290,11 @@ OptTemp:	TEMPORARY					{ $$ = RELPERSISTENCE_TEMP; }
 			| LOCAL TEMP				{ $$ = RELPERSISTENCE_TEMP; }
 			| GLOBAL TEMPORARY
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
-					$$ = RELPERSISTENCE_TEMP;
+					$$ = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
-					$$ = RELPERSISTENCE_TEMP;
+					$$ = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED					{ $$ = RELPERSISTENCE_UNLOGGED; }
 			| /*EMPTY*/					{ $$ = RELPERSISTENCE_PERMANENT; }
@@ -11588,19 +11582,13 @@ OptTempTableName:
 				}
 			| GLOBAL TEMPORARY opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED opt_table qualified_name
 				{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b875a50..123945b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index ee2d2b5..9c9abaa 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 6d1f28c..ed837d1 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2086,6 +2086,11 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* autovacuum skip vacuum global temp table */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2152,7 +2157,9 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		/* autovacuum skip vacuum global temp table */
+		if (classForm->relpersistence == RELPERSISTENCE_TEMP || 
+			classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index aba3960..3c4b96c 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -52,6 +53,7 @@
 #include "utils/rel.h"
 #include "utils/resowner_private.h"
 #include "utils/timestamp.h"
+#include "utils/guc.h"
 
 
 /* Note: these two macros only work on shared buffers, not local ones! */
@@ -432,7 +434,7 @@ ForgetPrivateRefCountEntry(PrivateRefCountEntry *ref)
 static Buffer ReadBuffer_common(SMgrRelation reln, char relpersistence,
 								ForkNumber forkNum, BlockNumber blockNum,
 								ReadBufferMode mode, BufferAccessStrategy strategy,
-								bool *hit);
+								bool *hit, Relation rel);
 static bool PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy);
 static void PinBuffer_Locked(BufferDesc *buf);
 static void UnpinBuffer(BufferDesc *buf, bool fixOwner);
@@ -664,7 +666,8 @@ ReadBufferExtended(Relation reln, ForkNumber forkNum, BlockNumber blockNum,
 	 */
 	pgstat_count_buffer_read(reln);
 	buf = ReadBuffer_common(reln->rd_smgr, reln->rd_rel->relpersistence,
-							forkNum, blockNum, mode, strategy, &hit);
+							forkNum, blockNum, mode, strategy, &hit,
+							reln);
 	if (hit)
 		pgstat_count_buffer_hit(reln);
 	return buf;
@@ -692,7 +695,7 @@ ReadBufferWithoutRelcache(RelFileNode rnode, ForkNumber forkNum,
 	Assert(InRecovery);
 
 	return ReadBuffer_common(smgr, RELPERSISTENCE_PERMANENT, forkNum, blockNum,
-							 mode, strategy, &hit);
+							 mode, strategy, &hit, NULL);
 }
 
 
@@ -704,7 +707,8 @@ ReadBufferWithoutRelcache(RelFileNode rnode, ForkNumber forkNum,
 static Buffer
 ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 				  BlockNumber blockNum, ReadBufferMode mode,
-				  BufferAccessStrategy strategy, bool *hit)
+				  BufferAccessStrategy strategy, bool *hit,
+				  Relation rel)
 {
 	BufferDesc *bufHdr;
 	Block		bufBlock;
@@ -719,6 +723,15 @@ ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 
 	isExtend = (blockNum == P_NEW);
 
+	/* create storage when first read data page for gtt */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(isExtend || blockNum == 0) &&
+		forkNum == MAIN_FORKNUM &&
+		!gtt_storage_attached(smgr->smgr_rnode.node.relNode))
+	{
+		RelationCreateStorage(smgr->smgr_rnode.node, relpersistence, rel);
+	}
+
 	TRACE_POSTGRESQL_BUFFER_READ_START(forkNum, blockNum,
 									   smgr->smgr_rnode.node.spcNode,
 									   smgr->smgr_rnode.node.dbNode,
@@ -2809,6 +2822,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(relation->rd_node.relNode))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d5..7adb969 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -21,6 +21,7 @@
 #include "access/nbtree.h"
 #include "access/subtrans.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, active_gtt_shared_hash_size());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* global temporary table */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index c3adb2e..eb95e5f 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -62,6 +62,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -4087,3 +4088,77 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temp table.
+ */
+int
+list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct *arrayP = procArray;
+	TransactionId result = InvalidTransactionId;
+	int			index;
+	int			flags = 0;
+	int			i = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+		*n = 0;
+	}
+
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		volatile PGXACT *pgxact = &allPgXact[pgprocno];
+
+		if (pgxact->vacuumFlags & flags)
+			continue;
+
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->session_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->session_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->session_gtt_frozenxid, result))
+				result = proc->session_gtt_frozenxid;
+
+			if (max_size > 0)
+			{
+				pids[i] = proc->pid;
+				xids[i] = proc->session_gtt_frozenxid;
+				i++;
+			}
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (max_size > 0)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 32df8c8..e4e1125 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c
index 85b7115..f5eae8c 100644
--- a/src/backend/storage/smgr/md.c
+++ b/src/backend/storage/smgr/md.c
@@ -646,6 +646,12 @@ mdread(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
 		 */
 		if (zero_damaged_pages || InRecovery)
 			MemSet(buffer, 0, BLCKSZ);
+		else if(SmgrIsTemp(reln) && blocknum == 0 && forknum == MAIN_FORKNUM)
+		{
+			/* global temp table init btree meta page */
+			MemSet(buffer, 0, BLCKSZ);
+			mdwrite(reln, forknum, blocknum, buffer, true);
+		}
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_DATA_CORRUPTED),
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 7c6f057..eb10cf2 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -112,6 +112,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -4578,12 +4579,25 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -4708,15 +4722,27 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6098,6 +6124,7 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	{
 		/* Simple variable --- look to stats for the underlying table */
 		RangeTblEntry *rte = planner_rt_fetch(index->rel->relid, root);
+		char	rel_persistence = get_rel_persistence(rte->relid);
 
 		Assert(rte->rtekind == RTE_RELATION);
 		relid = rte->relid;
@@ -6115,6 +6142,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6126,6 +6160,8 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	}
 	else
 	{
+		char	rel_persistence = get_rel_persistence(index->indexoid);
+
 		/* Expression --- maybe there are stats for the index itself */
 		relid = index->indexoid;
 		colnum = 1;
@@ -6141,6 +6177,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7054,6 +7097,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		/* attempt to lookup stats in relation for this index column */
 		if (attnum != 0)
 		{
+			char	rel_persistence = get_rel_persistence(rte->relid);
+
 			/* Simple variable -- look to stats for the underlying table */
 			if (get_relation_stats_hook &&
 				(*get_relation_stats_hook) (root, rte, attnum, &vardata))
@@ -7066,6 +7111,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7078,6 +7131,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		}
 		else
 		{
+			char	rel_persistence = get_rel_persistence(index->indexoid);
+
 			/*
 			 * Looks like we've found an expression column in the index. Let's
 			 * see if there's any stats for it.
@@ -7097,6 +7152,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 1e3e6d3..345e3e9 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -2878,6 +2879,18 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index df025a5..1f733f4 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -63,6 +63,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1124,6 +1125,28 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				/*
+				 * For global temp table, all backend can use
+				 * this relation, so rd_islocaltemp always true.
+				 */
+				relation->rd_islocaltemp = true;
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -3313,6 +3336,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;
@@ -3427,6 +3454,9 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
 
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+		elog(ERROR, "global temp table does not allow setting new relfilenode");
+
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
@@ -3467,7 +3497,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 e44f71e..44c86c8 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -139,6 +139,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,
@@ -1992,6 +2004,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 799b698..b98d396 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15583,6 +15583,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	{
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
+		char		*table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15634,9 +15635,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 			binary_upgrade_set_pg_class_oids(fout, q,
 											 tbinfo->dobj.catId.oid, false);
 
+		if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED)
+			table_type = "UNLOGGED ";
+		else if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			table_type = "GLOBAL TEMPORARY ";
+		else
+			table_type = "";
+
 		appendPQExpBuffer(q, "CREATE %s%s %s",
-						  tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ?
-						  "UNLOGGED " : "",
+						  table_type,
 						  reltypename,
 						  qualrelname);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index f3c7eb9..28134e2 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3723,7 +3723,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 052d98b..f35d9bf 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1003,6 +1003,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2326,6 +2328,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2534,6 +2539,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index a12fc1f..78958e4 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -165,6 +165,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index fcf2a12..ba0bd5f 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5484,6 +5484,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4191',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4192',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{relid,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4193',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4194',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 048003c..af48cdf 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -19,7 +19,7 @@
 #include "storage/smgr.h"
 #include "utils/relcache.h"
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationTruncate(Relation rel, BlockNumber nblocks);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..eacc214
--- /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 bool 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 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 870ecb5..92c590e 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 8fda8e4..172eae9 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_TBM,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_SXACT,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 5b407e6..f13250a 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -117,6 +117,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId session_gtt_frozenxid;	/* session level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ce93ace..0f7262e 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -281,6 +281,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 44ed04d..3f9720d 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -277,6 +277,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -535,7 +536,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
@@ -602,6 +604,17 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..50ca9ac
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,7 @@
+reset search_path;
+drop schema gtt cascade;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..a477917
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,197 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+ERROR:  cannot create indexes on global temporary tables using concurrent mode
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+ERROR:  not support cluster global temporary tables yet
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ok
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  regular table cannot specifie on_commit_delete_rows
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  can not defeine global temp table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test;
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  referenced relation "products" is not a global temp table
+-- ERROR
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  referenced relation "products" is not a global temp table
+-- ERROR
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ALL ERROR
+create index idx_err on gtt1 using hash (a);
+ERROR:  only support btree index on global temp table
+create index idx_err on gtt1 using gist (a);
+ERROR:  data type integer has no default operator class for access method "gist"
+HINT:  You must specify an operator class for the index or define a default operator class for the data type.
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+ERROR:  only support btree index on global temp table
+create index idx_tmp_t0_1 on tmp_t0 using gist (c0);
+ERROR:  only support btree index on global temp table
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 15 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..30d8a7b
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,84 @@
+set search_path=gtt,sys;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..850ef3e
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,161 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          475136 |                 974848
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |           409600 |        409600 |                 409600
+(2 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..9fe5fd4
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,9 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..b258b7c
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,76 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+(1 row)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+(1 row)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 70e1e2f..30cf4bd 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1348,6 +1348,93 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d33a4e1..643afe4 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..f3cf710
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,6 @@
+
+
+reset search_path;
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..cfc1879
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,163 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ok
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test;
+
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ERROR
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ERROR
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ALL ERROR
+create index idx_err on gtt1 using hash (a);
+create index idx_err on gtt1 using gist (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_1 on tmp_t0 using gist (c0);
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d7d81de
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,42 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..5203c2b
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,76 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..2f4d883
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,16 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..f041892
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,42 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
#80Pavel Stehule
pavel.stehule@gmail.com
In reply to: 曾文旌(义从) (#79)
Re: [Proposal] Global temporary tables

čt 23. 1. 2020 v 17:28 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
napsal:

2020年1月22日 下午1:29,曾文旌(义从) <wenjing.zwj@alibaba-inc.com> 写道:

2020年1月21日 下午1:43,Pavel Stehule <pavel.stehule@gmail.com> 写道:

Hi

I have a free time this evening, so I will check this patch

I have a one question

+ /* 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;
+ }

Isbn't possible to fill the rd_rel structure too, so this branching can be
reduced?

I'll make some improvements to optimize this part of the code.

I'm trying to improve this part of the implementation in
global_temporary_table_v7-pg13.patch
Please check my patch and give me feedback.

It is looking better, still there are some strange things (I didn't tested
functionality yet)

  elog(ERROR, "invalid relpersistence: %c",
  relation->rd_rel->relpersistence);
@@ -3313,6 +3336,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:

+ rel->rd_islocaltemp = true; <<<<<<< if this is valid, then the name of
field "rd_islocaltemp" is not probably best

regards

Pavel

Show quoted text

Thanks

Wenjing

Regards

Pavel

po 20. 1. 2020 v 17:27 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
napsal:

2020年1月20日 上午1:32,Erik Rijkers <er@xs4all.nl> 写道:

On 2020-01-19 18:04, 曾文旌(义从) wrote:

2020年1月14日 下午9:20,Pavel Stehule <pavel.stehule@gmail.com> 写道:
út 14. 1. 2020 v 14:09 odesílatel 曾文旌(义从) <

wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:

[global_temporary_table_v4-pg13.patch ]

Hi,

This patch doesn't quiet apply for me:

patching file src/backend/access/common/reloptions.c
patching file src/backend/access/gist/gistutil.c
patching file src/backend/access/hash/hash.c
Hunk #1 succeeded at 149 (offset 3 lines).
patching file src/backend/access/heap/heapam_handler.c
patching file src/backend/access/heap/vacuumlazy.c
patching file src/backend/access/nbtree/nbtpage.c
patching file src/backend/access/table/tableam.c
patching file src/backend/access/transam/xlog.c
patching file src/backend/catalog/Makefile
Hunk #1 FAILED at 44.
1 out of 1 hunk FAILED -- saving rejects to file

src/backend/catalog/Makefile.rej

[...]
(The rest applies without errors)

src/backend/catalog/Makefile.rej contains:

------------------------
--- src/backend/catalog/Makefile
+++ 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
------------------------

Can you have a look?

I updated the code and remade the patch.
Please give me feedback if you have any more questions.

thanks,

Erik Rijkers

#81Robert Haas
robertmhaas@gmail.com
In reply to: Tomas Vondra (#58)
Re: [Proposal] Global temporary tables

On Sat, Jan 11, 2020 at 8:51 PM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

I proposed just ignoring those new indexes because it seems much simpler
than alternative solutions that I can think of, and it's not like those
other solutions don't have other issues.

+1.

For example, I've looked at the "on demand" building as implemented in
global_private_temp-8.patch, I kinda doubt adding a bunch of index build
calls into various places in index code seems somewht suspicious.

+1. I can't imagine that's a safe or sane thing to do.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#82Konstantin Knizhnik
k.knizhnik@postgrespro.ru
In reply to: 曾文旌(义从) (#79)
Re: [Proposal] Global temporary tables

On 23.01.2020 19:28, 曾文旌(义从) wrote:

I'm trying to improve this part of the implementation in
global_temporary_table_v7-pg13.patch
Please check my patch and give me feedback.

Thanks

Wenjing

Below is my short review of the patch:

+    /*
+     * For global temp table only
+     * use AccessExclusiveLock for ensure safety
+     */
+    {
+        {
+            "on_commit_delete_rows",
+            "global temp table on commit options",
+            RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+            ShareUpdateExclusiveLock
+        },
+        true
+    },

The comment seems to be confusing: it says about AccessExclusiveLock but
actually uses ShareUpdateExclusiveLock.

- 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)));

It is actually equivalent to:

Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^
TransactionIdIsNormal(onerel->rd_rel->relfrozenxid);
Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^
MultiXactIdIsValid(onerel->rd_rel->relminmxid));

+    /* clean temp relation files */
+    if (max_active_gtt > 0)
+        RemovePgTempFiles();
+
      /*

I wonder why do we need some special check for GTT here.
From my point of view cleanup at startup of local storage of temp
tables should be performed in the same way for local and global temp tables.

-    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;
+    }
+

Why do we need to do it for GTT?
Did you check that there will be no problems with GTT in case of XID
wraparound?
Right now if you create temp table and keep session open, then it will
block XID wraparound.

+    /* 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");
+    }
+

Here we once again introduce incompatibility with normal (permanent) tables.
Assume that DBA or programmer need to change format of GTT. But there
are some active sessions which have used this GTT sometime in the past.
We will not be able to drop this GTT until all this sessions are terminated.
I do not think that it is acceptable behaviour.

+        LOCKMODE    lockmode = AccessExclusiveLock;
+
+        /* truncate global temp table only need RowExclusiveLock */
+        if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+            lockmode = RowExclusiveLock;

What are the reasons of using RowExclusiveLock for GTT instead of
AccessExclusiveLock?
Yes, GTT data is access only by one backend so no locking here seems to
be needed at all.
But I wonder what are the motivations/benefits of using weaker lock
level here?
There should be no conflicts in any case...

+        /* We allow to create index on global temp table only this 
session use it */
+        if (is_other_backend_use_gtt(heapRelation->rd_node))
+            elog(ERROR, "can not create index when have other backend 
attached this global temp table");
+

The same argument as in case of dropping GTT: I do not think that
prohibiting DLL operations on GTT used by more than one backend is bad idea.

+    /* 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))));
+

Why do we need to prohibit foreign key constraint on GTT?

+    /*
+     * 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;
+        }
+    }
+

As far as I understand, content of GTT will never be processes by
autovacuum.
So who will update frozenxid of GTT?
I see that up_gtt_relstats is invoked when:
- index is created on GTT
- GTT is truncated
- GTT is vacuumed
So unless GTT is explicitly vacuumed by user, its GTT is and them will
not be taken in account
when computing new frozen xid value. Autovacumm will produce this
warnings (which will ton be visible by end user and only appended to the
log).
And at some moment of time wrap around happen and if there still some
old active GTT, we will get incorrect results.

--

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

#83Konstantin Knizhnik
k.knizhnik@postgrespro.ru
In reply to: Robert Haas (#81)
Re: [Proposal] Global temporary tables

On 23.01.2020 23:47, Robert Haas wrote:

On Sat, Jan 11, 2020 at 8:51 PM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

I proposed just ignoring those new indexes because it seems much simpler
than alternative solutions that I can think of, and it's not like those
other solutions don't have other issues.

+1.

For example, I've looked at the "on demand" building as implemented in
global_private_temp-8.patch, I kinda doubt adding a bunch of index build
calls into various places in index code seems somewht suspicious.

+1. I can't imagine that's a safe or sane thing to do.

As far as you know there are two versions of GTT implementations now.
And we are going to merge them into single patch.
But there are some principle question concerning provided functionality
which has to be be discussed:
should we prohibit DDL on GTT if there are more than one sessions using
it. It includes creation/dropping indexes, dropping table, altering table...

If the answer is "yes", then the question whether to populate new
indexes with data is no relevant at all, because such situation will not
be possible.
But in this case we will get incompatible behavior with normal
(permanent) tables and it seems to be very inconvenient from DBA point
of view:
it will be necessary to enforce all clients to close their sessions to
perform some DDL manipulations with GTT.
Some DDLs will be very difficult to implement if GTT is used by more
than one backend, for example altering table schema.

My current solution is to allow creation/droping index on GTT and
dropping table itself, while prohibit alter schema at all for GTT.
Wenjing's approach is to prohibit any DDL if GTT is used by more than
one backend.

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

#84Pavel Stehule
pavel.stehule@gmail.com
In reply to: Konstantin Knizhnik (#83)
Re: [Proposal] Global temporary tables

pá 24. 1. 2020 v 9:39 odesílatel Konstantin Knizhnik <
k.knizhnik@postgrespro.ru> napsal:

On 23.01.2020 23:47, Robert Haas wrote:

On Sat, Jan 11, 2020 at 8:51 PM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

I proposed just ignoring those new indexes because it seems much simpler
than alternative solutions that I can think of, and it's not like those
other solutions don't have other issues.

+1.

For example, I've looked at the "on demand" building as implemented in
global_private_temp-8.patch, I kinda doubt adding a bunch of index build
calls into various places in index code seems somewht suspicious.

+1. I can't imagine that's a safe or sane thing to do.

As far as you know there are two versions of GTT implementations now.
And we are going to merge them into single patch.
But there are some principle question concerning provided functionality
which has to be be discussed:
should we prohibit DDL on GTT if there are more than one sessions using
it. It includes creation/dropping indexes, dropping table, altering
table...

If the answer is "yes", then the question whether to populate new
indexes with data is no relevant at all, because such situation will not
be possible.
But in this case we will get incompatible behavior with normal
(permanent) tables and it seems to be very inconvenient from DBA point
of view:
it will be necessary to enforce all clients to close their sessions to
perform some DDL manipulations with GTT.
Some DDLs will be very difficult to implement if GTT is used by more
than one backend, for example altering table schema.

My current solution is to allow creation/droping index on GTT and
dropping table itself, while prohibit alter schema at all for GTT.
Wenjing's approach is to prohibit any DDL if GTT is used by more than
one backend.

When I create index on GTT in one session, then I don't expect creating
same index in all other sessions that uses same GTT.

But I can imagine to creating index on GTT enforces index in current
session, and for other sessions this index will be invalid to end of
session.

Regards

Pavel

Show quoted text

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

#85Konstantin Knizhnik
k.knizhnik@postgrespro.ru
In reply to: Pavel Stehule (#84)
Re: [Proposal] Global temporary tables

On 24.01.2020 12:09, Pavel Stehule wrote:

pá 24. 1. 2020 v 9:39 odesílatel Konstantin Knizhnik
<k.knizhnik@postgrespro.ru <mailto:k.knizhnik@postgrespro.ru>> napsal:

On 23.01.2020 23:47, Robert Haas wrote:

On Sat, Jan 11, 2020 at 8:51 PM Tomas Vondra
<tomas.vondra@2ndquadrant.com

<mailto:tomas.vondra@2ndquadrant.com>> wrote:

I proposed just ignoring those new indexes because it seems

much simpler

than alternative solutions that I can think of, and it's not

like those

other solutions don't have other issues.

+1.

For example, I've looked at the "on demand" building as

implemented in

global_private_temp-8.patch, I kinda doubt adding a bunch of

index build

calls into various places in index code seems somewht suspicious.

+1. I can't imagine that's a safe or sane thing to do.

As far as you know there are two versions of GTT implementations now.
And we are going to merge them into single patch.
But there are some principle question concerning provided
functionality
which has to be be discussed:
should we prohibit DDL on GTT if there are more than one sessions
using
it. It includes creation/dropping indexes, dropping table,
altering table...

If the answer is "yes", then the question whether to populate new
indexes with data is no relevant at all, because such situation
will not
be possible.
But in this case we will get incompatible behavior with normal
(permanent) tables and it seems to be very inconvenient from DBA
point
of view:
it will be necessary to enforce all clients to close their
sessions to
perform some DDL manipulations with GTT.
Some DDLs will be very difficult to implement if GTT is used by more
than one backend, for example altering table schema.

My current solution is to allow creation/droping index on GTT and
dropping table itself, while prohibit alter schema at all for GTT.
Wenjing's approach is to prohibit any DDL if GTT is used by more than
one backend.

When I create index on GTT in one session, then I don't expect
creating same index in all other sessions that uses same GTT.

But I can imagine to creating index on GTT enforces index in current
session, and for other sessions this index will be invalid to end of
session.

So there are three possible alternatives:

1. Prohibit index creation of GTT when it used by more than once session.
2. Create index and populate them with data in all sessions using this GTT.
3. Create index only in current session and do not allow to use it in
all other sessions already using this GTT (but definitely allow to use
it in new sessions).

1 is Wenjing's approach, 2 - is my approach, 3 - is your suggestion :)

I can construct the following table with pro/cons of each approach:

Approach
Compatibility with normal table
User (DBA) friendly
Complexity of implementation
Consistency
1
-
1: requires restart of all sessions to perform operation
2: requires global cache of GTT
3/: /no man, no problem
2
+
3: if index is created then it is actually needed, isn't it? 1: use
existed functionality to create index
2: if alter schema is prohibited
3
-
2: requires restart of all sessions to use created index
3: requires some mechanism for prohibiting index created after first
session access to GTT
1: can perform DDL but do no see effect of it

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

#86Pavel Stehule
pavel.stehule@gmail.com
In reply to: Konstantin Knizhnik (#85)
Re: [Proposal] Global temporary tables

pá 24. 1. 2020 v 10:43 odesílatel Konstantin Knizhnik <
k.knizhnik@postgrespro.ru> napsal:

On 24.01.2020 12:09, Pavel Stehule wrote:

pá 24. 1. 2020 v 9:39 odesílatel Konstantin Knizhnik <
k.knizhnik@postgrespro.ru> napsal:

On 23.01.2020 23:47, Robert Haas wrote:

On Sat, Jan 11, 2020 at 8:51 PM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

I proposed just ignoring those new indexes because it seems much

simpler

than alternative solutions that I can think of, and it's not like those
other solutions don't have other issues.

+1.

For example, I've looked at the "on demand" building as implemented in
global_private_temp-8.patch, I kinda doubt adding a bunch of index

build

calls into various places in index code seems somewht suspicious.

+1. I can't imagine that's a safe or sane thing to do.

As far as you know there are two versions of GTT implementations now.
And we are going to merge them into single patch.
But there are some principle question concerning provided functionality
which has to be be discussed:
should we prohibit DDL on GTT if there are more than one sessions using
it. It includes creation/dropping indexes, dropping table, altering
table...

If the answer is "yes", then the question whether to populate new
indexes with data is no relevant at all, because such situation will not
be possible.
But in this case we will get incompatible behavior with normal
(permanent) tables and it seems to be very inconvenient from DBA point
of view:
it will be necessary to enforce all clients to close their sessions to
perform some DDL manipulations with GTT.
Some DDLs will be very difficult to implement if GTT is used by more
than one backend, for example altering table schema.

My current solution is to allow creation/droping index on GTT and
dropping table itself, while prohibit alter schema at all for GTT.
Wenjing's approach is to prohibit any DDL if GTT is used by more than
one backend.

When I create index on GTT in one session, then I don't expect creating
same index in all other sessions that uses same GTT.

But I can imagine to creating index on GTT enforces index in current
session, and for other sessions this index will be invalid to end of
session.

So there are three possible alternatives:

1. Prohibit index creation of GTT when it used by more than once session.
2. Create index and populate them with data in all sessions using this GTT.
3. Create index only in current session and do not allow to use it in all
other sessions already using this GTT (but definitely allow to use it in
new sessions).

1 is Wenjing's approach, 2 - is my approach, 3 - is your suggestion :)

I can construct the following table with pro/cons of each approach:

Approach
Compatibility with normal table
User (DBA) friendly
Complexity of implementation
Consistency
1
-
1: requires restart of all sessions to perform operation
2: requires global cache of GTT
3*: *no man, no problem
2
+
3: if index is created then it is actually needed, isn't it? 1: use
existed functionality to create index
2: if alter schema is prohibited
3
-
2: requires restart of all sessions to use created index
3: requires some mechanism for prohibiting index created after first
session access to GTT
1: can perform DDL but do no see effect of it

You will see a effect of DDL in current session (where you did the change),
all other sessions should to live without any any change do reconnect or to
RESET connect

I don't like 2 - when I do index on global temp table, I don't would to
wait on indexing on all other sessions. These operations should be
maximally independent.

Regards

Pavel

Show quoted text

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

#87Konstantin Knizhnik
k.knizhnik@postgrespro.ru
In reply to: Pavel Stehule (#86)
Re: [Proposal] Global temporary tables

On 24.01.2020 15:15, Pavel Stehule wrote:

You will see a effect of DDL in current session (where you did the
change), all other sessions should to live without any any change do
reconnect or to RESET connect

Why? I found this requirement quit unnatural and contradicting to the
behavior of normal tables.
Actually one of motivation for adding global tempo tables to Postgres is
to provide compatibility with Oracle.
Although I know that Oracle design decisions were never considered as 
axioms by Postgres community,
but ni case of GTT design I think that we should take in account Oracle
approach.
And GTT in Oracle behaves exactly as in my implementation:

https://www.oracletutorial.com/oracle-basics/oracle-global-temporary-table/

It is not clear from this documentation whether index created for GTT in
one session can be used in another session which already has some data
in this GTT.
But I did experiment with install Oracle server and  can confirm that
actually works in this way.

So I do not understand why do we need to complicate our GTT
implementation in order to prohibit useful functionality and introduce
inconsistency between behavior of normal and global temp tables.

I don't like 2 - when I do index on global temp table, I don't would
to wait on indexing on all other sessions. These operations should be
maximally independent.

Nobody suggest to wait building index in all sessions.
Indexes will be constructed on demand when session access this table.
If session will no access this table at all, then index will never be
constructed.

Once again: logic of dealing with indexes in GTT is very simple.
For normal tables, indexes are initialized at the tame when them are
created.
For GTT it is not true. We have to initialize index on demand when it is
accessed first time in session.

So it has to be handled in any way.
The question is only whether we should allow creation of index for table
already populated with some data?
Actually doesn't require some additional efforts. We can use existed
build_index function which initialize index and populates it with data.
So the solution proposed for me is most natural, convenient and simplest
solution at the same time. And compatible with Oracle.

Regards

Pavel

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

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

#88Pavel Stehule
pavel.stehule@gmail.com
In reply to: Konstantin Knizhnik (#87)
Re: [Proposal] Global temporary tables

pá 24. 1. 2020 v 14:17 odesílatel Konstantin Knizhnik <
k.knizhnik@postgrespro.ru> napsal:

On 24.01.2020 15:15, Pavel Stehule wrote:

You will see a effect of DDL in current session (where you did the
change), all other sessions should to live without any any change do
reconnect or to RESET connect

Why? I found this requirement quit unnatural and contradicting to the
behavior of normal tables.
Actually one of motivation for adding global tempo tables to Postgres is
to provide compatibility with Oracle.
Although I know that Oracle design decisions were never considered as
axioms by Postgres community,
but ni case of GTT design I think that we should take in account Oracle
approach.
And GTT in Oracle behaves exactly as in my implementation:

https://www.oracletutorial.com/oracle-basics/oracle-global-temporary-table/

It is not clear from this documentation whether index created for GTT in
one session can be used in another session which already has some data in
this GTT.
But I did experiment with install Oracle server and can confirm that
actually works in this way.

So I do not understand why do we need to complicate our GTT implementation
in order to prohibit useful functionality and introduce inconsistency
between behavior of normal and global temp tables.

I don't like 2 - when I do index on global temp table, I don't would to
wait on indexing on all other sessions. These operations should be
maximally independent.

Nobody suggest to wait building index in all sessions.
Indexes will be constructed on demand when session access this table.
If session will no access this table at all, then index will never be
constructed.

Once again: logic of dealing with indexes in GTT is very simple.
For normal tables, indexes are initialized at the tame when them are
created.
For GTT it is not true. We have to initialize index on demand when it is
accessed first time in session.

So it has to be handled in any way.
The question is only whether we should allow creation of index for table
already populated with some data?
Actually doesn't require some additional efforts. We can use existed
build_index function which initialize index and populates it with data.
So the solution proposed for me is most natural, convenient and simplest
solution at the same time. And compatible with Oracle.

I cannot to evaluate your proposal, and I am sure, so you know more about
this code.

There is a question if we can allow to build local temp index on global
temp table. It is different situation. When I work with global properties
personally I prefer total asynchronous implementation of any DDL operations
for other than current session. When it is true, then I have not any
objection. For me, good enough design of any DDL can be based on catalog
change without forcing to living tables.

I see following disadvantage of your proposal. See scenario

1. I have two sessions

A - small GTT with active owner
B - big GTT with some active application.

session A will do new index - it is fast, but if creating index is forced
on B on demand (when B was touched), then this operation have to wait after
index will be created.

So I afraid build a index on other sessions on GTT when GTT tables in other
sessions will not be empty.

Regards

Pavel

Show quoted text

Regards

Pavel

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

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

#89曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Konstantin Knizhnik (#82)
Re: [Proposal] Global temporary tables

Thank you for review patch.

2020年1月24日 下午4:20,Konstantin Knizhnik <k.knizhnik@postgrespro.ru> 写道:

On 23.01.2020 19:28, 曾文旌(义从) wrote:

I'm trying to improve this part of the implementation in global_temporary_table_v7-pg13.patch
Please check my patch and give me feedback.

Thanks

Wenjing

Below is my short review of the patch:

+    /*
+     * For global temp table only
+     * use AccessExclusiveLock for ensure safety
+     */
+    {
+        {
+            "on_commit_delete_rows",
+            "global temp table on commit options",
+            RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+            ShareUpdateExclusiveLock
+        },
+        true
+    },    

The comment seems to be confusing: it says about AccessExclusiveLock but actually uses ShareUpdateExclusiveLock.

There is a problem with the comment description, I will fix it.

-    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)));

It is actually equivalent to:

Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ TransactionIdIsNormal(onerel->rd_rel->relfrozenxid);
Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ MultiXactIdIsValid(onerel->rd_rel->relminmxid));

Yes, Thank you for your points out, It's simpler.

+    /* clean temp relation files */
+    if (max_active_gtt > 0)
+        RemovePgTempFiles();
+
/*

I wonder why do we need some special check for GTT here.
From my point of view cleanup at startup of local storage of temp tables should be performed in the same way for local and global temp tables.

After oom kill, In autovacuum, the Isolated local temp table will be cleaned like orphan temporary tables. The definition of local temp table is deleted with the storage file.
But GTT can not do that. So we have the this implementation in my patch.
If you have other solutions, please let me know.

-    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;
+    }
+

Why do we need to do it for GTT?
Did you check that there will be no problems with GTT in case of XID wraparound?
Right now if you create temp table and keep session open, then it will block XID wraparound.

In my design
1 Because different sessions have different transaction information, I choose to store the transaction information of GTT in MyProc,not catalog.
2 About the XID wraparound problem, the reason is the design of the temp table storage(local temp table and global temp table) that makes it can not to do vacuum by autovacuum.
It should be completely solve at the storage level.

+    /* 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");
+    }
+

Here we once again introduce incompatibility with normal (permanent) tables.
Assume that DBA or programmer need to change format of GTT. But there are some active sessions which have used this GTT sometime in the past.
We will not be able to drop this GTT until all this sessions are terminated.
I do not think that it is acceptable behaviour.

In fact, The dba can still complete the DDL of the GTT.
I've provided a set of functions for this case.
If the dba needs to modify a GTT A(or drop GTT or create index on GTT), he needs to do:
1 Use the pg_gtt_attached_pids view to list the pids for the session that is using the GTT A.
2 Use pg_terminate_backend(pid)terminate they except itself.
3 Do alter GTT A.

+        LOCKMODE    lockmode = AccessExclusiveLock;
+
+        /* truncate global temp table only need RowExclusiveLock */
+        if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+            lockmode = RowExclusiveLock;

What are the reasons of using RowExclusiveLock for GTT instead of AccessExclusiveLock?
Yes, GTT data is access only by one backend so no locking here seems to be needed at all.
But I wonder what are the motivations/benefits of using weaker lock level here?

1 Truncate GTT deletes only the data in the session, so no need use high-level lock.
2 I think it still needs to be block by DDL of GTT, which is why I use RowExclusiveLock.

There should be no conflicts in any case...

+        /* We allow to create index on global temp table only this session use it */
+        if (is_other_backend_use_gtt(heapRelation->rd_node))
+            elog(ERROR, "can not create index when have other backend attached this global temp table");
+

The same argument as in case of dropping GTT: I do not think that prohibiting DLL operations on GTT used by more than one backend is bad idea.

The idea was to give the GTT almost all the features of a regular table with few code changes.
The current version DBA can still do all DDL for GTT, I've already described.

+    /* 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))));
+

Why do we need to prohibit foreign key constraint on GTT?

It may be possible to support FK on GTT in later versions. Before that, I need to check some code.

+    /*
+     * 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;
+        }
+    }
+

As far as I understand, content of GTT will never be processes by autovacuum.
So who will update frozenxid of GTT?
I see that up_gtt_relstats is invoked when:
- index is created on GTT
- GTT is truncated
- GTT is vacuumed
So unless GTT is explicitly vacuumed by user, its GTT is and them will not be taken in account
when computing new frozen xid value. Autovacumm will produce this warnings (which will ton be visible by end user and only appended to the log).
And at some moment of time wrap around happen and if there still some old active GTT, we will get incorrect results.

I have already described my point in previous emails.

1. The core problem is that the data contains transaction information (xid), which needs to be vacuum(freeze) regularly to avoid running out of xid.
The autovacuum supports vacuum regular table but local temp does not. autovacuum also does not support GTT.

2. However, the difference between the local temp table and the global temp table(GTT) is that
a) For local temp table: one table hava one piece of data. the frozenxid of one local temp table is store in the catalog(pg_class).
b) For global temp table: each session has a separate copy of data, one GTT may contain maxbackend frozenxid.
and I don't think it's a good idea to keep frozenxid of GTT in the catalog(pg_class).
It becomes a question: how to handle GTT transaction information?

I agree that problem 1 should be completely solved by a some feature, such as local transactions. It is definitely not included in the GTT patch.
But, I think we need to ensure the durability of GTT data. For example, data in GTT cannot be lost due to the clog being cleaned up. It belongs to problem 2.

For problem 2
If we ignore the frozenxid of GTT, when vacuum truncates the clog that GTT need, the GTT data in some sessions is completely lost.
Perhaps we could consider let aotuvacuum terminate those sessions that contain "too old" data,
But It's not very friendly, so I didn't choose to implement it in the first version.
Maybe you have a better idea.

Wenjing

Show quoted text

--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com <http://www.postgrespro.com/&gt;
The Russian Postgres Company

#90Konstantin Knizhnik
k.knizhnik@postgrespro.ru
In reply to: Pavel Stehule (#88)
Re: [Proposal] Global temporary tables

On 24.01.2020 22:39, Pavel Stehule wrote:

I cannot to evaluate your proposal, and I am sure, so you know more
about this code.

There is a question if we can allow to build local temp index on
global temp table. It is different situation. When I work with global
properties personally I prefer total asynchronous implementation of
any DDL operations for other than current session. When it is true,
then I have not any objection. For me, good enough design of any DDL
can be based on catalog change without forcing to living tables.

From my point of view there are two difference uses cases of temp tables:
1. Backend needs some private data source which is specific to this
session and has no relation with activities of other sessions.
2. We need a table  containing private session data, but which is used
in the same way by all database users.

In the first case current Postgres temp tables works well (if we forget
for a moment about all known issues related with temp tables).
Global temp tables are used to address the second scenario.  Assume that
we write some stored procedure or implement some business logic  outside
database and
what to perform some complex analtic query which requires tepmp table
for storing intermediate results. In this case we can create GTT with
all needed index at the moment of database initialization
and do not perform any extra DDL during query execution. If will prevent
catalog bloating and makes execution of query more efficient.

I do not see any reasons to allow build local indexes for global table.
Yes,it can happen that some session will have small amount of data in
particular GTT and another - small amount of data in this table. But if
access pattern is the same  (and nature of GTT assumes it), then index
in either appreciate, either useless in both cases.

I see following disadvantage of your proposal. See scenario

1. I have two sessions

A - small GTT with active owner
B - big GTT with some active application.

session A will do new index - it is fast, but if creating index is
forced on B on demand (when B was touched), then this operation have
to wait after index will be created.

So I afraid build a index on other sessions on GTT when GTT tables in
other sessions will not be empty.

Yes, it is true. But is is not the most realistic scenario from my point
of view.
As I explained above, GTT should be used when we need temporary storage
accessed in the same way by all clients.
If (as with normal tables) at some moment of time DBA realizes, that
efficient execution of some queries needs extra indexes,
then it should be able to do it. It is very inconvenient and unnatural
to prohibit DBA to do it until all sessions using this GTT are closed
(it may never happen)
or require all sessions to restart to be able to use this index.

So it is possible to imagine different scenarios of working with GTTs.
But from my point of view the only non-contradictory model of their
behavior is to make it compatible with normal tables.
And do not forget about compatibility with Oracle. Simplifying of
porting existed applications from Oracle to Postgres  may be the
main motivation of adding GTT to Postgres. And making them incompatible
with Oracle will be very strange.

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

#91Konstantin Knizhnik
k.knizhnik@postgrespro.ru
In reply to: 曾文旌(义从) (#89)
Re: [Proposal] Global temporary tables

On 25.01.2020 18:15, 曾文旌(义从) wrote:

I wonder why do we need some special check for GTT here.

From my point of view cleanup at startup of local storage of temp
tables should be performed in the same way for local and global temp
tables.

After oom kill, In autovacuum, the Isolated local temp table will be
cleaned like orphan temporary tables. The definition of local temp
table is deleted with the storage file.
But GTT can not do that. So we have the this implementation in my patch.
If you have other solutions, please let me know.

I wonder if it is possible that autovacuum or some other Postgres
process is killed by OOM and postmaster is not noticing it can doens't
restart Postgres instance?
as far as I know, crash of any process connected to Postgres shared
memory (and autovacuum definitely has such connection) cause Postgres
restart.

In my design
1 Because different sessions have different transaction information, I
choose to store the transaction information of GTT in MyProc,not catalog.
2 About the XID wraparound problem, the reason is the design of the
temp table storage(local temp table and global temp table) that makes
it can not to do vacuum by autovacuum.
It should be completely solve at the storage level.

My point of view is that vacuuming of temp tables is common problem for
local and global temp tables.
So it has to be addressed in the common way and so we should not try to
fix this problem only for GTT.

In fact, The dba can still complete the DDL of the GTT.
I've provided a set of functions for this case.
If the dba needs to modify a GTT A(or drop GTT or create index on
GTT), he needs to do:
1 Use the pg_gtt_attached_pids view to list the pids for the session
that is using the GTT A.
2 Use pg_terminate_backend(pid)terminate they except itself.
3 Do alter GTT A.

IMHO forced terminated of client sessions is not acceptable solution.
And it is not an absolutely necessary requirement.
So from my point of view we should not add such limitations to GTT design.

What are the reasons of using RowExclusiveLock for GTT instead of
AccessExclusiveLock?
Yes, GTT data is access only by one backend so no locking here seems
to be needed at all.
But I wonder what are the motivations/benefits of using weaker lock
level here?

1 Truncate GTT deletes only the data in the session, so no need use
high-level lock.
2 I think it still needs to be block by DDL of GTT, which is why I use
RowExclusiveLock.

Sorry, I do not understand your arguments: we do not need exclusive lock
because we drop only local (private) data
but we need some kind of lock. I agree with 1) and not 2).

There should be no conflicts in any case...

+        /* We allow to create index on global temp table only this 
session use it */
+        if (is_other_backend_use_gtt(heapRelation->rd_node))
+            elog(ERROR, "can not create index when have other 
backend attached this global temp table");
+

The same argument as in case of dropping GTT: I do not think that
prohibiting DLL operations on GTT used by more than one backend is
bad idea.

The idea was to give the GTT almost all the features of a regular
table with few code changes.
The current version DBA can still do all DDL for GTT, I've already
described.

I absolutely agree with you that GTT should be given the same features
as regular tables.
The irony is that this most natural and convenient behavior is most easy
to implement without putting some extra restrictions.
Just let indexes for GTT be constructed on demand. It it can be done
using the same function used for regular index creation.

+    /* 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))));
+

Why do we need to prohibit foreign key constraint on GTT?

It may be possible to support FK on GTT in later versions. Before
that, I need to check some code.

Ok,  may be approach to prohibit everything except minimally required
functionality  is safe and reliable.
But frankly speaking I prefer different approach: if I do not see any
contradictions of new feature with existed operations
and it is passing tests, then we should  not prohibit this operations
for new feature.

I have already described my point in previous emails.

1. The core problem is that the data contains transaction information
(xid), which needs to be vacuum(freeze) regularly to avoid running out
of xid.
The autovacuum supports vacuum regular table but local temp does not.
autovacuum also does not support GTT.

2. However, the difference between the local temp table and the global
temp table(GTT) is that
a) For local temp table: one table hava one piece of data. the
frozenxid of one local temp table is store in the catalog(pg_class).
b) For global temp table: each session has a separate copy of data,
one GTT may contain maxbackend frozenxid.
and I don't think it's a good idea to keep frozenxid of GTT in the
catalog(pg_class).
It becomes a question: how to handle GTT transaction information?

I agree that problem 1 should be completely solved by a some feature,
such as local transactions. It is definitely not included in the GTT
patch.
But, I think we need to ensure the durability of GTT data. For
example, data in GTT cannot be lost due to the clog being cleaned up.
It belongs to problem 2.

For problem 2
If we ignore the frozenxid of GTT, when vacuum truncates the clog that
GTT need, the GTT data in some sessions is completely lost.
Perhaps we could consider let aotuvacuum terminate those sessions that
contain "too old" data,
But It's not very friendly, so I didn't choose to implement it in the
first version.
Maybe you have a better idea.

Sorry, I do not have better idea.
I prefer not to address this problem in first version of the patch at all.
fozen_xid of temp table is never changed unless user explicitly invoke
vacuum on it.
I do not think that anybody is doing it (because it accentually contains
temporary data which is not expected to live long time.
Certainly it is possible to imagine situation when session use GTT to
store some local data which is valid during all session life time (which
can be large enough).
But I am not sure that it is popular scenario.

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

#92Pavel Stehule
pavel.stehule@gmail.com
In reply to: Konstantin Knizhnik (#90)
Re: [Proposal] Global temporary tables

po 27. 1. 2020 v 10:11 odesílatel Konstantin Knizhnik <
k.knizhnik@postgrespro.ru> napsal:

On 24.01.2020 22:39, Pavel Stehule wrote:

I cannot to evaluate your proposal, and I am sure, so you know more about
this code.

There is a question if we can allow to build local temp index on global
temp table. It is different situation. When I work with global properties
personally I prefer total asynchronous implementation of any DDL operations
for other than current session. When it is true, then I have not any
objection. For me, good enough design of any DDL can be based on catalog
change without forcing to living tables.

From my point of view there are two difference uses cases of temp tables:
1. Backend needs some private data source which is specific to this
session and has no relation with activities of other sessions.
2. We need a table containing private session data, but which is used in
the same way by all database users.

In the first case current Postgres temp tables works well (if we forget
for a moment about all known issues related with temp tables).
Global temp tables are used to address the second scenario. Assume that
we write some stored procedure or implement some business logic outside
database and
what to perform some complex analtic query which requires tepmp table for
storing intermediate results. In this case we can create GTT with all
needed index at the moment of database initialization
and do not perform any extra DDL during query execution. If will prevent
catalog bloating and makes execution of query more efficient.

I do not see any reasons to allow build local indexes for global table.
Yes,it can happen that some session will have small amount of data in
particular GTT and another - small amount of data in this table. But if
access pattern is the same (and nature of GTT assumes it), then index in
either appreciate, either useless in both cases.

I see following disadvantage of your proposal. See scenario

1. I have two sessions

A - small GTT with active owner
B - big GTT with some active application.

session A will do new index - it is fast, but if creating index is forced
on B on demand (when B was touched), then this operation have to wait after
index will be created.

So I afraid build a index on other sessions on GTT when GTT tables in
other sessions will not be empty.

Yes, it is true. But is is not the most realistic scenario from my point
of view.
As I explained above, GTT should be used when we need temporary storage
accessed in the same way by all clients.
If (as with normal tables) at some moment of time DBA realizes, that
efficient execution of some queries needs extra indexes,
then it should be able to do it. It is very inconvenient and unnatural to
prohibit DBA to do it until all sessions using this GTT are closed (it may
never happen)
or require all sessions to restart to be able to use this index.

So it is possible to imagine different scenarios of working with GTTs.
But from my point of view the only non-contradictory model of their
behavior is to make it compatible with normal tables.
And do not forget about compatibility with Oracle. Simplifying of porting
existed applications from Oracle to Postgres may be the
main motivation of adding GTT to Postgres. And making them incompatible
with Oracle will be very strange.

I don't think so compatibility with Oracle is valid point in this case. We
need GTT, but the mechanism of index building should be designed for
Postgres, and for users.

Maybe the method proposed by you can be activated by some option like
CREATE INDEX IMMEDIATELY FOR ALL SESSION. When you use GTT without index,
then
it should to work some time more, and if you use short life sessions, then
index build can be last or almost last operation over table and can be
suboptimal.

Anyway, this behave can be changed later without bigger complications - and
now I am have strong opinion to prefer don't allow to any DDL (with index
creation) on any active GTT in other sessions.
Probably your proposal - build indexes on other sessions when GTT is
touched can share code with just modify metadata and wait on session reset
or GTT reset

Usually it is not hard problem to refresh sessions, and what I know when
you update plpgsql code, it is best practice to refresh session early.

Show quoted text

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

#93曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Robert Haas (#81)
1 attachment(s)
Re: [Proposal] Global temporary tables

2020年1月24日 上午4:47,Robert Haas <robertmhaas@gmail.com> 写道:

On Sat, Jan 11, 2020 at 8:51 PM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

I proposed just ignoring those new indexes because it seems much simpler
than alternative solutions that I can think of, and it's not like those
other solutions don't have other issues.

+1.

I complete the implementation of this feature.
When a session x create an index idx_a on GTT A then
For session x, idx_a is valid when after create index.
For session y, before session x create index done, GTT A has some data, then index_a is invalid.
For session z, before session x create index done, GTT A has no data, then index_a is valid.

For example, I've looked at the "on demand" building as implemented in
global_private_temp-8.patch, I kinda doubt adding a bunch of index build
calls into various places in index code seems somewht suspicious.

+1. I can't imagine that's a safe or sane thing to do.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

Opinion by Pavel
+ rel->rd_islocaltemp = true; <<<<<<< if this is valid, then the name of field "rd_islocaltemp" is not probably best
I renamed rd_islocaltemp

Opinion by Konstantin Knizhnik
1 Fixed comments
2 Fixed assertion

Please help me review.

Wenjing

Attachments:

global_temporary_table_v8-pg13.patchapplication/octet-stream; name=global_temporary_table_v8-pg13.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 79430d2..babb5f3 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -158,6 +158,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * For global temp table only
+	 * use ShareUpdateExclusiveLock for ensure safety
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temp table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},	
 	/* list terminator */
 	{{NULL}}
 };
@@ -1486,6 +1499,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1586,13 +1601,18 @@ build_reloptions(Datum reloptions, bool validate,
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	/*
 	 * There are no options for partitioned tables yet, but this is able to do
 	 * some validation.
 	 */
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index dd975b1..1610e7d 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1013,7 +1013,9 @@ gistGetFakeLSN(Relation rel)
 {
 	static XLogRecPtr counter = FirstNormalUnloggedLSN;
 
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	/* global temp is same as local temp table */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 4871b7f..16b00c9 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -149,7 +149,9 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	/* global temp table is same as local temp table */
+	if (!(index->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		index->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 3fa4b76..b54882d 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -598,7 +598,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -651,7 +651,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 8ce5011..0d6ae76 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -62,6 +62,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -399,9 +400,9 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
 	Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
-	/* not every AM requires these to be valid, but heap does */
-	Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
-	Assert(MultiXactIdIsValid(onerel->rd_rel->relminmxid));
+	/* not every AM requires these to be valid, but regular heap does */
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ MultiXactIdIsValid(onerel->rd_rel->relminmxid));
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index f05cbe7..946c9d2 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -763,7 +763,14 @@ _bt_getbuf(Relation rel, BlockNumber blkno, int access)
 		/* Read an existing block of the relation */
 		buf = ReadBuffer(rel, blkno);
 		LockBuffer(buf, access);
-		_bt_checkpage(rel, buf);
+
+		/* global temp table may be not yet initialized for this backend. */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			blkno == BTREE_METAPAGE &&
+			PageIsNew(BufferGetPage(buf)))
+			_bt_initmetapage(BufferGetPage(buf), P_NONE, 0);
+		else
+			_bt_checkpage(rel, buf);
 	}
 	else
 	{
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 6e09ded..a2059fa 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6325,6 +6325,10 @@ StartupXLOG(void)
 	else
 		recoveryTargetTLI = ControlFile->checkPointCopy.ThisTimeLineID;
 
+	/* clean temp relation files */
+	if (max_active_gtt > 0)
+		RemovePgTempFiles();
+
 	/*
 	 * Check for signal files, and if so set up state for offline recovery
 	 */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d5da81c..c753b0e 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -42,6 +42,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 16cb6d8..f28f2c2 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -398,7 +398,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/* global temp table is same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 0fdff29..210d019 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -404,6 +406,10 @@ heap_create(const char *relname,
 									 relpersistence,
 									 relkind);
 
+	/* global temp table not create storage file when catalog create */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		create_storage = false;
+
 	/*
 	 * Have the storage manager create the relation's disk file, if needed.
 	 *
@@ -427,7 +433,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -956,6 +962,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -994,8 +1001,18 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/* global temp table not remember transaction info in catalog */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1357,6 +1374,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1441,11 +1459,15 @@ heap_create_with_catalog(const char *relname,
 	 */
 	StoreConstraints(new_rel_desc, cooked_constraints, is_internal);
 
-	/*
-	 * If there's a special on-commit action, remember it
-	 */
-	if (oncommit != ONCOMMIT_NOOP)
-		register_on_commit_action(relid, oncommit);
+	/* global temp table register action when storage init */
+	if (relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		/*
+		 * If there's a special on-commit action, remember it
+		 */
+		if (oncommit != ONCOMMIT_NOOP)
+			register_on_commit_action(relid, oncommit);
+	}
 
 	/*
 	 * ok, the relation has been cataloged, so close our relations and return
@@ -1937,6 +1959,13 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/* We allow to drop global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(rel->rd_node))
+			elog(ERROR, "can not drop relation when other backend attached this global temp table");
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3165,7 +3194,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3176,8 +3205,12 @@ RelationTruncateIndexes(Relation heapRelation)
 		Relation	currentIndex;
 		IndexInfo  *indexInfo;
 
+		if (RELATION_IS_GLOBAL_TEMP(heapRelation) &&
+			!gtt_storage_attached(indexId))
+			continue;
+
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3223,8 +3256,13 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		/* truncate global temp table only need RowExclusiveLock */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3257,6 +3295,8 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
+	bool		truncate_toastrel = false;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3265,23 +3305,40 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	toastrelid = rel->rd_rel->reltoastrelid;
+
+	/*
+	 * Truncate global temp table only need RowExclusiveLock
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		lockmode = RowExclusiveLock;
+		if (OidIsValid(toastrelid) &&
+			gtt_storage_attached(toastrelid))
+			truncate_toastrel = true;
+	}
+	else if (OidIsValid(toastrelid))
+		truncate_toastrel = true;
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
-	toastrelid = rel->rd_rel->reltoastrelid;
-	if (OidIsValid(toastrelid))
+	if (truncate_toastrel)
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(rel, 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 8880586..0852073 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -52,6 +52,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -877,6 +878,22 @@ index_create(Relation heapRelation,
 						indexRelationName, RelationGetRelationName(heapRelation))));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		if (accessMethodObjectId != BTREE_AM_OID)
+			elog(ERROR, "only support btree index on global temp table");
+
+		/* No support create index on global temp table use concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot create indexes on global temporary tables using concurrent mode")));
+
+		/* if global temp table not init storage, then skip build index */
+		if (!gtt_storage_attached(heapRelation->rd_node.relNode))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -2054,6 +2071,13 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/* We allow to drop index on global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation))
+	{
+		if (is_other_backend_use_gtt(userHeapRelation->rd_node))
+			elog(ERROR, "can not drop index when other backend attached this global temp table.");
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2660,6 +2684,11 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	/* update index stats into localhash and rel_rd_rel for global temp table */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		is_gtt = true;
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2745,21 +2774,35 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
+		if (is_gtt)
+			rel->rd_rel->relpages = (int32) relpages;
+		else if (rd_rel->relpages != (int32) relpages)
 		{
 			rd_rel->relpages = (int32) relpages;
 			dirty = true;
 		}
-		if (rd_rel->reltuples != (float4) reltuples)
+
+		if (is_gtt)
+			rel->rd_rel->reltuples = (float4) reltuples;
+		else if (rd_rel->reltuples != (float4) reltuples)
 		{
 			rd_rel->reltuples = (float4) reltuples;
 			dirty = true;
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+
+		if (is_gtt)
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+		else if (rd_rel->relallvisible != (int32) relallvisible)
 		{
 			rd_rel->relallvisible = (int32) relallvisible;
 			dirty = true;
 		}
+
+		if (is_gtt)
+		{
+			up_gtt_relstats(rel, relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
+		}
 	}
 
 	/*
@@ -2873,6 +2916,15 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, index, val);
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		if (!gtt_storage_attached(indexRelation->rd_node.relNode))
+		{
+			gtt_force_enable_index(indexRelation);
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3468,6 +3520,15 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 				 errmsg("cannot reindex temporary tables of other sessions")));
 
 	/*
+	 * Because global temp table cannot change relfilenode
+	 * no support reindex on global temp table yet.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(iRel))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot reindex global temporary tables")));
+
+	/*
 	 * Also check for active uses of the index in the current transaction; we
 	 * don't want to reindex underneath an open indexscan.
 	 */
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index e70243a..301da79 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -647,6 +647,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		/* global temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp relations in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index fddfbf1..671c614 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -26,6 +26,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -75,7 +76,7 @@ static PendingRelDelete *pendingDeletes = NULL; /* head of linked list */
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -85,6 +86,8 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		/* global temp table use same storage strategy as local temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -117,6 +120,10 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/* remember global temp table storage info to localhash */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel)
+		remember_gtt_storage_info(rnode, rel);
+
 	return srel;
 }
 
@@ -486,8 +493,15 @@ smgrDoPendingDeletes(bool isCommit)
 		smgrdounlinkall(srels, nrels, false);
 
 		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* clean global temp table flags when transaction commit or rollback */
+			if (SmgrIsTemp(srels[i]) && 
+				gtt_storage_attached(srels[i]->smgr_rnode.node.relNode))
+				forget_gtt_storage_info(srels[i]->smgr_rnode.node.relNode);
+		}
+
 		pfree(srels);
 	}
 }
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..24a7e77
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1174 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  code to create and destroy physical storage for global temparary table
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/table.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_relstats_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+typedef struct gtt_ctl_data
+{
+	LWLock			lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct
+{
+	RelFileNode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+	Oid			relid;
+
+	Oid			spcnode;
+	/* pg_class stat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+	char		relkind;
+	bool		on_commit_delete;
+
+	/* pg_statistic */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(RelFileNode rnode);
+static void gtt_storage_checkout(RelFileNode rnode, bool skiplock);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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 (rel->rd_rel->relkind == RELKIND_INDEX &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create stroage file", RelationGetRelationName(rel));
+
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		/* First time through: initialize the hash table */
+		HASHCTL		ctl;
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		gtt_storage_local_hash =
+			hash_create("global temp relation table",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_relstats_context =
+			AllocSetContextCreate(CacheMemoryContext,
+							"gtt relstats context",
+							ALLOCSET_DEFAULT_SIZES);
+	}
+
+	/* Look up or create an entry */
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_ENTER, &found);
+
+	if (found)
+	{
+		elog(ERROR, "backend %d relid %u already exists in global temp table local hash",
+					MyBackendId, relid);
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+	entry->spcnode = rnode.spcNode;
+	entry->relpages = 0;
+	entry->reltuples = 0;
+	entry->relallvisible = 0;
+	entry->relkind = rel->rd_rel->relkind;
+	/* only heap contain transaction information */
+	if (entry->relkind == RELKIND_RELATION || entry->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		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;
+		int			i;
+
+		rnode.spcNode = entry->spcnode;
+		rnode.dbNode = MyDatabaseId;
+		rnode.relNode = entry->relid;
+
+		gtt_storage_checkout(rnode, false);
+
+		Assert(TransactionIdIsNormal(entry->relfrozenxid));
+		remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+
+		for (i = 0; i < entry->natts; i++)
+		{
+			if (entry->att_stat_tups[i])
+			{
+				heap_freetuple(entry->att_stat_tups[i]);
+				entry->att_stat_tups[i] = NULL;
+			}
+		}
+	}
+
+	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.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (gtt_storage_local_hash == NULL)
+		return false;
+
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_FIND, &found);
+
+	if (!found)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	if (relpages)
+		*relpages = entry->relpages;
+
+	if (reltuples)
+		*reltuples = entry->reltuples;
+
+	if (relallvisible)
+		*relallvisible = entry->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = entry->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = entry->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	MemoryContext			oldcontext;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+						(void *) &reloid, HASH_FIND, &found);
+
+	if (!found)
+		return;
+
+	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;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	HeapTuple	tuple;
+	Relation	rel = NULL;
+	int		attnum = PG_GETARG_INT32(1);
+	Oid		reloid = PG_GETARG_OID(0);
+	char		rel_persistence;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	  tupdesc;
+	MemoryContext oldcontext;
+	Tuplestorestate *tupstore;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = table_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+
+	table_close(rel, NoLock);
+	table_close(pg_tatistic, AccessShareLock);
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate	*tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Relation	rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	if (get_gtt_relstats(reloid,
+						&relpages, &reltuples, &relallvisible,
+						&relfrozenxid, &relminmxid))
+	{
+		Datum	values[5];
+		bool	isnull[5];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = Int32GetDatum(relpages);
+		values[1] = Float4GetDatum((float4)reltuples);
+		values[2] = Int32GetDatum(relallvisible);
+		values[3] = UInt32GetDatum(relfrozenxid);
+		values[4] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+
+	tuplestore_donestoring(tupstore);
+	table_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(rel->rd_node);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	table_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(indexOid));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid relOid = index->rd_index->indrelid;
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	if (gtt_storage_attached(relOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index c9e75f4..2adde62 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM 
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c4420dd..977a984 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -586,14 +587,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 		 * pg_statistic for columns we didn't process, we leave them alone.)
 		 */
 		update_attstats(RelationGetRelid(onerel), inh,
-						attr_cnt, vacattrstats);
+						attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
 
 		for (ind = 0; ind < nindexes; ind++)
 		{
 			AnlIndexData *thisdata = &indexdata[ind];
 
 			update_attstats(RelationGetRelid(Irel[ind]), false,
-							thisdata->attr_cnt, thisdata->vacattrstats);
+							thisdata->attr_cnt, thisdata->vacattrstats,
+							RelationGetRelPersistence(Irel[ind]));
 		}
 
 		/*
@@ -1456,7 +1458,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
  *		by taking a self-exclusive lock on the relation in analyze_rel().
  */
 static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
 {
 	Relation	sd;
 	int			attno;
@@ -1558,31 +1560,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/* Update column statistic to localhash, not catalog */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index e9d7a7f..3088e26 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -391,6 +391,12 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/* not support cluster global temp table yet */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("not support cluster global temporary tables yet")));
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 40a8ec1..e7b4e44 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1062,7 +1062,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !rel->rd_istemp)
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, stmt->filename, stmt->is_program,
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index ec20ba3..a29cc00 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2614,6 +2614,16 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 			!isTempNamespace(classtuple->relnamespace))
 			continue;
 
+		/* not support reindex on global temp table, so skip it */
+		if (classtuple->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			ereport(WARNING,
+				(errmsg("global temp table \"%s.%s\" skip reindexed",
+					get_namespace_name(get_rel_namespace(relid)),
+					get_rel_name(relid))));
+			continue;
+		}
+
 		/* Check user/system classification, and optionally skip */
 		if (objectKind == REINDEX_OBJECT_SYSTEM &&
 			!IsSystemClass(relid, classtuple))
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 329ab84..dfa257c 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -96,7 +96,8 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 	 * transaction.
 	 */
 	relpersistence = get_rel_persistence(relid);
-	if (relpersistence == RELPERSISTENCE_TEMP)
+	if (relpersistence == RELPERSISTENCE_TEMP ||
+		relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
 
 	/* Check permissions. */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..5787674 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -94,7 +94,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +108,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -222,7 +223,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* now initialize the sequence's data */
 	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -327,7 +328,7 @@ ResetSequence(Oid seq_relid)
 	/*
 	 * Insert the modified tuple into the new storage file.
 	 */
-	fill_seq_with_data(seq_rel, tuple);
+	fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -340,17 +341,21 @@ ResetSequence(Oid seq_relid)
  * Initialize a sequence's relation with the specified tuple as content
  */
 static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 	sequence_magic *sm;
 	OffsetNumber offnum;
+	bool lockBuffer = false;
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ReadBuffer(rel, P_NEW);
-	Assert(BufferGetBlockNumber(buf) == 0);
+	if (buf == InvalidBuffer)
+	{
+		buf = ReadBuffer(rel, P_NEW);
+		Assert(BufferGetBlockNumber(buf) == 0);
+		lockBuffer = true;
+	}
 
 	page = BufferGetPage(buf);
 
@@ -360,7 +365,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* Now insert sequence tuple */
 
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+	if (lockBuffer)
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +416,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	END_CRIT_SECTION();
 
-	UnlockReleaseBuffer(buf);
+	if (lockBuffer)
+		UnlockReleaseBuffer(buf);
 }
 
 /*
@@ -502,7 +509,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * Insert the modified tuple into the new storage file.
 		 */
-		fill_seq_with_data(seqrel, newdatatuple);
+		fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
 	}
 
 	/* process OWNED BY if given */
@@ -611,7 +618,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!seqrel->rd_istemp)
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +943,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!seqrel->rd_istemp)
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1178,6 +1185,25 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
 	LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
 
 	page = BufferGetPage(*buf);
+	if (GlobalTempRelationPageIsNotInitialized(rel, page))
+	{
+		/* Initialize sequence for global temporary tables */
+		Datum		value[SEQ_COL_LASTCOL] = {0};
+		bool		null[SEQ_COL_LASTCOL] = {false};
+		HeapTuple	tuple;
+		int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+		/*
+		 * last_value from pg_sequence.seqstart
+		 * log_cnt = 0
+		 * is_called = false
+		 */
+		value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+		tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+		fill_seq_with_data(rel, tuple, *buf);
+		heap_freetuple(tuple);
+	}
 	sm = (sequence_magic *) PageGetSpecialPointer(page);
 
 	if (sm->magic != SEQ_MAGIC)
@@ -1954,3 +1980,23 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 70589dd..dfe7cc0 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -552,6 +553,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 								  Relation partitionTbl);
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static bool has_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -597,6 +599,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	bool		has_oncommit_clause = false;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -607,8 +610,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Check consistency of arguments
 	 */
+	/* global temp table same as local temp table */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !(stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+			 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -638,7 +643,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	/* global temp table same as local temp table */
+	if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+		 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -739,6 +746,57 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* global temp table */
+	has_oncommit_clause = has_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* inherit table or parition table inherit on commit property from parent table*/
+		if (inheritOids && stmt->oncommit == ONCOMMIT_NOOP && !has_oncommit_clause)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			if (RELATION_GTT_ON_COMMIT_DELETE(relation))
+				stmt->oncommit = ONCOMMIT_DELETE_ROWS;
+			else
+				stmt->oncommit = ONCOMMIT_PRESERVE_ROWS;
+			table_close(relation, NoLock);
+		}
+
+		if (has_oncommit_clause)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "can not defeine global temp table with on commit and with clause at same time");
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temp table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (has_oncommit_clause)
+		elog(ERROR, "regular table cannot specifie on_commit_delete_rows");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1816,7 +1874,8 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
 		 * table or the current physical file to be thrown away anyway.
 		 */
 		if (rel->rd_createSubid == mySubid ||
-			rel->rd_newRelfilenodeSubid == mySubid)
+			rel->rd_newRelfilenodeSubid == mySubid ||
+			RELATION_IS_GLOBAL_TEMP(rel))
 		{
 			/* Immediate, non-rollbackable truncation is OK */
 			heap_truncate_one_rel(rel);
@@ -2259,7 +2318,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 
 		/* If existing rel is temp, it must belong to this session */
 		if (relation->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
-			!relation->rd_islocaltemp)
+			!relation->rd_istemp)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg(!is_partition
@@ -3375,6 +3434,13 @@ RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal, bo
 	 * specially.
 	 */
 	targetrelation = relation_open(myrelid, is_index ? ShareUpdateExclusiveLock : AccessExclusiveLock);
+
+	if (RELATION_IS_GLOBAL_TEMP(targetrelation))
+	{
+		if (is_other_backend_use_gtt(targetrelation->rd_node))
+			elog(ERROR, "can not rename relation when other backend attached this global temp table");
+	}
+
 	namespaceId = RelationGetNamespace(targetrelation);
 
 	/*
@@ -3550,6 +3616,13 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(rel->rd_node))
+			elog(ERROR, "can not alter relation when other backend attached this global temp table");
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -8123,6 +8196,13 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 				 errmsg("referenced relation \"%s\" is not a table",
 						RelationGetRelationName(pkrel))));
 
+	/* global temp table not support foreign key constraint yet */
+	if (RELATION_IS_GLOBAL_TEMP(pkrel))
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("referenced relation \"%s\" is not a global temp table",
+						RelationGetRelationName(pkrel))));
+
 	if (!allowSystemTableMods && IsSystemRelation(pkrel))
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -8157,11 +8237,17 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables may reference only temporary tables")));
-			if (!pkrel->rd_islocaltemp || !rel->rd_islocaltemp)
+			if (!pkrel->rd_istemp || !rel->rd_istemp)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		/* global temp table not support foreign key constraint yet */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("not support foreign key constraints on global temp table yet")));
+			break;
 	}
 
 	/*
@@ -13199,7 +13285,7 @@ index_copy_data(Relation rel, RelFileNode newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
@@ -13314,14 +13400,14 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 
 	/* If parent rel is temp, it must belong to this session */
 	if (parent_rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
-		!parent_rel->rd_islocaltemp)
+		!parent_rel->rd_istemp)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot inherit from temporary relation of another session")));
 
 	/* Ditto for the child */
 	if (child_rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
-		!child_rel->rd_islocaltemp)
+		!child_rel->rd_istemp)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot inherit to temporary relation of another session")));
@@ -14606,7 +14692,9 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
 	 */
 	switch (rel->rd_rel->relpersistence)
 	{
+		/* global temp table same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -16096,14 +16184,14 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 
 	/* If the parent is temp, it must belong to this session */
 	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
-		!rel->rd_islocaltemp)
+		!rel->rd_istemp)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot attach as partition of temporary relation of another session")));
 
 	/* Ditto for the partition */
 	if (attachrel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
-		!attachrel->rd_islocaltemp)
+		!attachrel->rd_istemp)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot attach temporary relation of another session as partition")));
@@ -17246,3 +17334,20 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
 		table_close(rel, NoLock);
 	}
 }
+
+static bool
+has_oncommit_option(List *options)
+{
+	ListCell   *listptr;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (pg_strcasecmp(def->defname, "on_commit_delete_rows") == 0)
+			return true;
+	}
+
+	return false;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index d625d17..69ad24f 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/vacuum.h"
@@ -1217,6 +1218,17 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	 /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		is_gtt = true;
+		up_gtt_relstats(relation,
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1230,17 +1242,26 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relpages = (int32) num_pages;
+	else if (pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (is_gtt)
+		relation->rd_rel->reltuples = (float4) num_tuples;
+	else if (pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	else if (pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1285,7 +1306,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1296,7 +1318,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1394,6 +1417,10 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/* global temp table relstats not in pg_class */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1451,6 +1478,25 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/*
+	 * Global temp table get frozenxid from MyProc
+	 * to avoid the vacuum truncate clog that gtt need.
+	 */
+	if (max_active_gtt > 0)
+	{
+		TransactionId oldest_gtt_frozenxid =
+			list_all_session_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid) &&
+			TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+		{
+			ereport(WARNING,
+				(errmsg("global temp table oldest FrozenXid is far in the past"),
+				 errhint("please truncate them or kill those sessions that use them.")));
+			newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 8286d9c..c3992a4 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -591,6 +591,8 @@ static void
 set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 						  RangeTblEntry *rte)
 {
+	char relpersistence;
+
 	/*
 	 * The flag has previously been initialized to false, so we can just
 	 * return if it becomes clear that we can't safely set it.
@@ -618,7 +620,11 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			/* global temp table is same as local temp table */
+			relpersistence = get_rel_persistence(rte->relid);
+
+			if (relpersistence == RELPERSISTENCE_TEMP ||
+				relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index d6f2153..310a9e2 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6312,7 +6312,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
+	/* global temp table is same as local temp table */
 	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 748bebf..a5ddbcd 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2579,6 +2579,11 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("materialized views must not use temporary tables or views")));
 
+		if (is_query_using_gtt(query))
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("materialized views must not use global temporary tables or views")));
+
 		/*
 		 * A materialized view would either need to save parameters for use in
 		 * maintaining/loading the data or prohibit them entirely.  The latter
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ba5916b..0ee6931 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3290,17 +3290,11 @@ OptTemp:	TEMPORARY					{ $$ = RELPERSISTENCE_TEMP; }
 			| LOCAL TEMP				{ $$ = RELPERSISTENCE_TEMP; }
 			| GLOBAL TEMPORARY
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
-					$$ = RELPERSISTENCE_TEMP;
+					$$ = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
-					$$ = RELPERSISTENCE_TEMP;
+					$$ = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED					{ $$ = RELPERSISTENCE_UNLOGGED; }
 			| /*EMPTY*/					{ $$ = RELPERSISTENCE_PERMANENT; }
@@ -11588,19 +11582,13 @@ OptTempTableName:
 				}
 			| GLOBAL TEMPORARY opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED opt_table qualified_name
 				{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b875a50..123945b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index ee2d2b5..9c9abaa 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 6d1f28c..ed837d1 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2086,6 +2086,11 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* autovacuum skip vacuum global temp table */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2152,7 +2157,9 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		/* autovacuum skip vacuum global temp table */
+		if (classForm->relpersistence == RELPERSISTENCE_TEMP || 
+			classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index aba3960..3c4b96c 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -52,6 +53,7 @@
 #include "utils/rel.h"
 #include "utils/resowner_private.h"
 #include "utils/timestamp.h"
+#include "utils/guc.h"
 
 
 /* Note: these two macros only work on shared buffers, not local ones! */
@@ -432,7 +434,7 @@ ForgetPrivateRefCountEntry(PrivateRefCountEntry *ref)
 static Buffer ReadBuffer_common(SMgrRelation reln, char relpersistence,
 								ForkNumber forkNum, BlockNumber blockNum,
 								ReadBufferMode mode, BufferAccessStrategy strategy,
-								bool *hit);
+								bool *hit, Relation rel);
 static bool PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy);
 static void PinBuffer_Locked(BufferDesc *buf);
 static void UnpinBuffer(BufferDesc *buf, bool fixOwner);
@@ -664,7 +666,8 @@ ReadBufferExtended(Relation reln, ForkNumber forkNum, BlockNumber blockNum,
 	 */
 	pgstat_count_buffer_read(reln);
 	buf = ReadBuffer_common(reln->rd_smgr, reln->rd_rel->relpersistence,
-							forkNum, blockNum, mode, strategy, &hit);
+							forkNum, blockNum, mode, strategy, &hit,
+							reln);
 	if (hit)
 		pgstat_count_buffer_hit(reln);
 	return buf;
@@ -692,7 +695,7 @@ ReadBufferWithoutRelcache(RelFileNode rnode, ForkNumber forkNum,
 	Assert(InRecovery);
 
 	return ReadBuffer_common(smgr, RELPERSISTENCE_PERMANENT, forkNum, blockNum,
-							 mode, strategy, &hit);
+							 mode, strategy, &hit, NULL);
 }
 
 
@@ -704,7 +707,8 @@ ReadBufferWithoutRelcache(RelFileNode rnode, ForkNumber forkNum,
 static Buffer
 ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 				  BlockNumber blockNum, ReadBufferMode mode,
-				  BufferAccessStrategy strategy, bool *hit)
+				  BufferAccessStrategy strategy, bool *hit,
+				  Relation rel)
 {
 	BufferDesc *bufHdr;
 	Block		bufBlock;
@@ -719,6 +723,15 @@ ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 
 	isExtend = (blockNum == P_NEW);
 
+	/* create storage when first read data page for gtt */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(isExtend || blockNum == 0) &&
+		forkNum == MAIN_FORKNUM &&
+		!gtt_storage_attached(smgr->smgr_rnode.node.relNode))
+	{
+		RelationCreateStorage(smgr->smgr_rnode.node, relpersistence, rel);
+	}
+
 	TRACE_POSTGRESQL_BUFFER_READ_START(forkNum, blockNum,
 									   smgr->smgr_rnode.node.spcNode,
 									   smgr->smgr_rnode.node.dbNode,
@@ -2809,6 +2822,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(relation->rd_node.relNode))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d5..7adb969 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -21,6 +21,7 @@
 #include "access/nbtree.h"
 #include "access/subtrans.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, active_gtt_shared_hash_size());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* global temporary table */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index c3adb2e..eb95e5f 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -62,6 +62,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -4087,3 +4088,77 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temp table.
+ */
+int
+list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct *arrayP = procArray;
+	TransactionId result = InvalidTransactionId;
+	int			index;
+	int			flags = 0;
+	int			i = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+		*n = 0;
+	}
+
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		volatile PGXACT *pgxact = &allPgXact[pgprocno];
+
+		if (pgxact->vacuumFlags & flags)
+			continue;
+
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->session_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->session_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->session_gtt_frozenxid, result))
+				result = proc->session_gtt_frozenxid;
+
+			if (max_size > 0)
+			{
+				pids[i] = proc->pid;
+				xids[i] = proc->session_gtt_frozenxid;
+				i++;
+			}
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (max_size > 0)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 32df8c8..e4e1125 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c
index c5b771c..e7725ce 100644
--- a/src/backend/storage/smgr/md.c
+++ b/src/backend/storage/smgr/md.c
@@ -646,6 +646,12 @@ mdread(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
 		 */
 		if (zero_damaged_pages || InRecovery)
 			MemSet(buffer, 0, BLCKSZ);
+		else if(SmgrIsTemp(reln) && blocknum == 0 && forknum == MAIN_FORKNUM)
+		{
+			/* global temp table init btree meta page */
+			MemSet(buffer, 0, BLCKSZ);
+			mdwrite(reln, forknum, blocknum, buffer, true);
+		}
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_DATA_CORRUPTED),
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 8406644..0d99321 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -1008,6 +1008,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 7c6f057..eb10cf2 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -112,6 +112,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -4578,12 +4579,25 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -4708,15 +4722,27 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6098,6 +6124,7 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	{
 		/* Simple variable --- look to stats for the underlying table */
 		RangeTblEntry *rte = planner_rt_fetch(index->rel->relid, root);
+		char	rel_persistence = get_rel_persistence(rte->relid);
 
 		Assert(rte->rtekind == RTE_RELATION);
 		relid = rte->relid;
@@ -6115,6 +6142,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6126,6 +6160,8 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	}
 	else
 	{
+		char	rel_persistence = get_rel_persistence(index->indexoid);
+
 		/* Expression --- maybe there are stats for the index itself */
 		relid = index->indexoid;
 		colnum = 1;
@@ -6141,6 +6177,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7054,6 +7097,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		/* attempt to lookup stats in relation for this index column */
 		if (attnum != 0)
 		{
+			char	rel_persistence = get_rel_persistence(rte->relid);
+
 			/* Simple variable -- look to stats for the underlying table */
 			if (get_relation_stats_hook &&
 				(*get_relation_stats_hook) (root, rte, attnum, &vardata))
@@ -7066,6 +7111,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7078,6 +7131,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		}
 		else
 		{
+			char	rel_persistence = get_rel_persistence(index->indexoid);
+
 			/*
 			 * Looks like we've found an expression column in the index. Let's
 			 * see if there's any stats for it.
@@ -7097,6 +7152,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 1e3e6d3..345e3e9 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -2878,6 +2879,18 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index df025a5..e33269b 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -63,6 +63,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1095,13 +1096,13 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELPERSISTENCE_UNLOGGED:
 		case RELPERSISTENCE_PERMANENT:
 			relation->rd_backend = InvalidBackendId;
-			relation->rd_islocaltemp = false;
+			relation->rd_istemp = false;
 			break;
 		case RELPERSISTENCE_TEMP:
 			if (isTempOrTempToastNamespace(relation->rd_rel->relnamespace))
 			{
 				relation->rd_backend = BackendIdForTempRelations();
-				relation->rd_islocaltemp = true;
+				relation->rd_istemp = true;
 			}
 			else
 			{
@@ -1114,14 +1115,36 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				 * from a crashed backend that coincidentally had the same
 				 * BackendId we're using.  We should *not* consider such a
 				 * table to be "ours"; this is why we need the separate
-				 * rd_islocaltemp flag.  The pg_class entry will get flushed
+				 * rd_istemp flag.  The pg_class entry will get flushed
 				 * if/when we clean out the corresponding temp table namespace
 				 * in preparation for using it.
 				 */
 				relation->rd_backend =
 					GetTempNamespaceBackendId(relation->rd_rel->relnamespace);
 				Assert(relation->rd_backend != InvalidBackendId);
-				relation->rd_islocaltemp = false;
+				relation->rd_istemp = false;
+			}
+			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				/*
+				 * For global temp table, all backend can use
+				 * this relation, so rd_istemp always true.
+				 */
+				relation->rd_istemp = true;
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
 			}
 			break;
 		default:
@@ -1178,6 +1201,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			gtt_fix_index_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -1815,7 +1839,7 @@ formrdesc(const char *relationName, Oid relationReltype,
 	relation->rd_createSubid = InvalidSubTransactionId;
 	relation->rd_newRelfilenodeSubid = InvalidSubTransactionId;
 	relation->rd_backend = InvalidBackendId;
-	relation->rd_islocaltemp = false;
+	relation->rd_istemp = false;
 
 	/*
 	 * initialize relation tuple form
@@ -2217,6 +2241,8 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		gtt_fix_index_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3306,12 +3332,16 @@ RelationBuildLocalRelation(const char *relname,
 		case RELPERSISTENCE_UNLOGGED:
 		case RELPERSISTENCE_PERMANENT:
 			rel->rd_backend = InvalidBackendId;
-			rel->rd_islocaltemp = false;
+			rel->rd_istemp = false;
 			break;
 		case RELPERSISTENCE_TEMP:
 			Assert(isTempOrTempToastNamespace(relnamespace));
 			rel->rd_backend = BackendIdForTempRelations();
-			rel->rd_islocaltemp = true;
+			rel->rd_istemp = true;
+			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_istemp = true;
 			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
@@ -3427,6 +3457,9 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
 
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+		elog(ERROR, "global temp table does not allow setting new relfilenode");
+
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
@@ -3467,7 +3500,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 				/* handle these directly, at least for now */
 				SMgrRelation srel;
 
-				srel = RelationCreateStorage(newrnode, persistence);
+				srel = RelationCreateStorage(newrnode, persistence, relation);
 				smgrclose(srel);
 			}
 			break;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index cacbe90..9c4220b 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -140,6 +140,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temp table feature.
+ * global temp table define can still storage in catalog
+ * just can not use.
+ * num > 0 means database can management num active global temp table.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2012,6 +2024,15 @@ static struct config_bool ConfigureNamesBool[] =
 static struct config_int ConfigureNamesInt[] =
 {
 	{
+		{"max_active_global_temporary_table", PGC_POSTMASTER, UNGROUPED,
+			gettext_noop("max active global temporary table."),
+			NULL
+		},
+		&max_active_gtt,
+		DEFAULT_NUM_ACTIVE_GTT, MIN_NUM_ACTIVE_GTT, MAX_NUM_ACTIVE_GTT,
+		NULL, NULL, NULL
+	},
+	{
 		{"archive_timeout", PGC_SIGHUP, WAL_ARCHIVING,
 			gettext_noop("Forces a switch to the next WAL file if a "
 						 "new file has not been started within N seconds."),
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 799b698..b98d396 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15583,6 +15583,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	{
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
+		char		*table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15634,9 +15635,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 			binary_upgrade_set_pg_class_oids(fout, q,
 											 tbinfo->dobj.catId.oid, false);
 
+		if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED)
+			table_type = "UNLOGGED ";
+		else if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			table_type = "GLOBAL TEMPORARY ";
+		else
+			table_type = "";
+
 		appendPQExpBuffer(q, "CREATE %s%s %s",
-						  tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ?
-						  "UNLOGGED " : "",
+						  table_type,
 						  reltypename,
 						  qualrelname);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index f3c7eb9..28134e2 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3723,7 +3723,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index dc03fbd..8bd6d09 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1016,6 +1016,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2378,6 +2380,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2586,6 +2591,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index a12fc1f..78958e4 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -165,6 +165,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index bef50c7..bc36c6c 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5504,6 +5504,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4191',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4192',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{relid,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4193',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4194',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 048003c..af48cdf 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -19,7 +19,7 @@
 #include "storage/smgr.h"
 #include "utils/relcache.h"
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationTruncate(Relation rel, BlockNumber nblocks);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..aa80cb5
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,41 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "utils/relcache.h"
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid);
+extern bool is_other_backend_use_gtt(RelFileNode node);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(RelFileNode node);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Relation relation,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void gtt_force_enable_index(Relation index);
+extern void gtt_fix_index_state(Relation index);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 870ecb5..92c590e 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 8fda8e4..172eae9 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_TBM,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_SXACT,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index d217801..8adde87 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -117,6 +117,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId session_gtt_frozenxid;	/* session level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ce93ace..0f7262e 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -281,6 +281,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 44ed04d..9f29744 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	struct SMgrRelationData *rd_smgr;	/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_istemp;		/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -277,6 +277,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -535,7 +536,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
@@ -546,7 +548,7 @@ typedef struct ViewOptions
  * Beware of multiple eval of argument
  */
 #define RELATION_IS_LOCAL(relation) \
-	((relation)->rd_islocaltemp || \
+	((relation)->rd_istemp || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -557,7 +559,7 @@ typedef struct ViewOptions
  */
 #define RELATION_IS_OTHER_TEMP(relation) \
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
-	 !(relation)->rd_islocaltemp)
+	 !(relation)->rd_istemp)
 
 
 /*
@@ -602,6 +604,17 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..50ca9ac
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,7 @@
+reset search_path;
+drop schema gtt cascade;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..a477917
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,197 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+ERROR:  cannot create indexes on global temporary tables using concurrent mode
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+ERROR:  not support cluster global temporary tables yet
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ok
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  regular table cannot specifie on_commit_delete_rows
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  can not defeine global temp table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test;
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  referenced relation "products" is not a global temp table
+-- ERROR
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  referenced relation "products" is not a global temp table
+-- ERROR
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ALL ERROR
+create index idx_err on gtt1 using hash (a);
+ERROR:  only support btree index on global temp table
+create index idx_err on gtt1 using gist (a);
+ERROR:  data type integer has no default operator class for access method "gist"
+HINT:  You must specify an operator class for the index or define a default operator class for the data type.
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+ERROR:  only support btree index on global temp table
+create index idx_tmp_t0_1 on tmp_t0 using gist (c0);
+ERROR:  only support btree index on global temp table
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 15 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..30d8a7b
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,84 @@
+set search_path=gtt,sys;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..850ef3e
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,161 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          475136 |                 974848
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |           409600 |        409600 |                 409600
+(2 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..9fe5fd4
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,9 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..b258b7c
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,76 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+(1 row)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+(1 row)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 70e1e2f..30cf4bd 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1348,6 +1348,93 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d2b17dd..80e577f 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..f3cf710
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,6 @@
+
+
+reset search_path;
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..cfc1879
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,163 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ok
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test;
+
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ERROR
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ERROR
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ALL ERROR
+create index idx_err on gtt1 using hash (a);
+create index idx_err on gtt1 using gist (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_1 on tmp_t0 using gist (c0);
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d7d81de
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,42 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..5203c2b
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,76 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..2f4d883
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,16 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..f041892
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,42 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
#94Pavel Stehule
pavel.stehule@gmail.com
In reply to: 曾文旌(义从) (#93)
Re: [Proposal] Global temporary tables

út 28. 1. 2020 v 17:01 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
napsal:

2020年1月24日 上午4:47,Robert Haas <robertmhaas@gmail.com> 写道:

On Sat, Jan 11, 2020 at 8:51 PM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

I proposed just ignoring those new indexes because it seems much simpler
than alternative solutions that I can think of, and it's not like those
other solutions don't have other issues.

+1.

I complete the implementation of this feature.
When a session x create an index idx_a on GTT A then
For session x, idx_a is valid when after create index.
For session y, before session x create index done, GTT A has some data,
then index_a is invalid.
For session z, before session x create index done, GTT A has no data,
then index_a is valid.

For example, I've looked at the "on demand" building as implemented in
global_private_temp-8.patch, I kinda doubt adding a bunch of index build
calls into various places in index code seems somewht suspicious.

+1. I can't imagine that's a safe or sane thing to do.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

Opinion by Pavel
+ rel->rd_islocaltemp = true; <<<<<<< if this is valid, then the name of
field "rd_islocaltemp" is not probably best
I renamed rd_islocaltemp

I don't see any change?

Show quoted text

Opinion by Konstantin Knizhnik
1 Fixed comments
2 Fixed assertion

Please help me review.

Wenjing

#95曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Pavel Stehule (#94)
1 attachment(s)
Re: [Proposal] Global temporary tables

2020年1月29日 上午12:40,Pavel Stehule <pavel.stehule@gmail.com> 写道:

út 28. 1. 2020 v 17:01 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:

2020年1月24日 上午4:47,Robert Haas <robertmhaas@gmail.com <mailto:robertmhaas@gmail.com>> 写道:

On Sat, Jan 11, 2020 at 8:51 PM Tomas Vondra
<tomas.vondra@2ndquadrant.com <mailto:tomas.vondra@2ndquadrant.com>> wrote:

I proposed just ignoring those new indexes because it seems much simpler
than alternative solutions that I can think of, and it's not like those
other solutions don't have other issues.

+1.

I complete the implementation of this feature.
When a session x create an index idx_a on GTT A then
For session x, idx_a is valid when after create index.
For session y, before session x create index done, GTT A has some data, then index_a is invalid.
For session z, before session x create index done, GTT A has no data, then index_a is valid.

For example, I've looked at the "on demand" building as implemented in
global_private_temp-8.patch, I kinda doubt adding a bunch of index build
calls into various places in index code seems somewht suspicious.

+1. I can't imagine that's a safe or sane thing to do.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;
The Enterprise PostgreSQL Company

Opinion by Pavel
+ rel->rd_islocaltemp = true; <<<<<<< if this is valid, then the name of field "rd_islocaltemp" is not probably best
I renamed rd_islocaltemp

I don't see any change?

Rename rd_islocaltemp to rd_istemp in global_temporary_table_v8-pg13.patch

Wenjing

Show quoted text

Opinion by Konstantin Knizhnik
1 Fixed comments
2 Fixed assertion

Please help me review.

Wenjing

Attachments:

global_temporary_table_v8-pg13.patchapplication/octet-stream; name=global_temporary_table_v8-pg13.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 79430d2..babb5f3 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -158,6 +158,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * For global temp table only
+	 * use ShareUpdateExclusiveLock for ensure safety
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temp table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},	
 	/* list terminator */
 	{{NULL}}
 };
@@ -1486,6 +1499,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1586,13 +1601,18 @@ build_reloptions(Datum reloptions, bool validate,
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	/*
 	 * There are no options for partitioned tables yet, but this is able to do
 	 * some validation.
 	 */
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index dd975b1..1610e7d 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1013,7 +1013,9 @@ gistGetFakeLSN(Relation rel)
 {
 	static XLogRecPtr counter = FirstNormalUnloggedLSN;
 
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	/* global temp is same as local temp table */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 4871b7f..16b00c9 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -149,7 +149,9 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	/* global temp table is same as local temp table */
+	if (!(index->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		index->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 3fa4b76..b54882d 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -598,7 +598,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -651,7 +651,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 8ce5011..0d6ae76 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -62,6 +62,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -399,9 +400,9 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
 	Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
-	/* not every AM requires these to be valid, but heap does */
-	Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
-	Assert(MultiXactIdIsValid(onerel->rd_rel->relminmxid));
+	/* not every AM requires these to be valid, but regular heap does */
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ MultiXactIdIsValid(onerel->rd_rel->relminmxid));
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index f05cbe7..946c9d2 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -763,7 +763,14 @@ _bt_getbuf(Relation rel, BlockNumber blkno, int access)
 		/* Read an existing block of the relation */
 		buf = ReadBuffer(rel, blkno);
 		LockBuffer(buf, access);
-		_bt_checkpage(rel, buf);
+
+		/* global temp table may be not yet initialized for this backend. */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			blkno == BTREE_METAPAGE &&
+			PageIsNew(BufferGetPage(buf)))
+			_bt_initmetapage(BufferGetPage(buf), P_NONE, 0);
+		else
+			_bt_checkpage(rel, buf);
 	}
 	else
 	{
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 6e09ded..a2059fa 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6325,6 +6325,10 @@ StartupXLOG(void)
 	else
 		recoveryTargetTLI = ControlFile->checkPointCopy.ThisTimeLineID;
 
+	/* clean temp relation files */
+	if (max_active_gtt > 0)
+		RemovePgTempFiles();
+
 	/*
 	 * Check for signal files, and if so set up state for offline recovery
 	 */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d5da81c..c753b0e 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -42,6 +42,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 16cb6d8..f28f2c2 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -398,7 +398,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/* global temp table is same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 0fdff29..210d019 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -404,6 +406,10 @@ heap_create(const char *relname,
 									 relpersistence,
 									 relkind);
 
+	/* global temp table not create storage file when catalog create */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		create_storage = false;
+
 	/*
 	 * Have the storage manager create the relation's disk file, if needed.
 	 *
@@ -427,7 +433,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -956,6 +962,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -994,8 +1001,18 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/* global temp table not remember transaction info in catalog */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1357,6 +1374,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1441,11 +1459,15 @@ heap_create_with_catalog(const char *relname,
 	 */
 	StoreConstraints(new_rel_desc, cooked_constraints, is_internal);
 
-	/*
-	 * If there's a special on-commit action, remember it
-	 */
-	if (oncommit != ONCOMMIT_NOOP)
-		register_on_commit_action(relid, oncommit);
+	/* global temp table register action when storage init */
+	if (relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		/*
+		 * If there's a special on-commit action, remember it
+		 */
+		if (oncommit != ONCOMMIT_NOOP)
+			register_on_commit_action(relid, oncommit);
+	}
 
 	/*
 	 * ok, the relation has been cataloged, so close our relations and return
@@ -1937,6 +1959,13 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/* We allow to drop global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(rel->rd_node))
+			elog(ERROR, "can not drop relation when other backend attached this global temp table");
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3165,7 +3194,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3176,8 +3205,12 @@ RelationTruncateIndexes(Relation heapRelation)
 		Relation	currentIndex;
 		IndexInfo  *indexInfo;
 
+		if (RELATION_IS_GLOBAL_TEMP(heapRelation) &&
+			!gtt_storage_attached(indexId))
+			continue;
+
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3223,8 +3256,13 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		/* truncate global temp table only need RowExclusiveLock */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3257,6 +3295,8 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
+	bool		truncate_toastrel = false;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3265,23 +3305,40 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	toastrelid = rel->rd_rel->reltoastrelid;
+
+	/*
+	 * Truncate global temp table only need RowExclusiveLock
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		lockmode = RowExclusiveLock;
+		if (OidIsValid(toastrelid) &&
+			gtt_storage_attached(toastrelid))
+			truncate_toastrel = true;
+	}
+	else if (OidIsValid(toastrelid))
+		truncate_toastrel = true;
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
-	toastrelid = rel->rd_rel->reltoastrelid;
-	if (OidIsValid(toastrelid))
+	if (truncate_toastrel)
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(rel, 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 8880586..0852073 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -52,6 +52,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -877,6 +878,22 @@ index_create(Relation heapRelation,
 						indexRelationName, RelationGetRelationName(heapRelation))));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		if (accessMethodObjectId != BTREE_AM_OID)
+			elog(ERROR, "only support btree index on global temp table");
+
+		/* No support create index on global temp table use concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot create indexes on global temporary tables using concurrent mode")));
+
+		/* if global temp table not init storage, then skip build index */
+		if (!gtt_storage_attached(heapRelation->rd_node.relNode))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -2054,6 +2071,13 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/* We allow to drop index on global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation))
+	{
+		if (is_other_backend_use_gtt(userHeapRelation->rd_node))
+			elog(ERROR, "can not drop index when other backend attached this global temp table.");
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2660,6 +2684,11 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	/* update index stats into localhash and rel_rd_rel for global temp table */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		is_gtt = true;
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2745,21 +2774,35 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
+		if (is_gtt)
+			rel->rd_rel->relpages = (int32) relpages;
+		else if (rd_rel->relpages != (int32) relpages)
 		{
 			rd_rel->relpages = (int32) relpages;
 			dirty = true;
 		}
-		if (rd_rel->reltuples != (float4) reltuples)
+
+		if (is_gtt)
+			rel->rd_rel->reltuples = (float4) reltuples;
+		else if (rd_rel->reltuples != (float4) reltuples)
 		{
 			rd_rel->reltuples = (float4) reltuples;
 			dirty = true;
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+
+		if (is_gtt)
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+		else if (rd_rel->relallvisible != (int32) relallvisible)
 		{
 			rd_rel->relallvisible = (int32) relallvisible;
 			dirty = true;
 		}
+
+		if (is_gtt)
+		{
+			up_gtt_relstats(rel, relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
+		}
 	}
 
 	/*
@@ -2873,6 +2916,15 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, index, val);
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		if (!gtt_storage_attached(indexRelation->rd_node.relNode))
+		{
+			gtt_force_enable_index(indexRelation);
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3468,6 +3520,15 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 				 errmsg("cannot reindex temporary tables of other sessions")));
 
 	/*
+	 * Because global temp table cannot change relfilenode
+	 * no support reindex on global temp table yet.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(iRel))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot reindex global temporary tables")));
+
+	/*
 	 * Also check for active uses of the index in the current transaction; we
 	 * don't want to reindex underneath an open indexscan.
 	 */
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index e70243a..301da79 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -647,6 +647,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		/* global temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp relations in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index fddfbf1..671c614 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -26,6 +26,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -75,7 +76,7 @@ static PendingRelDelete *pendingDeletes = NULL; /* head of linked list */
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -85,6 +86,8 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		/* global temp table use same storage strategy as local temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -117,6 +120,10 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/* remember global temp table storage info to localhash */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel)
+		remember_gtt_storage_info(rnode, rel);
+
 	return srel;
 }
 
@@ -486,8 +493,15 @@ smgrDoPendingDeletes(bool isCommit)
 		smgrdounlinkall(srels, nrels, false);
 
 		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* clean global temp table flags when transaction commit or rollback */
+			if (SmgrIsTemp(srels[i]) && 
+				gtt_storage_attached(srels[i]->smgr_rnode.node.relNode))
+				forget_gtt_storage_info(srels[i]->smgr_rnode.node.relNode);
+		}
+
 		pfree(srels);
 	}
 }
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..24a7e77
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1174 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  code to create and destroy physical storage for global temparary table
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/table.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_relstats_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+typedef struct gtt_ctl_data
+{
+	LWLock			lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct
+{
+	RelFileNode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+	Oid			relid;
+
+	Oid			spcnode;
+	/* pg_class stat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+	char		relkind;
+	bool		on_commit_delete;
+
+	/* pg_statistic */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(RelFileNode rnode);
+static void gtt_storage_checkout(RelFileNode rnode, bool skiplock);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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 (rel->rd_rel->relkind == RELKIND_INDEX &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create stroage file", RelationGetRelationName(rel));
+
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		/* First time through: initialize the hash table */
+		HASHCTL		ctl;
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		gtt_storage_local_hash =
+			hash_create("global temp relation table",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_relstats_context =
+			AllocSetContextCreate(CacheMemoryContext,
+							"gtt relstats context",
+							ALLOCSET_DEFAULT_SIZES);
+	}
+
+	/* Look up or create an entry */
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_ENTER, &found);
+
+	if (found)
+	{
+		elog(ERROR, "backend %d relid %u already exists in global temp table local hash",
+					MyBackendId, relid);
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+	entry->spcnode = rnode.spcNode;
+	entry->relpages = 0;
+	entry->reltuples = 0;
+	entry->relallvisible = 0;
+	entry->relkind = rel->rd_rel->relkind;
+	/* only heap contain transaction information */
+	if (entry->relkind == RELKIND_RELATION || entry->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		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;
+		int			i;
+
+		rnode.spcNode = entry->spcnode;
+		rnode.dbNode = MyDatabaseId;
+		rnode.relNode = entry->relid;
+
+		gtt_storage_checkout(rnode, false);
+
+		Assert(TransactionIdIsNormal(entry->relfrozenxid));
+		remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+
+		for (i = 0; i < entry->natts; i++)
+		{
+			if (entry->att_stat_tups[i])
+			{
+				heap_freetuple(entry->att_stat_tups[i]);
+				entry->att_stat_tups[i] = NULL;
+			}
+		}
+	}
+
+	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.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (gtt_storage_local_hash == NULL)
+		return false;
+
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_FIND, &found);
+
+	if (!found)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	if (relpages)
+		*relpages = entry->relpages;
+
+	if (reltuples)
+		*reltuples = entry->reltuples;
+
+	if (relallvisible)
+		*relallvisible = entry->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = entry->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = entry->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	MemoryContext			oldcontext;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+						(void *) &reloid, HASH_FIND, &found);
+
+	if (!found)
+		return;
+
+	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;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	HeapTuple	tuple;
+	Relation	rel = NULL;
+	int		attnum = PG_GETARG_INT32(1);
+	Oid		reloid = PG_GETARG_OID(0);
+	char		rel_persistence;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	  tupdesc;
+	MemoryContext oldcontext;
+	Tuplestorestate *tupstore;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = table_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+
+	table_close(rel, NoLock);
+	table_close(pg_tatistic, AccessShareLock);
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate	*tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Relation	rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	if (get_gtt_relstats(reloid,
+						&relpages, &reltuples, &relallvisible,
+						&relfrozenxid, &relminmxid))
+	{
+		Datum	values[5];
+		bool	isnull[5];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = Int32GetDatum(relpages);
+		values[1] = Float4GetDatum((float4)reltuples);
+		values[2] = Int32GetDatum(relallvisible);
+		values[3] = UInt32GetDatum(relfrozenxid);
+		values[4] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+
+	tuplestore_donestoring(tupstore);
+	table_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(rel->rd_node);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	table_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(indexOid));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid relOid = index->rd_index->indrelid;
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	if (gtt_storage_attached(relOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index c9e75f4..2adde62 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM 
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c4420dd..977a984 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -586,14 +587,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 		 * pg_statistic for columns we didn't process, we leave them alone.)
 		 */
 		update_attstats(RelationGetRelid(onerel), inh,
-						attr_cnt, vacattrstats);
+						attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
 
 		for (ind = 0; ind < nindexes; ind++)
 		{
 			AnlIndexData *thisdata = &indexdata[ind];
 
 			update_attstats(RelationGetRelid(Irel[ind]), false,
-							thisdata->attr_cnt, thisdata->vacattrstats);
+							thisdata->attr_cnt, thisdata->vacattrstats,
+							RelationGetRelPersistence(Irel[ind]));
 		}
 
 		/*
@@ -1456,7 +1458,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
  *		by taking a self-exclusive lock on the relation in analyze_rel().
  */
 static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
 {
 	Relation	sd;
 	int			attno;
@@ -1558,31 +1560,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/* Update column statistic to localhash, not catalog */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index e9d7a7f..3088e26 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -391,6 +391,12 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/* not support cluster global temp table yet */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("not support cluster global temporary tables yet")));
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 40a8ec1..e7b4e44 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1062,7 +1062,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !rel->rd_istemp)
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, stmt->filename, stmt->is_program,
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index ec20ba3..a29cc00 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2614,6 +2614,16 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 			!isTempNamespace(classtuple->relnamespace))
 			continue;
 
+		/* not support reindex on global temp table, so skip it */
+		if (classtuple->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			ereport(WARNING,
+				(errmsg("global temp table \"%s.%s\" skip reindexed",
+					get_namespace_name(get_rel_namespace(relid)),
+					get_rel_name(relid))));
+			continue;
+		}
+
 		/* Check user/system classification, and optionally skip */
 		if (objectKind == REINDEX_OBJECT_SYSTEM &&
 			!IsSystemClass(relid, classtuple))
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 329ab84..dfa257c 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -96,7 +96,8 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 	 * transaction.
 	 */
 	relpersistence = get_rel_persistence(relid);
-	if (relpersistence == RELPERSISTENCE_TEMP)
+	if (relpersistence == RELPERSISTENCE_TEMP ||
+		relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
 
 	/* Check permissions. */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..5787674 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -94,7 +94,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +108,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -222,7 +223,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* now initialize the sequence's data */
 	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -327,7 +328,7 @@ ResetSequence(Oid seq_relid)
 	/*
 	 * Insert the modified tuple into the new storage file.
 	 */
-	fill_seq_with_data(seq_rel, tuple);
+	fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -340,17 +341,21 @@ ResetSequence(Oid seq_relid)
  * Initialize a sequence's relation with the specified tuple as content
  */
 static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 	sequence_magic *sm;
 	OffsetNumber offnum;
+	bool lockBuffer = false;
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ReadBuffer(rel, P_NEW);
-	Assert(BufferGetBlockNumber(buf) == 0);
+	if (buf == InvalidBuffer)
+	{
+		buf = ReadBuffer(rel, P_NEW);
+		Assert(BufferGetBlockNumber(buf) == 0);
+		lockBuffer = true;
+	}
 
 	page = BufferGetPage(buf);
 
@@ -360,7 +365,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* Now insert sequence tuple */
 
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+	if (lockBuffer)
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +416,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	END_CRIT_SECTION();
 
-	UnlockReleaseBuffer(buf);
+	if (lockBuffer)
+		UnlockReleaseBuffer(buf);
 }
 
 /*
@@ -502,7 +509,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * Insert the modified tuple into the new storage file.
 		 */
-		fill_seq_with_data(seqrel, newdatatuple);
+		fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
 	}
 
 	/* process OWNED BY if given */
@@ -611,7 +618,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!seqrel->rd_istemp)
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +943,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!seqrel->rd_istemp)
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1178,6 +1185,25 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
 	LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
 
 	page = BufferGetPage(*buf);
+	if (GlobalTempRelationPageIsNotInitialized(rel, page))
+	{
+		/* Initialize sequence for global temporary tables */
+		Datum		value[SEQ_COL_LASTCOL] = {0};
+		bool		null[SEQ_COL_LASTCOL] = {false};
+		HeapTuple	tuple;
+		int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+		/*
+		 * last_value from pg_sequence.seqstart
+		 * log_cnt = 0
+		 * is_called = false
+		 */
+		value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+		tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+		fill_seq_with_data(rel, tuple, *buf);
+		heap_freetuple(tuple);
+	}
 	sm = (sequence_magic *) PageGetSpecialPointer(page);
 
 	if (sm->magic != SEQ_MAGIC)
@@ -1954,3 +1980,23 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 70589dd..dfe7cc0 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -552,6 +553,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 								  Relation partitionTbl);
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static bool has_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -597,6 +599,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	bool		has_oncommit_clause = false;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -607,8 +610,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Check consistency of arguments
 	 */
+	/* global temp table same as local temp table */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !(stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+			 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -638,7 +643,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	/* global temp table same as local temp table */
+	if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+		 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -739,6 +746,57 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* global temp table */
+	has_oncommit_clause = has_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* inherit table or parition table inherit on commit property from parent table*/
+		if (inheritOids && stmt->oncommit == ONCOMMIT_NOOP && !has_oncommit_clause)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			if (RELATION_GTT_ON_COMMIT_DELETE(relation))
+				stmt->oncommit = ONCOMMIT_DELETE_ROWS;
+			else
+				stmt->oncommit = ONCOMMIT_PRESERVE_ROWS;
+			table_close(relation, NoLock);
+		}
+
+		if (has_oncommit_clause)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "can not defeine global temp table with on commit and with clause at same time");
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temp table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (has_oncommit_clause)
+		elog(ERROR, "regular table cannot specifie on_commit_delete_rows");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1816,7 +1874,8 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
 		 * table or the current physical file to be thrown away anyway.
 		 */
 		if (rel->rd_createSubid == mySubid ||
-			rel->rd_newRelfilenodeSubid == mySubid)
+			rel->rd_newRelfilenodeSubid == mySubid ||
+			RELATION_IS_GLOBAL_TEMP(rel))
 		{
 			/* Immediate, non-rollbackable truncation is OK */
 			heap_truncate_one_rel(rel);
@@ -2259,7 +2318,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 
 		/* If existing rel is temp, it must belong to this session */
 		if (relation->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
-			!relation->rd_islocaltemp)
+			!relation->rd_istemp)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg(!is_partition
@@ -3375,6 +3434,13 @@ RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal, bo
 	 * specially.
 	 */
 	targetrelation = relation_open(myrelid, is_index ? ShareUpdateExclusiveLock : AccessExclusiveLock);
+
+	if (RELATION_IS_GLOBAL_TEMP(targetrelation))
+	{
+		if (is_other_backend_use_gtt(targetrelation->rd_node))
+			elog(ERROR, "can not rename relation when other backend attached this global temp table");
+	}
+
 	namespaceId = RelationGetNamespace(targetrelation);
 
 	/*
@@ -3550,6 +3616,13 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(rel->rd_node))
+			elog(ERROR, "can not alter relation when other backend attached this global temp table");
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -8123,6 +8196,13 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 				 errmsg("referenced relation \"%s\" is not a table",
 						RelationGetRelationName(pkrel))));
 
+	/* global temp table not support foreign key constraint yet */
+	if (RELATION_IS_GLOBAL_TEMP(pkrel))
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("referenced relation \"%s\" is not a global temp table",
+						RelationGetRelationName(pkrel))));
+
 	if (!allowSystemTableMods && IsSystemRelation(pkrel))
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -8157,11 +8237,17 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables may reference only temporary tables")));
-			if (!pkrel->rd_islocaltemp || !rel->rd_islocaltemp)
+			if (!pkrel->rd_istemp || !rel->rd_istemp)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		/* global temp table not support foreign key constraint yet */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("not support foreign key constraints on global temp table yet")));
+			break;
 	}
 
 	/*
@@ -13199,7 +13285,7 @@ index_copy_data(Relation rel, RelFileNode newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
@@ -13314,14 +13400,14 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 
 	/* If parent rel is temp, it must belong to this session */
 	if (parent_rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
-		!parent_rel->rd_islocaltemp)
+		!parent_rel->rd_istemp)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot inherit from temporary relation of another session")));
 
 	/* Ditto for the child */
 	if (child_rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
-		!child_rel->rd_islocaltemp)
+		!child_rel->rd_istemp)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot inherit to temporary relation of another session")));
@@ -14606,7 +14692,9 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
 	 */
 	switch (rel->rd_rel->relpersistence)
 	{
+		/* global temp table same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -16096,14 +16184,14 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 
 	/* If the parent is temp, it must belong to this session */
 	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
-		!rel->rd_islocaltemp)
+		!rel->rd_istemp)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot attach as partition of temporary relation of another session")));
 
 	/* Ditto for the partition */
 	if (attachrel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
-		!attachrel->rd_islocaltemp)
+		!attachrel->rd_istemp)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot attach temporary relation of another session as partition")));
@@ -17246,3 +17334,20 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
 		table_close(rel, NoLock);
 	}
 }
+
+static bool
+has_oncommit_option(List *options)
+{
+	ListCell   *listptr;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (pg_strcasecmp(def->defname, "on_commit_delete_rows") == 0)
+			return true;
+	}
+
+	return false;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index d625d17..69ad24f 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/vacuum.h"
@@ -1217,6 +1218,17 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	 /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		is_gtt = true;
+		up_gtt_relstats(relation,
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1230,17 +1242,26 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relpages = (int32) num_pages;
+	else if (pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (is_gtt)
+		relation->rd_rel->reltuples = (float4) num_tuples;
+	else if (pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	else if (pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1285,7 +1306,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1296,7 +1318,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1394,6 +1417,10 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/* global temp table relstats not in pg_class */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1451,6 +1478,25 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/*
+	 * Global temp table get frozenxid from MyProc
+	 * to avoid the vacuum truncate clog that gtt need.
+	 */
+	if (max_active_gtt > 0)
+	{
+		TransactionId oldest_gtt_frozenxid =
+			list_all_session_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid) &&
+			TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+		{
+			ereport(WARNING,
+				(errmsg("global temp table oldest FrozenXid is far in the past"),
+				 errhint("please truncate them or kill those sessions that use them.")));
+			newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 8286d9c..c3992a4 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -591,6 +591,8 @@ static void
 set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 						  RangeTblEntry *rte)
 {
+	char relpersistence;
+
 	/*
 	 * The flag has previously been initialized to false, so we can just
 	 * return if it becomes clear that we can't safely set it.
@@ -618,7 +620,11 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			/* global temp table is same as local temp table */
+			relpersistence = get_rel_persistence(rte->relid);
+
+			if (relpersistence == RELPERSISTENCE_TEMP ||
+				relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index d6f2153..310a9e2 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6312,7 +6312,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
+	/* global temp table is same as local temp table */
 	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 748bebf..a5ddbcd 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2579,6 +2579,11 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("materialized views must not use temporary tables or views")));
 
+		if (is_query_using_gtt(query))
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("materialized views must not use global temporary tables or views")));
+
 		/*
 		 * A materialized view would either need to save parameters for use in
 		 * maintaining/loading the data or prohibit them entirely.  The latter
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ba5916b..0ee6931 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3290,17 +3290,11 @@ OptTemp:	TEMPORARY					{ $$ = RELPERSISTENCE_TEMP; }
 			| LOCAL TEMP				{ $$ = RELPERSISTENCE_TEMP; }
 			| GLOBAL TEMPORARY
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
-					$$ = RELPERSISTENCE_TEMP;
+					$$ = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
-					$$ = RELPERSISTENCE_TEMP;
+					$$ = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED					{ $$ = RELPERSISTENCE_UNLOGGED; }
 			| /*EMPTY*/					{ $$ = RELPERSISTENCE_PERMANENT; }
@@ -11588,19 +11582,13 @@ OptTempTableName:
 				}
 			| GLOBAL TEMPORARY opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED opt_table qualified_name
 				{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b875a50..123945b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index ee2d2b5..9c9abaa 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 6d1f28c..ed837d1 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2086,6 +2086,11 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* autovacuum skip vacuum global temp table */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2152,7 +2157,9 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		/* autovacuum skip vacuum global temp table */
+		if (classForm->relpersistence == RELPERSISTENCE_TEMP || 
+			classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index aba3960..3c4b96c 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -52,6 +53,7 @@
 #include "utils/rel.h"
 #include "utils/resowner_private.h"
 #include "utils/timestamp.h"
+#include "utils/guc.h"
 
 
 /* Note: these two macros only work on shared buffers, not local ones! */
@@ -432,7 +434,7 @@ ForgetPrivateRefCountEntry(PrivateRefCountEntry *ref)
 static Buffer ReadBuffer_common(SMgrRelation reln, char relpersistence,
 								ForkNumber forkNum, BlockNumber blockNum,
 								ReadBufferMode mode, BufferAccessStrategy strategy,
-								bool *hit);
+								bool *hit, Relation rel);
 static bool PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy);
 static void PinBuffer_Locked(BufferDesc *buf);
 static void UnpinBuffer(BufferDesc *buf, bool fixOwner);
@@ -664,7 +666,8 @@ ReadBufferExtended(Relation reln, ForkNumber forkNum, BlockNumber blockNum,
 	 */
 	pgstat_count_buffer_read(reln);
 	buf = ReadBuffer_common(reln->rd_smgr, reln->rd_rel->relpersistence,
-							forkNum, blockNum, mode, strategy, &hit);
+							forkNum, blockNum, mode, strategy, &hit,
+							reln);
 	if (hit)
 		pgstat_count_buffer_hit(reln);
 	return buf;
@@ -692,7 +695,7 @@ ReadBufferWithoutRelcache(RelFileNode rnode, ForkNumber forkNum,
 	Assert(InRecovery);
 
 	return ReadBuffer_common(smgr, RELPERSISTENCE_PERMANENT, forkNum, blockNum,
-							 mode, strategy, &hit);
+							 mode, strategy, &hit, NULL);
 }
 
 
@@ -704,7 +707,8 @@ ReadBufferWithoutRelcache(RelFileNode rnode, ForkNumber forkNum,
 static Buffer
 ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 				  BlockNumber blockNum, ReadBufferMode mode,
-				  BufferAccessStrategy strategy, bool *hit)
+				  BufferAccessStrategy strategy, bool *hit,
+				  Relation rel)
 {
 	BufferDesc *bufHdr;
 	Block		bufBlock;
@@ -719,6 +723,15 @@ ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 
 	isExtend = (blockNum == P_NEW);
 
+	/* create storage when first read data page for gtt */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(isExtend || blockNum == 0) &&
+		forkNum == MAIN_FORKNUM &&
+		!gtt_storage_attached(smgr->smgr_rnode.node.relNode))
+	{
+		RelationCreateStorage(smgr->smgr_rnode.node, relpersistence, rel);
+	}
+
 	TRACE_POSTGRESQL_BUFFER_READ_START(forkNum, blockNum,
 									   smgr->smgr_rnode.node.spcNode,
 									   smgr->smgr_rnode.node.dbNode,
@@ -2809,6 +2822,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(relation->rd_node.relNode))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d5..7adb969 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -21,6 +21,7 @@
 #include "access/nbtree.h"
 #include "access/subtrans.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, active_gtt_shared_hash_size());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* global temporary table */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index c3adb2e..eb95e5f 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -62,6 +62,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -4087,3 +4088,77 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temp table.
+ */
+int
+list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct *arrayP = procArray;
+	TransactionId result = InvalidTransactionId;
+	int			index;
+	int			flags = 0;
+	int			i = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+		*n = 0;
+	}
+
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		volatile PGXACT *pgxact = &allPgXact[pgprocno];
+
+		if (pgxact->vacuumFlags & flags)
+			continue;
+
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->session_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->session_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->session_gtt_frozenxid, result))
+				result = proc->session_gtt_frozenxid;
+
+			if (max_size > 0)
+			{
+				pids[i] = proc->pid;
+				xids[i] = proc->session_gtt_frozenxid;
+				i++;
+			}
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (max_size > 0)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 32df8c8..e4e1125 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c
index c5b771c..e7725ce 100644
--- a/src/backend/storage/smgr/md.c
+++ b/src/backend/storage/smgr/md.c
@@ -646,6 +646,12 @@ mdread(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
 		 */
 		if (zero_damaged_pages || InRecovery)
 			MemSet(buffer, 0, BLCKSZ);
+		else if(SmgrIsTemp(reln) && blocknum == 0 && forknum == MAIN_FORKNUM)
+		{
+			/* global temp table init btree meta page */
+			MemSet(buffer, 0, BLCKSZ);
+			mdwrite(reln, forknum, blocknum, buffer, true);
+		}
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_DATA_CORRUPTED),
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 8406644..0d99321 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -1008,6 +1008,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 7c6f057..eb10cf2 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -112,6 +112,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -4578,12 +4579,25 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -4708,15 +4722,27 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6098,6 +6124,7 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	{
 		/* Simple variable --- look to stats for the underlying table */
 		RangeTblEntry *rte = planner_rt_fetch(index->rel->relid, root);
+		char	rel_persistence = get_rel_persistence(rte->relid);
 
 		Assert(rte->rtekind == RTE_RELATION);
 		relid = rte->relid;
@@ -6115,6 +6142,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6126,6 +6160,8 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	}
 	else
 	{
+		char	rel_persistence = get_rel_persistence(index->indexoid);
+
 		/* Expression --- maybe there are stats for the index itself */
 		relid = index->indexoid;
 		colnum = 1;
@@ -6141,6 +6177,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7054,6 +7097,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		/* attempt to lookup stats in relation for this index column */
 		if (attnum != 0)
 		{
+			char	rel_persistence = get_rel_persistence(rte->relid);
+
 			/* Simple variable -- look to stats for the underlying table */
 			if (get_relation_stats_hook &&
 				(*get_relation_stats_hook) (root, rte, attnum, &vardata))
@@ -7066,6 +7111,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7078,6 +7131,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		}
 		else
 		{
+			char	rel_persistence = get_rel_persistence(index->indexoid);
+
 			/*
 			 * Looks like we've found an expression column in the index. Let's
 			 * see if there's any stats for it.
@@ -7097,6 +7152,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 1e3e6d3..345e3e9 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -2878,6 +2879,18 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index df025a5..e33269b 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -63,6 +63,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1095,13 +1096,13 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELPERSISTENCE_UNLOGGED:
 		case RELPERSISTENCE_PERMANENT:
 			relation->rd_backend = InvalidBackendId;
-			relation->rd_islocaltemp = false;
+			relation->rd_istemp = false;
 			break;
 		case RELPERSISTENCE_TEMP:
 			if (isTempOrTempToastNamespace(relation->rd_rel->relnamespace))
 			{
 				relation->rd_backend = BackendIdForTempRelations();
-				relation->rd_islocaltemp = true;
+				relation->rd_istemp = true;
 			}
 			else
 			{
@@ -1114,14 +1115,36 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				 * from a crashed backend that coincidentally had the same
 				 * BackendId we're using.  We should *not* consider such a
 				 * table to be "ours"; this is why we need the separate
-				 * rd_islocaltemp flag.  The pg_class entry will get flushed
+				 * rd_istemp flag.  The pg_class entry will get flushed
 				 * if/when we clean out the corresponding temp table namespace
 				 * in preparation for using it.
 				 */
 				relation->rd_backend =
 					GetTempNamespaceBackendId(relation->rd_rel->relnamespace);
 				Assert(relation->rd_backend != InvalidBackendId);
-				relation->rd_islocaltemp = false;
+				relation->rd_istemp = false;
+			}
+			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				/*
+				 * For global temp table, all backend can use
+				 * this relation, so rd_istemp always true.
+				 */
+				relation->rd_istemp = true;
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
 			}
 			break;
 		default:
@@ -1178,6 +1201,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			gtt_fix_index_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -1815,7 +1839,7 @@ formrdesc(const char *relationName, Oid relationReltype,
 	relation->rd_createSubid = InvalidSubTransactionId;
 	relation->rd_newRelfilenodeSubid = InvalidSubTransactionId;
 	relation->rd_backend = InvalidBackendId;
-	relation->rd_islocaltemp = false;
+	relation->rd_istemp = false;
 
 	/*
 	 * initialize relation tuple form
@@ -2217,6 +2241,8 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		gtt_fix_index_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3306,12 +3332,16 @@ RelationBuildLocalRelation(const char *relname,
 		case RELPERSISTENCE_UNLOGGED:
 		case RELPERSISTENCE_PERMANENT:
 			rel->rd_backend = InvalidBackendId;
-			rel->rd_islocaltemp = false;
+			rel->rd_istemp = false;
 			break;
 		case RELPERSISTENCE_TEMP:
 			Assert(isTempOrTempToastNamespace(relnamespace));
 			rel->rd_backend = BackendIdForTempRelations();
-			rel->rd_islocaltemp = true;
+			rel->rd_istemp = true;
+			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_istemp = true;
 			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
@@ -3427,6 +3457,9 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
 
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+		elog(ERROR, "global temp table does not allow setting new relfilenode");
+
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
@@ -3467,7 +3500,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 				/* handle these directly, at least for now */
 				SMgrRelation srel;
 
-				srel = RelationCreateStorage(newrnode, persistence);
+				srel = RelationCreateStorage(newrnode, persistence, relation);
 				smgrclose(srel);
 			}
 			break;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index cacbe90..9c4220b 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -140,6 +140,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temp table feature.
+ * global temp table define can still storage in catalog
+ * just can not use.
+ * num > 0 means database can management num active global temp table.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2012,6 +2024,15 @@ static struct config_bool ConfigureNamesBool[] =
 static struct config_int ConfigureNamesInt[] =
 {
 	{
+		{"max_active_global_temporary_table", PGC_POSTMASTER, UNGROUPED,
+			gettext_noop("max active global temporary table."),
+			NULL
+		},
+		&max_active_gtt,
+		DEFAULT_NUM_ACTIVE_GTT, MIN_NUM_ACTIVE_GTT, MAX_NUM_ACTIVE_GTT,
+		NULL, NULL, NULL
+	},
+	{
 		{"archive_timeout", PGC_SIGHUP, WAL_ARCHIVING,
 			gettext_noop("Forces a switch to the next WAL file if a "
 						 "new file has not been started within N seconds."),
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 799b698..b98d396 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15583,6 +15583,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	{
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
+		char		*table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15634,9 +15635,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 			binary_upgrade_set_pg_class_oids(fout, q,
 											 tbinfo->dobj.catId.oid, false);
 
+		if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED)
+			table_type = "UNLOGGED ";
+		else if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			table_type = "GLOBAL TEMPORARY ";
+		else
+			table_type = "";
+
 		appendPQExpBuffer(q, "CREATE %s%s %s",
-						  tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ?
-						  "UNLOGGED " : "",
+						  table_type,
 						  reltypename,
 						  qualrelname);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index f3c7eb9..28134e2 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3723,7 +3723,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index dc03fbd..8bd6d09 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1016,6 +1016,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2378,6 +2380,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2586,6 +2591,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index a12fc1f..78958e4 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -165,6 +165,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index bef50c7..bc36c6c 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5504,6 +5504,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4191',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4192',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{relid,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4193',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4194',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 048003c..af48cdf 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -19,7 +19,7 @@
 #include "storage/smgr.h"
 #include "utils/relcache.h"
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationTruncate(Relation rel, BlockNumber nblocks);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..aa80cb5
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,41 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "utils/relcache.h"
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid);
+extern bool is_other_backend_use_gtt(RelFileNode node);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(RelFileNode node);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Relation relation,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void gtt_force_enable_index(Relation index);
+extern void gtt_fix_index_state(Relation index);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 870ecb5..92c590e 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 8fda8e4..172eae9 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_TBM,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_SXACT,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index d217801..8adde87 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -117,6 +117,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId session_gtt_frozenxid;	/* session level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ce93ace..0f7262e 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -281,6 +281,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 44ed04d..9f29744 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	struct SMgrRelationData *rd_smgr;	/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_istemp;		/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -277,6 +277,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -535,7 +536,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
@@ -546,7 +548,7 @@ typedef struct ViewOptions
  * Beware of multiple eval of argument
  */
 #define RELATION_IS_LOCAL(relation) \
-	((relation)->rd_islocaltemp || \
+	((relation)->rd_istemp || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -557,7 +559,7 @@ typedef struct ViewOptions
  */
 #define RELATION_IS_OTHER_TEMP(relation) \
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
-	 !(relation)->rd_islocaltemp)
+	 !(relation)->rd_istemp)
 
 
 /*
@@ -602,6 +604,17 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..50ca9ac
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,7 @@
+reset search_path;
+drop schema gtt cascade;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..a477917
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,197 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+ERROR:  cannot create indexes on global temporary tables using concurrent mode
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+ERROR:  not support cluster global temporary tables yet
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ok
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  regular table cannot specifie on_commit_delete_rows
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  can not defeine global temp table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test;
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  referenced relation "products" is not a global temp table
+-- ERROR
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  referenced relation "products" is not a global temp table
+-- ERROR
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ALL ERROR
+create index idx_err on gtt1 using hash (a);
+ERROR:  only support btree index on global temp table
+create index idx_err on gtt1 using gist (a);
+ERROR:  data type integer has no default operator class for access method "gist"
+HINT:  You must specify an operator class for the index or define a default operator class for the data type.
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+ERROR:  only support btree index on global temp table
+create index idx_tmp_t0_1 on tmp_t0 using gist (c0);
+ERROR:  only support btree index on global temp table
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 15 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..30d8a7b
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,84 @@
+set search_path=gtt,sys;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..850ef3e
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,161 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          475136 |                 974848
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |           409600 |        409600 |                 409600
+(2 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..9fe5fd4
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,9 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..b258b7c
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,76 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+(1 row)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+(1 row)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 70e1e2f..30cf4bd 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1348,6 +1348,93 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d2b17dd..80e577f 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..f3cf710
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,6 @@
+
+
+reset search_path;
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..cfc1879
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,163 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ok
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test;
+
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ERROR
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ERROR
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ALL ERROR
+create index idx_err on gtt1 using hash (a);
+create index idx_err on gtt1 using gist (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_1 on tmp_t0 using gist (c0);
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d7d81de
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,42 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..5203c2b
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,76 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..2f4d883
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,16 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..f041892
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,42 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
#96Pavel Stehule
pavel.stehule@gmail.com
In reply to: 曾文旌(义从) (#95)
Re: [Proposal] Global temporary tables

út 28. 1. 2020 v 18:12 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
napsal:

2020年1月29日 上午12:40,Pavel Stehule <pavel.stehule@gmail.com> 写道:

út 28. 1. 2020 v 17:01 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
napsal:

2020年1月24日 上午4:47,Robert Haas <robertmhaas@gmail.com> 写道:

On Sat, Jan 11, 2020 at 8:51 PM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

I proposed just ignoring those new indexes because it seems much simpler
than alternative solutions that I can think of, and it's not like those
other solutions don't have other issues.

+1.

I complete the implementation of this feature.
When a session x create an index idx_a on GTT A then
For session x, idx_a is valid when after create index.
For session y, before session x create index done, GTT A has some data,
then index_a is invalid.
For session z, before session x create index done, GTT A has no data,
then index_a is valid.

For example, I've looked at the "on demand" building as implemented in
global_private_temp-8.patch, I kinda doubt adding a bunch of index build
calls into various places in index code seems somewht suspicious.

+1. I can't imagine that's a safe or sane thing to do.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

Opinion by Pavel
+ rel->rd_islocaltemp = true; <<<<<<< if this is valid, then the name of
field "rd_islocaltemp" is not probably best
I renamed rd_islocaltemp

I don't see any change?

Rename rd_islocaltemp to rd_istemp
in global_temporary_table_v8-pg13.patch

ok :)

Pavel

Show quoted text

Wenjing

Opinion by Konstantin Knizhnik
1 Fixed comments
2 Fixed assertion

Please help me review.

Wenjing

#97Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#96)
Re: [Proposal] Global temporary tables

út 28. 1. 2020 v 18:13 odesílatel Pavel Stehule <pavel.stehule@gmail.com>
napsal:

út 28. 1. 2020 v 18:12 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
napsal:

2020年1月29日 上午12:40,Pavel Stehule <pavel.stehule@gmail.com> 写道:

út 28. 1. 2020 v 17:01 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
napsal:

2020年1月24日 上午4:47,Robert Haas <robertmhaas@gmail.com> 写道:

On Sat, Jan 11, 2020 at 8:51 PM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

I proposed just ignoring those new indexes because it seems much simpler
than alternative solutions that I can think of, and it's not like those
other solutions don't have other issues.

+1.

I complete the implementation of this feature.
When a session x create an index idx_a on GTT A then
For session x, idx_a is valid when after create index.
For session y, before session x create index done, GTT A has some data,
then index_a is invalid.
For session z, before session x create index done, GTT A has no data,
then index_a is valid.

For example, I've looked at the "on demand" building as implemented in
global_private_temp-8.patch, I kinda doubt adding a bunch of index build
calls into various places in index code seems somewht suspicious.

+1. I can't imagine that's a safe or sane thing to do.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

Opinion by Pavel
+ rel->rd_islocaltemp = true; <<<<<<< if this is valid, then the name
of field "rd_islocaltemp" is not probably best
I renamed rd_islocaltemp

I don't see any change?

Rename rd_islocaltemp to rd_istemp
in global_temporary_table_v8-pg13.patch

ok :)

I found a bug

postgres=# create global temp table x(a int);
CREATE TABLE
postgres=# insert into x values(1);
INSERT 0 1
postgres=# create index on x (a);
CREATE INDEX
postgres=# create index on x((a + 1));
CREATE INDEX
postgres=# analyze x;
WARNING: oid 16468 not a relation
ANALYZE

other behave looks well for me.

Regards

Pavel

Show quoted text

Pavel

Wenjing

Opinion by Konstantin Knizhnik
1 Fixed comments
2 Fixed assertion

Please help me review.

Wenjing

#98Konstantin Knizhnik
k.knizhnik@postgrespro.ru
In reply to: Pavel Stehule (#92)
Re: [Proposal] Global temporary tables

On 27.01.2020 22:44, Pavel Stehule wrote:

I don't think so compatibility with Oracle is valid point in this
case. We need GTT, but the mechanism of index building should be
designed for Postgres, and for users.

Maybe the method proposed by you can be activated by some option like
CREATE INDEX IMMEDIATELY FOR ALL SESSION. When you use GTT without
index, then
it should to work some time more, and if you use short life sessions,
then index build can be last or almost last operation over table and
can be suboptimal.

Anyway, this behave can be changed later without bigger complications
- and now I am have strong opinion to prefer don't allow to any DDL
(with index creation) on any active GTT in other sessions.
Probably your proposal - build indexes on other sessions when GTT is
touched can share code with just modify metadata and wait on session
reset or GTT reset

Well, compatibility with Oracle was never treated as important argument
in this group:)
But I hope that you agree that it real argument against your proposal.
Much more important argument is incompatibility with behavior of regular
table.
If you propose such incompatibility, then you should have some very
strong arguments for such behavior which will definitely confuse users.

But I heard only two arguments:

1. Concurrent building of indexes by all backends may consume much
memory (n_backends * maintenance_work_mem) and consume a lot of disk/CPU
resources.

First of all it is not completely true. Indexes will be created on
demand when GTT will be accessed and chance that all sessions will
become building indexes simultaneously is very small.

But what will happen if we prohibit access to this index for existed
sessions? If we need index for GTT, then most likely it is used for joins.
If there is no index, then optimizer has to choose some other plan to
perform this join. For example use hash join. Hash join also requires
memory,
so if all backends will perform such join simultaneously, then them
consume (n_backends * work_mem) memory.
Yes, work_mem is used to be smaller than maintenance_work_mem. But in
any case DBA has a choice to adjust this parameters to avoid this problem.
And in case of your proposal (prohibit access to this index) you give
him no choice to optimize query execution in existed sessions.

Also if all sessions will simultaneously perform sequential scan of GTT
instead of building index for it, then them will read the same amount of
data and consume comparable CPU time.
So prohibiting access to the indexes will not save us from high
resources consumption if all existed sessions are really actively
working with this GTT.

2. GTT in one session can contains large amount of data and we need
index for it, but small amount of data in another session and we do not
need index for it.

Such situation definitely can happen. But it contradicts to the main
assumption of GTT use case (that it is accessed in the same way by all
sessions).
Also I may be agree with this argument if you propose to create indexes
locally for each sessions.
But your proposal is to prohibit access to the index to the sessions
which already have populated GTT with data but allow it for sessions
which have not accessed this GTT yet.
So if some session stores some data in GTT after index was created, then
it will build index for it, doesn't matter whether size of table is
small or large.
Why do we make an exception for sessions which already have data in GTT
in this case?

So from my point of view both arguments are doubtful and can not explain
why rules of index usability for GTT should be different from regular
tables.

Usually it is not hard problem to refresh sessions, and what I know
when you update plpgsql code, it is best practice to refresh session
early.

I know may systems where session is established once client is connected
to the system and not closed until client is disconnected.
And any attempt to force termination of the session will cause
application errors which are not expected by the client.

Sorry, I think that it is principle point in discussion concerning GTT
design.
Implementation of GTT can be changed in future, but it is bad if
behavior of GTT will be changed.
It is not clear for me why from the very beginning we should provide
inconsistent behavior which is even more difficult to implement than
behavior compatible with regular tables.
And say that in the future it can be changed...

Sorry, but I do not consider proposals to create indexes locally for
each session (i.e. global tables but private indexes) or use some
special complicated SQL syntax constructions like
CREATE INDEX IMMEDIATELY FOR ALL SESSION as some real alternatives which
have to be discussed.

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

#99曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Pavel Stehule (#97)
Re: [Proposal] Global temporary tables

2020年1月29日 上午1:54,Pavel Stehule <pavel.stehule@gmail.com> 写道:

út 28. 1. 2020 v 18:13 odesílatel Pavel Stehule <pavel.stehule@gmail.com <mailto:pavel.stehule@gmail.com>> napsal:

út 28. 1. 2020 v 18:12 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:

2020年1月29日 上午12:40,Pavel Stehule <pavel.stehule@gmail.com <mailto:pavel.stehule@gmail.com>> 写道:

út 28. 1. 2020 v 17:01 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:

2020年1月24日 上午4:47,Robert Haas <robertmhaas@gmail.com <mailto:robertmhaas@gmail.com>> 写道:

On Sat, Jan 11, 2020 at 8:51 PM Tomas Vondra
<tomas.vondra@2ndquadrant.com <mailto:tomas.vondra@2ndquadrant.com>> wrote:

I proposed just ignoring those new indexes because it seems much simpler
than alternative solutions that I can think of, and it's not like those
other solutions don't have other issues.

+1.

I complete the implementation of this feature.
When a session x create an index idx_a on GTT A then
For session x, idx_a is valid when after create index.
For session y, before session x create index done, GTT A has some data, then index_a is invalid.
For session z, before session x create index done, GTT A has no data, then index_a is valid.

For example, I've looked at the "on demand" building as implemented in
global_private_temp-8.patch, I kinda doubt adding a bunch of index build
calls into various places in index code seems somewht suspicious.

+1. I can't imagine that's a safe or sane thing to do.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;
The Enterprise PostgreSQL Company

Opinion by Pavel
+ rel->rd_islocaltemp = true; <<<<<<< if this is valid, then the name of field "rd_islocaltemp" is not probably best
I renamed rd_islocaltemp

I don't see any change?

Rename rd_islocaltemp to rd_istemp in global_temporary_table_v8-pg13.patch

ok :)

I found a bug

postgres=# create global temp table x(a int);
CREATE TABLE
postgres=# insert into x values(1);
INSERT 0 1
postgres=# create index on x (a);
CREATE INDEX
postgres=# create index on x((a + 1));
CREATE INDEX
postgres=# analyze x;
WARNING: oid 16468 not a relation
ANALYZE

Thanks for review.

The index expression need to store statistics on index, I missed it and I'll fix it later.

Wenjing

Show quoted text

other behave looks well for me.

Regards

Pavel

Pavel

Wenjing

Opinion by Konstantin Knizhnik
1 Fixed comments
2 Fixed assertion

Please help me review.

Wenjing

#100Robert Haas
robertmhaas@gmail.com
In reply to: Konstantin Knizhnik (#90)
Re: [Proposal] Global temporary tables

On Mon, Jan 27, 2020 at 4:11 AM Konstantin Knizhnik
<k.knizhnik@postgrespro.ru> wrote:

I do not see any reasons to allow build local indexes for global table. Yes,it can happen that some session will have small amount of data in particular GTT and another - small amount of data in this table. But if access pattern is the same (and nature of GTT assumes it), then index in either appreciate, either useless in both cases.

I agree. I think allowing different backends to have different indexes
is overly complicated.

Regarding another point that was raised, I think it's not a good idea
to prohibit DDL on global temporary tables altogether. It should be
fine to change things when no sessions are using the GTT. Going
further and allowing changes when there are attached sessions would be
nice, but I think we shouldn't try. Getting this feature committed is
going to be a huge amount of work with even a minimal feature set;
complicating the problem by adding what are essentially new
DDL-concurrency features on top of the basic feature seems very much
unwise.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#101Robert Haas
robertmhaas@gmail.com
In reply to: 曾文旌(义从) (#95)
Re: [Proposal] Global temporary tables

On Tue, Jan 28, 2020 at 12:12 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com> wrote:

Opinion by Pavel
+ rel->rd_islocaltemp = true; <<<<<<< if this is valid, then the name of field "rd_islocaltemp" is not probably best
I renamed rd_islocaltemp

I don't see any change?

Rename rd_islocaltemp to rd_istemp in global_temporary_table_v8-pg13.patch

In view of commit 6919b7e3294702adc39effd16634b2715d04f012, I think
that this has approximately a 0% chance of being acceptable. If you're
setting a field in a way that is inconsistent with the current use of
the field, you're probably doing it wrong, because the field has an
existing purpose to which new code must conform. And if you're not
doing that, then you don't need to rename it.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#102Robert Haas
robertmhaas@gmail.com
In reply to: Konstantin Knizhnik (#98)
Re: [Proposal] Global temporary tables

On Wed, Jan 29, 2020 at 3:13 AM Konstantin Knizhnik
<k.knizhnik@postgrespro.ru> wrote:

But I heard only two arguments:

1. Concurrent building of indexes by all backends may consume much memory (n_backends * maintenance_work_mem) and consume a lot of disk/CPU resources.
2. GTT in one session can contains large amount of data and we need index for it, but small amount of data in another session and we do not need index for it.

You seem to be ignoring the fact that two committers told you this
probably wasn't safe.

Perhaps your view is that those people made no argument, and therefore
you don't have to respond to it. But the onus is not on somebody else
to tell you why a completely novel idea is not safe. The onus is on
you to analyze it in detail and prove that it is safe. What you need
to show is that there is no code anywhere in the system which will be
confused by an index springing into existence at whatever time you're
creating it.

One problem is that there are various backend-local data structures in
the relcache, the planner, and the executor that remember information
about indexes, and that may not respond well to having more indexes
show up unexpectedly. On the one hand, they might crash; on the other
hand, they might ignore the new index when they shouldn't. Another
problem is that the code which creates indexes might fail or misbehave
when run in an environment different from the one in which it
currently runs. I haven't really studied your code, so I don't know
exactly what it does, but for example it would be really bad to try to
build an index while holding a buffer lock, both because it might
cause (low-probability) undetected deadlocks and also because it might
block another process that wants that buffer lock in a
non-interruptible wait state for a long time.

Now, maybe you can make an argument that you only create indexes at
points in the query that are "safe." But I am skeptical, because of
this example:

rhaas=# create table foo (a int primary key, b text, c text, d text);
CREATE TABLE
rhaas=# create function blump() returns trigger as $$begin create
index on foo (b); return new; end$$ language plpgsql;
CREATE FUNCTION
rhaas=# create trigger thud before insert on foo execute function blump();
CREATE TRIGGER
rhaas=# insert into foo (a) select generate_series(1,10);
ERROR: cannot CREATE INDEX "foo" because it is being used by active
queries in this session
CONTEXT: SQL statement "create index on foo (b)"
PL/pgSQL function blump() line 1 at SQL statement

That prohibition is there for some reason. Someone did not just decide
to arbitrarily prohibit it. A CREATE INDEX command run in that context
won't run afoul of many of the things that might be problems in other
places -- e.g. there won't be a buffer lock held. Yet, despite the
fact that a trigger context is safe for executing a wide variety of
user-defined code, this particular operation is not allowed here. That
is the sort of thing that should worry you.

At any rate, even if this somehow were or could be made safe,
on-the-fly index creation is a feature that cannot and should not be
combined with a patch to implement global temporary tables. Surely, it
will require a lot of study and work to get the details right. And so
will GTT. As I said in the other email I wrote, this feature is hard
enough without adding this kind of thing to it. There's a reason why I
never got around to implementing this ten years ago when I did
unlogged tables; I was intending that to be a precursor to the GTT
work. I found that it was too hard and I gave up. I'm glad to see
people trying again, but the idea that we can afford to add in extra
features, or frankly that either of the dueling patches on this thread
are close to committable, is just plain wrong.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#103Konstantin Knizhnik
k.knizhnik@postgrespro.ru
In reply to: Robert Haas (#102)
Re: [Proposal] Global temporary tables

On 29.01.2020 17:47, Robert Haas wrote:

On Wed, Jan 29, 2020 at 3:13 AM Konstantin Knizhnik
<k.knizhnik@postgrespro.ru> wrote:

But I heard only two arguments:

1. Concurrent building of indexes by all backends may consume much memory (n_backends * maintenance_work_mem) and consume a lot of disk/CPU resources.
2. GTT in one session can contains large amount of data and we need index for it, but small amount of data in another session and we do not need index for it.

You seem to be ignoring the fact that two committers told you this
probably wasn't safe.

Perhaps your view is that those people made no argument, and therefore
you don't have to respond to it. But the onus is not on somebody else
to tell you why a completely novel idea is not safe. The onus is on
you to analyze it in detail and prove that it is safe. What you need
to show is that there is no code anywhere in the system which will be
confused by an index springing into existence at whatever time you're
creating it.

One problem is that there are various backend-local data structures in
the relcache, the planner, and the executor that remember information
about indexes, and that may not respond well to having more indexes
show up unexpectedly. On the one hand, they might crash; on the other
hand, they might ignore the new index when they shouldn't. Another
problem is that the code which creates indexes might fail or misbehave
when run in an environment different from the one in which it
currently runs. I haven't really studied your code, so I don't know
exactly what it does, but for example it would be really bad to try to
build an index while holding a buffer lock, both because it might
cause (low-probability) undetected deadlocks and also because it might
block another process that wants that buffer lock in a
non-interruptible wait state for a long time.

Now, maybe you can make an argument that you only create indexes at
points in the query that are "safe." But I am skeptical, because of
this example:

rhaas=# create table foo (a int primary key, b text, c text, d text);
CREATE TABLE
rhaas=# create function blump() returns trigger as $$begin create
index on foo (b); return new; end$$ language plpgsql;
CREATE FUNCTION
rhaas=# create trigger thud before insert on foo execute function blump();
CREATE TRIGGER
rhaas=# insert into foo (a) select generate_series(1,10);
ERROR: cannot CREATE INDEX "foo" because it is being used by active
queries in this session
CONTEXT: SQL statement "create index on foo (b)"
PL/pgSQL function blump() line 1 at SQL statement

That prohibition is there for some reason. Someone did not just decide
to arbitrarily prohibit it. A CREATE INDEX command run in that context
won't run afoul of many of the things that might be problems in other
places -- e.g. there won't be a buffer lock held. Yet, despite the
fact that a trigger context is safe for executing a wide variety of
user-defined code, this particular operation is not allowed here. That
is the sort of thing that should worry you.

At any rate, even if this somehow were or could be made safe,
on-the-fly index creation is a feature that cannot and should not be
combined with a patch to implement global temporary tables. Surely, it
will require a lot of study and work to get the details right. And so
will GTT. As I said in the other email I wrote, this feature is hard
enough without adding this kind of thing to it. There's a reason why I
never got around to implementing this ten years ago when I did
unlogged tables; I was intending that to be a precursor to the GTT
work. I found that it was too hard and I gave up. I'm glad to see
people trying again, but the idea that we can afford to add in extra
features, or frankly that either of the dueling patches on this thread
are close to committable, is just plain wrong.

Sorry, I really didn't consider statements containing word "probably" as
arguments.
But I agree with you: it is task of developer of new feature to prove
that proposed approach is safe rather than of reviewers to demonstrate
that it is unsafe.
Can I provide such proof now? I afraid that not.
But please consider two arguments:

1. Index for GTT in any case has to be initialized on demand. In case of
regular tables index is initialized at the moment of its creation. In
case of GTT it doesn't work.
So we should somehow detect that accessed index is not initialized and
perform lazy initialization of the index.
The only difference with the approach proposed by Pavel  (allow index
for empty GTT but prohibit it for GTT filled with data) is whether we
also need to populate index with data or not.
I can imagine that implicit initialization of index in read-only query
(select) can be unsafe and cause some problems. I have not encountered
such problems yet after performing many tests with GTTs, but certainly I
have not covered all possible scenarios (not sure that it is possible at
all).
But I do not understand how populating  index with data can add some
extra unsafety.

So I can not prove that building index for GTT on demand is safe, but it
is not more unsafe than initialization of index on demand which is
required in any case.

2. Actually I do not propose some completely new approach. I try to
provide behavior with is compatible with regular tables.
If you create index for regular table, then it can be used in all
sessions, right?
And all "various backend-local data structures in the relcache, the
planner, and the executor that remember information about indexes"
have to be properly updated.  It is done using invalidation mechanism.
The same mechanism is used in case of DDL operations with GTT, because
we change system catalog.

So my point here is that creation index of GTT is almost the same as
creation of index for regular tables and the same mechanism will be used
to provide correctness of this operation.

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

#104Pavel Stehule
pavel.stehule@gmail.com
In reply to: Konstantin Knizhnik (#103)
Re: [Proposal] Global temporary tables

2. Actually I do not propose some completely new approach. I try to

provide behavior with is compatible with regular tables.
If you create index for regular table, then it can be used in all
sessions, right?

I don't understand to this point. Regular tables shares data, shares files.
You cannot to separate it. More - you have to uses relatively aggressive
locks to be this operation safe.

Nothing from these points are valid for GTT.

Regards

Pavel

Show quoted text

And all "various backend-local data structures in the relcache, the
planner, and the executor that remember information about indexes"
have to be properly updated. It is done using invalidation mechanism.
The same mechanism is used in case of DDL operations with GTT, because
we change system catalog.

So my point here is that creation index of GTT is almost the same as
creation of index for regular tables and the same mechanism will be used
to provide correctness of this operation.

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

#105Konstantin Knizhnik
k.knizhnik@postgrespro.ru
In reply to: Pavel Stehule (#104)
Re: [Proposal] Global temporary tables

On 29.01.2020 20:08, Pavel Stehule wrote:

2. Actually I do not propose some completely new approach. I try to
provide behavior with is compatible with regular tables.
If you create index for regular table, then it can be used in all
sessions, right?

I don't understand to this point. Regular tables shares data, shares
files. You cannot to separate it. More - you have to uses relatively
aggressive locks to be this operation safe.

Nothing from these points are valid for GTT.

GTT shares metadata.
As far as them are not sharing data, then GTT are safer than regular
table, aren't them?
"Safer" means that we need less "aggressive" locks for them: we need to
protect only metadata, not data itself.

My point is that if we allow other sessions to access created indexes
for regular tables, then it will be not more complex to support it for GTT.
Actually "not more complex" in this case means "no extra efforts are
needed".

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

#106Pavel Stehule
pavel.stehule@gmail.com
In reply to: Konstantin Knizhnik (#105)
Re: [Proposal] Global temporary tables

st 29. 1. 2020 v 18:21 odesílatel Konstantin Knizhnik <
k.knizhnik@postgrespro.ru> napsal:

On 29.01.2020 20:08, Pavel Stehule wrote:

2. Actually I do not propose some completely new approach. I try to

provide behavior with is compatible with regular tables.
If you create index for regular table, then it can be used in all
sessions, right?

I don't understand to this point. Regular tables shares data, shares
files. You cannot to separate it. More - you have to uses relatively
aggressive locks to be this operation safe.

Nothing from these points are valid for GTT.

GTT shares metadata.
As far as them are not sharing data, then GTT are safer than regular
table, aren't them?
"Safer" means that we need less "aggressive" locks for them: we need to
protect only metadata, not data itself.

My point is that if we allow other sessions to access created indexes for
regular tables, then it will be not more complex to support it for GTT.
Actually "not more complex" in this case means "no extra efforts are
needed".

It is hard to say. I see a significant difference. When I do index on
regular table, then I don't change a context of other processes. I have to
wait for lock, and after I got a lock then other processes waiting.

With GTT, I don't want to wait for others - and other processes should
build indexes inside - without expected sequence of operations. Maybe it
can have positive effect, but it can have negative effect too. In this case
I prefer (in this moment) zero effect on other sessions. So I would to
build index in my session and I don't would to wait for other sessions, and
if it is possible other sessions doesn't need to interact or react on my
action too. It should be independent what is possible. The most simple
solution is request on unique usage. I understand so it can be not too
practical. Better is allow to usage GTT by other tables, but the changes
are invisible in other sessions to session reset. It is minimalistic
strategy. It has not benefits for other sessions, but it has not negative
impacts too.

Show quoted text

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

#107Robert Haas
robertmhaas@gmail.com
In reply to: Konstantin Knizhnik (#103)
Re: [Proposal] Global temporary tables

On Wed, Jan 29, 2020 at 10:30 AM Konstantin Knizhnik
<k.knizhnik@postgrespro.ru> wrote:

But please consider two arguments:

1. Index for GTT in any case has to be initialized on demand. In case of
regular tables index is initialized at the moment of its creation. In
case of GTT it doesn't work.
So we should somehow detect that accessed index is not initialized and
perform lazy initialization of the index.
The only difference with the approach proposed by Pavel (allow index
for empty GTT but prohibit it for GTT filled with data) is whether we
also need to populate index with data or not.
I can imagine that implicit initialization of index in read-only query
(select) can be unsafe and cause some problems. I have not encountered
such problems yet after performing many tests with GTTs, but certainly I
have not covered all possible scenarios (not sure that it is possible at
all).
But I do not understand how populating index with data can add some
extra unsafety.

So I can not prove that building index for GTT on demand is safe, but it
is not more unsafe than initialization of index on demand which is
required in any case.

I think that the idea of calling ambuild() on the fly is not going to
work, because, again, I don't think that calling that from random
places in the code is safe. What I expect we're going to need to do
here is model this on the approach used for unlogged tables. For an
unlogged table, each table and index has an init fork which contains
the correct initial contents for that relation - which is nothing at
all for a heap table, and a couple of boilerplate pages for an index.
In the case of an unlogged table, the init forks get copied over the
main forks after crash recovery, and then we have a brand-new, empty
relation with brand-new empty indexes which everyone can use. In the
case of global temporary tables, I think that we should do the same
kind of copying, but at the time when the session first tries to
access the table. There is some fuzziness in my mind about what
exactly constitutes accessing the table - it probably shouldn't be
when the relcache entry is built, because that seems too early, but
I'm not sure what is exactly right. In any event, it's easier to find
a context where copying some files on disk (that are certain not to be
changing) is safe than it is to find a context where index builds are
safe.

2. Actually I do not propose some completely new approach. I try to
provide behavior with is compatible with regular tables.
If you create index for regular table, then it can be used in all
sessions, right?

Yes. :-)

And all "various backend-local data structures in the relcache, the
planner, and the executor that remember information about indexes"
have to be properly updated. It is done using invalidation mechanism.
The same mechanism is used in case of DDL operations with GTT, because
we change system catalog.

I mean, that's not really a valid argument. Invalidations can only
take effect at certain points in the code, and the whole argument here
is about which places in the code are safe for which operations, so
the fact that some things (like accepting invalidations) are safe at
some points in the code (like the places where we accept them) does
not prove that other things (like calling ambuild) are safe at other
points in the code (like wherever you are proposing to call it). In
particular, if you've got a relation open, there's currently no way
for another index to show up while you've still got that relation
open. That means that the planner and executor (which keep the
relevant relations open) don't ever have to worry about updating their
data structures, because it can never be necessary. It also means that
any code anywhere in the system that keeps a lock on a relation can
count on the list of indexes for that relation staying the same until
it releases the lock. In fact, it can hold on to pointers to data
allocated by the relcache and count on those pointers being stable for
as long as it holds the lock, and RelationClearRelation contain
specific code that aims to make sure that certain objects don't get
deallocated and reallocated at a different address precisely for that
reason. That code, however, only works as long as nothing actually
changes. The upshot is that it's entirely possible for changing
catalog entries in one backend with an inadequate lock level -- or at
unexpected point in the code -- to cause a core dump either in that
backend or in some other backend. This stuff is really subtle, and
super-easy to screw up.

I am speaking a bit generally here, because I haven't really studied
*exactly* what might go wrong in the relcache, or elsewhere, as a
result of creating an index on the fly. However, I'm very sure that a
general appeal to invalidation messages is not sufficient to make
something like what you want to do safe. Invalidation messages are a
complex, ancient, under-documented, fragile system for solving a very
specific problem that is not the one you are hoping they'll solve
here. They could justifiably be called magic, but it's not the sort of
magic where the fairy godmother waves her wand and solves all of your
problems; it's more like the kind where you go explore the forbidden
forest and are never seen or heard from again.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#108Konstantin Knizhnik
k.knizhnik@postgrespro.ru
In reply to: Pavel Stehule (#106)
Re: [Proposal] Global temporary tables

On 29.01.2020 20:37, Pavel Stehule wrote:

st 29. 1. 2020 v 18:21 odesílatel Konstantin Knizhnik
<k.knizhnik@postgrespro.ru <mailto:k.knizhnik@postgrespro.ru>> napsal:

On 29.01.2020 20:08, Pavel Stehule wrote:

2. Actually I do not propose some completely new approach. I
try to
provide behavior with is compatible with regular tables.
If you create index for regular table, then it can be used in
all
sessions, right?

I don't understand to this point. Regular tables shares data,
shares files. You cannot to separate it. More - you have to uses
relatively aggressive locks to be this operation safe.

Nothing from these points are valid for GTT.

GTT shares metadata.
As far as them are not sharing data, then GTT are safer than
regular table, aren't them?
"Safer" means that we need less "aggressive" locks for them: we
need to protect only metadata, not data itself.

My point is that if we allow other sessions to access created
indexes for regular tables, then it will be not more complex to
support it for GTT.
Actually "not more complex" in this case means "no extra efforts
are needed".

It is hard to say. I see a significant difference. When I do index on
regular table, then I don't change a context of other processes. I
have to wait for lock, and after I got a lock then other processes
waiting.

With GTT, I don't want to wait for others - and other processes should
build indexes inside - without expected sequence of operations. Maybe
it can have positive effect, but it can have negative effect too. In
this case I prefer (in this moment) zero effect on other sessions. So
I would to build index in my session and I don't would to wait for
other sessions, and if it is possible other sessions doesn't need to
interact or react on my action too. It should be independent what is
possible. The most simple solution is request on unique usage. I
understand so it can be not too practical. Better is allow to usage
GTT by other tables, but the changes are invisible in other sessions
to session reset. It is minimalistic strategy. It has not benefits for
other sessions, but it has not negative impacts too.

Building regular index requires two kinds of lock:
1. You have to lock pg_class to make changes in system catalog.
2. You need to lock heap relation  to pervent concurrent updates while
building index.

GTT requires 1)  but not 2).
Once backend inserts information about new index in system catalog, all
other sessions may use it. pg_class lock prevents any race condition here.
And building index itself doesn't affect any other backends.

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

#109Pavel Stehule
pavel.stehule@gmail.com
In reply to: Konstantin Knizhnik (#108)
Re: [Proposal] Global temporary tables

čt 30. 1. 2020 v 9:45 odesílatel Konstantin Knizhnik <
k.knizhnik@postgrespro.ru> napsal:

On 29.01.2020 20:37, Pavel Stehule wrote:

st 29. 1. 2020 v 18:21 odesílatel Konstantin Knizhnik <
k.knizhnik@postgrespro.ru> napsal:

On 29.01.2020 20:08, Pavel Stehule wrote:

2. Actually I do not propose some completely new approach. I try to

provide behavior with is compatible with regular tables.
If you create index for regular table, then it can be used in all
sessions, right?

I don't understand to this point. Regular tables shares data, shares
files. You cannot to separate it. More - you have to uses relatively
aggressive locks to be this operation safe.

Nothing from these points are valid for GTT.

GTT shares metadata.
As far as them are not sharing data, then GTT are safer than regular
table, aren't them?
"Safer" means that we need less "aggressive" locks for them: we need to
protect only metadata, not data itself.

My point is that if we allow other sessions to access created indexes for
regular tables, then it will be not more complex to support it for GTT.
Actually "not more complex" in this case means "no extra efforts are
needed".

It is hard to say. I see a significant difference. When I do index on
regular table, then I don't change a context of other processes. I have to
wait for lock, and after I got a lock then other processes waiting.

With GTT, I don't want to wait for others - and other processes should
build indexes inside - without expected sequence of operations. Maybe it
can have positive effect, but it can have negative effect too. In this case
I prefer (in this moment) zero effect on other sessions. So I would to
build index in my session and I don't would to wait for other sessions, and
if it is possible other sessions doesn't need to interact or react on my
action too. It should be independent what is possible. The most simple
solution is request on unique usage. I understand so it can be not too
practical. Better is allow to usage GTT by other tables, but the changes
are invisible in other sessions to session reset. It is minimalistic
strategy. It has not benefits for other sessions, but it has not negative
impacts too.

Building regular index requires two kinds of lock:
1. You have to lock pg_class to make changes in system catalog.
2. You need to lock heap relation to pervent concurrent updates while
building index.

GTT requires 1) but not 2).
Once backend inserts information about new index in system catalog, all
other sessions may use it. pg_class lock prevents any race condition here.
And building index itself doesn't affect any other backends.

It is true. The difference for GTT, so any other sessions have to build
index (in your proposal) as extra operation against original plan.

Pavel

Show quoted text

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

#110Konstantin Knizhnik
k.knizhnik@postgrespro.ru
In reply to: Robert Haas (#107)
Re: [Proposal] Global temporary tables

On 29.01.2020 21:16, Robert Haas wrote:

On Wed, Jan 29, 2020 at 10:30 AM Konstantin Knizhnik
<k.knizhnik@postgrespro.ru> wrote:

I think that the idea of calling ambuild() on the fly is not going to
work, because, again, I don't think that calling that from random
places in the code is safe.

It is not a random place in the code.
Actually it is just one place - _bt_getbuf
Why it can be unsafe if it affects only private backends data?

What I expect we're going to need to do
here is model this on the approach used for unlogged tables. For an
unlogged table, each table and index has an init fork which contains
the correct initial contents for that relation - which is nothing at
all for a heap table, and a couple of boilerplate pages for an index.
In the case of an unlogged table, the init forks get copied over the
main forks after crash recovery, and then we have a brand-new, empty
relation with brand-new empty indexes which everyone can use. In the
case of global temporary tables, I think that we should do the same
kind of copying, but at the time when the session first tries to
access the table. There is some fuzziness in my mind about what
exactly constitutes accessing the table - it probably shouldn't be
when the relcache entry is built, because that seems too early, but
I'm not sure what is exactly right. In any event, it's easier to find
a context where copying some files on disk (that are certain not to be
changing) is safe than it is to find a context where index builds are
safe.

I do not think that approach used for unlogged tables is good for GTT.
Unlogged tables has to be reinitialized only after server restart.
GTT to should be initialized by each backend on demand.
It seems to me that init fork is used for unlogged table because
recovery process to not have enough context to be able to reintialize
table and indexes.
It is much safer and simpler for recovery process just to copy files.
But GTT case is different. Heap and indexes can be easily initialized by
backend  using existed functions.

Approach with just calling btbuild is much simpler than you propose with
creating extra forks and copying data from it.
You say that it not safe. But you have not explained why it is unsafe.
Yes, I agree that it is my responsibility to prove that it is safe.
And as I already wrote, I can not provide such proof now. I will be
pleased if you or anybody else can help to convince that this approach
is safe or demonstrate problems with this approach.

Copying data from fork doesn't help to provide the same behavior of GTT
indexes as regular indexes.
And from my point of view compatibility with regular tables is most
important point in GTT design.
If for some reasons it is not possible, than we should think about other
solutions.
But right now I do not know such problems. We have two working
prototypes of GTT. Certainly it is not mean lack of problems with the
current implementations.
But I really like to receive more constructive critics rather than "this
approach is wrong because it is unsafe".

planner, and the executor that remember information about indexes"
have to be properly updated. It is done using invalidation mechanism.
The same mechanism is used in case of DDL operations with GTT, because
we change system catalog.

I mean, that's not really a valid argument. Invalidations can only
take effect at certain points in the code, and the whole argument here
is about which places in the code are safe for which operations, so
the fact that some things (like accepting invalidations) are safe at
some points in the code (like the places where we accept them) does
not prove that other things (like calling ambuild) are safe at other
points in the code (like wherever you are proposing to call it). In
particular, if you've got a relation open, there's currently no way
for another index to show up while you've still got that relation
open.

The same is true for GTT. Right now building GTT index also locks the
relation.
It may be not absolutely needed, because data of relation is local and
can not be changed by some other backend.
But I have not added some special handling of GTT here.
Mostly because I want to follow the same way as with regular indexes and
prevent possible problems which as you mention can happen
if we somehow changing locking policy.

That means that the planner and executor (which keep the
relevant relations open) don't ever have to worry about updating their
data structures, because it can never be necessary. It also means that
any code anywhere in the system that keeps a lock on a relation can
count on the list of indexes for that relation staying the same until
it releases the lock. In fact, it can hold on to pointers to data
allocated by the relcache and count on those pointers being stable for
as long as it holds the lock, and RelationClearRelation contain
specific code that aims to make sure that certain objects don't get
deallocated and reallocated at a different address precisely for that
reason. That code, however, only works as long as nothing actually
changes. The upshot is that it's entirely possible for changing
catalog entries in one backend with an inadequate lock level -- or at
unexpected point in the code -- to cause a core dump either in that
backend or in some other backend. This stuff is really subtle, and
super-easy to screw up.

I am speaking a bit generally here, because I haven't really studied
*exactly* what might go wrong in the relcache, or elsewhere, as a
result of creating an index on the fly. However, I'm very sure that a
general appeal to invalidation messages is not sufficient to make
something like what you want to do safe. Invalidation messages are a
complex, ancient, under-documented, fragile system for solving a very
specific problem that is not the one you are hoping they'll solve
here. They could justifiably be called magic, but it's not the sort of
magic where the fairy godmother waves her wand and solves all of your
problems; it's more like the kind where you go explore the forbidden
forest and are never seen or heard from again.

Actually index is not created on the fly.
Index is created is usual way, by executing "create index" command.
So all  components of the Postgres (planner, executor,...) treat GTT
indexes in the same way as regular indexes.
Locking and invalidations policies are exactly the same for them.
The only difference is that content of GTT index is constructed  on
demand using private backend data.
Is it safe or not? We are just reading data from local buffers/files and
writing them here.
May be I missed something but I do not see any unsafety here.
There are issues with updating statistic but them can be solved.

--

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

#111Konstantin Knizhnik
k.knizhnik@postgrespro.ru
In reply to: Pavel Stehule (#109)
Re: [Proposal] Global temporary tables

On 30.01.2020 12:23, Pavel Stehule wrote:

Building regular index requires two kinds of lock:
1. You have to lock pg_class to make changes in system catalog.
2. You need to lock heap relation  to pervent concurrent updates
while building index.

GTT requires 1)  but not 2).
Once backend inserts information about new index in system
catalog, all other sessions may use it. pg_class lock prevents any
race condition here.
And building index itself doesn't affect any other backends.

It is true. The difference for GTT, so any other sessions have to
build index (in your proposal) as extra operation against original plan.

What is "index"?
For most parts of Postgres it is just an entry in system catalog.
And only executor deals with its particular implementation and content.

My point is that if we process GTT index metadata in the same way as
regular index metadata,
then there will be no differences for the postgres between GTT and
regular indexes.
And we can provide the same behavior.

Concerning actual content of the index - it is local to the backend and
it is safe to construct it a t any point of time (on demand).
It depends only on private data and can not be somehow affected by other
backends (taken in account that we preserve locking policy of regular
tables).

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

#112Pavel Stehule
pavel.stehule@gmail.com
In reply to: Konstantin Knizhnik (#111)
Re: [Proposal] Global temporary tables

čt 30. 1. 2020 v 10:44 odesílatel Konstantin Knizhnik <
k.knizhnik@postgrespro.ru> napsal:

On 30.01.2020 12:23, Pavel Stehule wrote:

Building regular index requires two kinds of lock:

1. You have to lock pg_class to make changes in system catalog.
2. You need to lock heap relation to pervent concurrent updates while
building index.

GTT requires 1) but not 2).
Once backend inserts information about new index in system catalog, all
other sessions may use it. pg_class lock prevents any race condition here.
And building index itself doesn't affect any other backends.

It is true. The difference for GTT, so any other sessions have to build
index (in your proposal) as extra operation against original plan.

What is "index"?
For most parts of Postgres it is just an entry in system catalog.
And only executor deals with its particular implementation and content.

My point is that if we process GTT index metadata in the same way as
regular index metadata,
then there will be no differences for the postgres between GTT and regular
indexes.
And we can provide the same behavior.

There should be a difference - index on regular table is created by one
process. Same thing is not possible on GTT. So there should be a difference
every time.

You can reduce some differences, but minimally me and Robert don't feel it
well. Starting a building index from routine, that is used for reading from
buffer doesn't look well. I can accept some stranges, but I need to have
feeling so it is necessary. I don't think so it is necessary in this case.

Regards

Pavel

Show quoted text

Concerning actual content of the index - it is local to the backend and it
is safe to construct it a t any point of time (on demand).
It depends only on private data and can not be somehow affected by other
backends (taken in account that we preserve locking policy of regular
tables).

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

#113Konstantin Knizhnik
k.knizhnik@postgrespro.ru
In reply to: Pavel Stehule (#112)
Re: [Proposal] Global temporary tables

On 30.01.2020 12:52, Pavel Stehule wrote:

čt 30. 1. 2020 v 10:44 odesílatel Konstantin Knizhnik
<k.knizhnik@postgrespro.ru <mailto:k.knizhnik@postgrespro.ru>> napsal:

On 30.01.2020 12:23, Pavel Stehule wrote:

Building regular index requires two kinds of lock:
1. You have to lock pg_class to make changes in system catalog.
2. You need to lock heap relation  to pervent concurrent
updates while building index.

GTT requires 1)  but not 2).
Once backend inserts information about new index in system
catalog, all other sessions may use it. pg_class lock
prevents any race condition here.
And building index itself doesn't affect any other backends.

It is true. The difference for GTT, so any other sessions have to
build index (in your proposal) as extra operation against
original plan.

What is "index"?
For most parts of Postgres it is just an entry in system catalog.
And only executor deals with its particular implementation and
content.

My point is that if we process GTT index metadata in the same way
as regular index metadata,
then there will be no differences for the postgres between GTT and
regular indexes.
And we can provide the same behavior.

There should be a difference - index on regular table is created by
one process. Same thing is not possible on GTT. So there should be a
difference every time.

Metadata of GTT index is also created by one process. And actual content
of the index is not interesting for most parts of Postgres.

You can reduce some differences, but minimally me and Robert don't
feel it well. Starting a building index from routine, that is used for
reading from buffer doesn't look well. I can accept some stranges, but
I need to have feeling so it is necessary. I don't think so it is
necessary in this case.

Sorry, but "don't feel it well", "doesn't look well" looks more
likeliterary criticism rather than code review;)
Yes, I agree that it is unnatural to call btindex from _bt_getbuf. But
what can go wrong here?

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

#114Pavel Stehule
pavel.stehule@gmail.com
In reply to: Konstantin Knizhnik (#113)
Re: [Proposal] Global temporary tables

čt 30. 1. 2020 v 11:02 odesílatel Konstantin Knizhnik <
k.knizhnik@postgrespro.ru> napsal:

On 30.01.2020 12:52, Pavel Stehule wrote:

čt 30. 1. 2020 v 10:44 odesílatel Konstantin Knizhnik <
k.knizhnik@postgrespro.ru> napsal:

On 30.01.2020 12:23, Pavel Stehule wrote:

Building regular index requires two kinds of lock:

1. You have to lock pg_class to make changes in system catalog.
2. You need to lock heap relation to pervent concurrent updates while
building index.

GTT requires 1) but not 2).
Once backend inserts information about new index in system catalog, all
other sessions may use it. pg_class lock prevents any race condition here.
And building index itself doesn't affect any other backends.

It is true. The difference for GTT, so any other sessions have to build
index (in your proposal) as extra operation against original plan.

What is "index"?
For most parts of Postgres it is just an entry in system catalog.
And only executor deals with its particular implementation and content.

My point is that if we process GTT index metadata in the same way as
regular index metadata,
then there will be no differences for the postgres between GTT and
regular indexes.
And we can provide the same behavior.

There should be a difference - index on regular table is created by one
process. Same thing is not possible on GTT. So there should be a difference
every time.

Metadata of GTT index is also created by one process. And actual content
of the index is not interesting for most parts of Postgres.

You can reduce some differences, but minimally me and Robert don't feel it
well. Starting a building index from routine, that is used for reading from
buffer doesn't look well. I can accept some stranges, but I need to have
feeling so it is necessary. I don't think so it is necessary in this case.

Sorry, but "don't feel it well", "doesn't look well" looks more like
literary criticism rather than code review;)

The design is subjective. I am sure, so your solution can work, like mine,
or any other. But I am not sure, so your solution is good for practical
usage.

Yes, I agree that it is unnatural to call btindex from _bt_getbuf. But
what can go wrong here?

creating index as side effect of table reading. Just the side effect too
much big.

Show quoted text

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

#115曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Pavel Stehule (#97)
1 attachment(s)
Re: [Proposal] Global temporary tables

2020年1月29日 上午1:54,Pavel Stehule <pavel.stehule@gmail.com> 写道:

út 28. 1. 2020 v 18:13 odesílatel Pavel Stehule <pavel.stehule@gmail.com <mailto:pavel.stehule@gmail.com>> napsal:

út 28. 1. 2020 v 18:12 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:

2020年1月29日 上午12:40,Pavel Stehule <pavel.stehule@gmail.com <mailto:pavel.stehule@gmail.com>> 写道:

út 28. 1. 2020 v 17:01 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:

2020年1月24日 上午4:47,Robert Haas <robertmhaas@gmail.com <mailto:robertmhaas@gmail.com>> 写道:

On Sat, Jan 11, 2020 at 8:51 PM Tomas Vondra
<tomas.vondra@2ndquadrant.com <mailto:tomas.vondra@2ndquadrant.com>> wrote:

I proposed just ignoring those new indexes because it seems much simpler
than alternative solutions that I can think of, and it's not like those
other solutions don't have other issues.

+1.

I complete the implementation of this feature.
When a session x create an index idx_a on GTT A then
For session x, idx_a is valid when after create index.
For session y, before session x create index done, GTT A has some data, then index_a is invalid.
For session z, before session x create index done, GTT A has no data, then index_a is valid.

For example, I've looked at the "on demand" building as implemented in
global_private_temp-8.patch, I kinda doubt adding a bunch of index build
calls into various places in index code seems somewht suspicious.

+1. I can't imagine that's a safe or sane thing to do.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;
The Enterprise PostgreSQL Company

Opinion by Pavel
+ rel->rd_islocaltemp = true; <<<<<<< if this is valid, then the name of field "rd_islocaltemp" is not probably best
I renamed rd_islocaltemp

I don't see any change?

Rename rd_islocaltemp to rd_istemp in global_temporary_table_v8-pg13.patch

ok :)

I found a bug

postgres=# create global temp table x(a int);
CREATE TABLE
postgres=# insert into x values(1);
INSERT 0 1
postgres=# create index on x (a);
CREATE INDEX
postgres=# create index on x((a + 1));
CREATE INDEX
postgres=# analyze x;
WARNING: oid 16468 not a relation
ANALYZE

The bug has been fixed in the global_temporary_table_v9-pg13.patch

Wenjing

Show quoted text

other behave looks well for me.

Regards

Pavel

Pavel

Wenjing

Opinion by Konstantin Knizhnik
1 Fixed comments
2 Fixed assertion

Please help me review.

Wenjing

Attachments:

global_temporary_table_v9-pg13.patchapplication/octet-stream; name=global_temporary_table_v9-pg13.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 79430d2..babb5f3 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -158,6 +158,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * For global temp table only
+	 * use ShareUpdateExclusiveLock for ensure safety
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temp table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},	
 	/* list terminator */
 	{{NULL}}
 };
@@ -1486,6 +1499,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1586,13 +1601,18 @@ build_reloptions(Datum reloptions, bool validate,
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	/*
 	 * There are no options for partitioned tables yet, but this is able to do
 	 * some validation.
 	 */
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index dd975b1..1610e7d 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1013,7 +1013,9 @@ gistGetFakeLSN(Relation rel)
 {
 	static XLogRecPtr counter = FirstNormalUnloggedLSN;
 
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	/* global temp is same as local temp table */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 4871b7f..16b00c9 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -149,7 +149,9 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	/* global temp table is same as local temp table */
+	if (!(index->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		index->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 3fa4b76..b54882d 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -598,7 +598,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -651,7 +651,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 8ce5011..0d6ae76 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -62,6 +62,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -399,9 +400,9 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
 	Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
-	/* not every AM requires these to be valid, but heap does */
-	Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
-	Assert(MultiXactIdIsValid(onerel->rd_rel->relminmxid));
+	/* not every AM requires these to be valid, but regular heap does */
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ MultiXactIdIsValid(onerel->rd_rel->relminmxid));
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index f05cbe7..946c9d2 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -763,7 +763,14 @@ _bt_getbuf(Relation rel, BlockNumber blkno, int access)
 		/* Read an existing block of the relation */
 		buf = ReadBuffer(rel, blkno);
 		LockBuffer(buf, access);
-		_bt_checkpage(rel, buf);
+
+		/* global temp table may be not yet initialized for this backend. */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			blkno == BTREE_METAPAGE &&
+			PageIsNew(BufferGetPage(buf)))
+			_bt_initmetapage(BufferGetPage(buf), P_NONE, 0);
+		else
+			_bt_checkpage(rel, buf);
 	}
 	else
 	{
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 3813ead..fd731d8 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6325,6 +6325,10 @@ StartupXLOG(void)
 	else
 		recoveryTargetTLI = ControlFile->checkPointCopy.ThisTimeLineID;
 
+	/* clean temp relation files */
+	if (max_active_gtt > 0)
+		RemovePgTempFiles();
+
 	/*
 	 * Check for signal files, and if so set up state for offline recovery
 	 */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index f8f0b48..dc8bbb1 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -42,6 +42,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 7d6acae..3be8d63 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -393,7 +393,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/* global temp table is same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 0fdff29..210d019 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -404,6 +406,10 @@ heap_create(const char *relname,
 									 relpersistence,
 									 relkind);
 
+	/* global temp table not create storage file when catalog create */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		create_storage = false;
+
 	/*
 	 * Have the storage manager create the relation's disk file, if needed.
 	 *
@@ -427,7 +433,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -956,6 +962,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -994,8 +1001,18 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/* global temp table not remember transaction info in catalog */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1357,6 +1374,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1441,11 +1459,15 @@ heap_create_with_catalog(const char *relname,
 	 */
 	StoreConstraints(new_rel_desc, cooked_constraints, is_internal);
 
-	/*
-	 * If there's a special on-commit action, remember it
-	 */
-	if (oncommit != ONCOMMIT_NOOP)
-		register_on_commit_action(relid, oncommit);
+	/* global temp table register action when storage init */
+	if (relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		/*
+		 * If there's a special on-commit action, remember it
+		 */
+		if (oncommit != ONCOMMIT_NOOP)
+			register_on_commit_action(relid, oncommit);
+	}
 
 	/*
 	 * ok, the relation has been cataloged, so close our relations and return
@@ -1937,6 +1959,13 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/* We allow to drop global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(rel->rd_node))
+			elog(ERROR, "can not drop relation when other backend attached this global temp table");
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3165,7 +3194,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3176,8 +3205,12 @@ RelationTruncateIndexes(Relation heapRelation)
 		Relation	currentIndex;
 		IndexInfo  *indexInfo;
 
+		if (RELATION_IS_GLOBAL_TEMP(heapRelation) &&
+			!gtt_storage_attached(indexId))
+			continue;
+
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3223,8 +3256,13 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		/* truncate global temp table only need RowExclusiveLock */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3257,6 +3295,8 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
+	bool		truncate_toastrel = false;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3265,23 +3305,40 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	toastrelid = rel->rd_rel->reltoastrelid;
+
+	/*
+	 * Truncate global temp table only need RowExclusiveLock
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		lockmode = RowExclusiveLock;
+		if (OidIsValid(toastrelid) &&
+			gtt_storage_attached(toastrelid))
+			truncate_toastrel = true;
+	}
+	else if (OidIsValid(toastrelid))
+		truncate_toastrel = true;
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
-	toastrelid = rel->rd_rel->reltoastrelid;
-	if (OidIsValid(toastrelid))
+	if (truncate_toastrel)
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(rel, 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 8880586..0852073 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -52,6 +52,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -877,6 +878,22 @@ index_create(Relation heapRelation,
 						indexRelationName, RelationGetRelationName(heapRelation))));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		if (accessMethodObjectId != BTREE_AM_OID)
+			elog(ERROR, "only support btree index on global temp table");
+
+		/* No support create index on global temp table use concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot create indexes on global temporary tables using concurrent mode")));
+
+		/* if global temp table not init storage, then skip build index */
+		if (!gtt_storage_attached(heapRelation->rd_node.relNode))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -2054,6 +2071,13 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/* We allow to drop index on global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation))
+	{
+		if (is_other_backend_use_gtt(userHeapRelation->rd_node))
+			elog(ERROR, "can not drop index when other backend attached this global temp table.");
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2660,6 +2684,11 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	/* update index stats into localhash and rel_rd_rel for global temp table */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		is_gtt = true;
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2745,21 +2774,35 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
+		if (is_gtt)
+			rel->rd_rel->relpages = (int32) relpages;
+		else if (rd_rel->relpages != (int32) relpages)
 		{
 			rd_rel->relpages = (int32) relpages;
 			dirty = true;
 		}
-		if (rd_rel->reltuples != (float4) reltuples)
+
+		if (is_gtt)
+			rel->rd_rel->reltuples = (float4) reltuples;
+		else if (rd_rel->reltuples != (float4) reltuples)
 		{
 			rd_rel->reltuples = (float4) reltuples;
 			dirty = true;
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+
+		if (is_gtt)
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+		else if (rd_rel->relallvisible != (int32) relallvisible)
 		{
 			rd_rel->relallvisible = (int32) relallvisible;
 			dirty = true;
 		}
+
+		if (is_gtt)
+		{
+			up_gtt_relstats(rel, relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
+		}
 	}
 
 	/*
@@ -2873,6 +2916,15 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, index, val);
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		if (!gtt_storage_attached(indexRelation->rd_node.relNode))
+		{
+			gtt_force_enable_index(indexRelation);
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3468,6 +3520,15 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 				 errmsg("cannot reindex temporary tables of other sessions")));
 
 	/*
+	 * Because global temp table cannot change relfilenode
+	 * no support reindex on global temp table yet.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(iRel))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot reindex global temporary tables")));
+
+	/*
 	 * Also check for active uses of the index in the current transaction; we
 	 * don't want to reindex underneath an open indexscan.
 	 */
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index e70243a..301da79 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -647,6 +647,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		/* global temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp relations in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index fddfbf1..671c614 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -26,6 +26,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -75,7 +76,7 @@ static PendingRelDelete *pendingDeletes = NULL; /* head of linked list */
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -85,6 +86,8 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		/* global temp table use same storage strategy as local temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -117,6 +120,10 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/* remember global temp table storage info to localhash */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel)
+		remember_gtt_storage_info(rnode, rel);
+
 	return srel;
 }
 
@@ -486,8 +493,15 @@ smgrDoPendingDeletes(bool isCommit)
 		smgrdounlinkall(srels, nrels, false);
 
 		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* clean global temp table flags when transaction commit or rollback */
+			if (SmgrIsTemp(srels[i]) && 
+				gtt_storage_attached(srels[i]->smgr_rnode.node.relNode))
+				forget_gtt_storage_info(srels[i]->smgr_rnode.node.relNode);
+		}
+
 		pfree(srels);
 	}
 }
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..71be416
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1174 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  code to create and destroy physical storage for global temparary table
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/table.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_relstats_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+typedef struct gtt_ctl_data
+{
+	LWLock			lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct
+{
+	RelFileNode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+	Oid			relid;
+
+	Oid			spcnode;
+	/* pg_class stat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+	char		relkind;
+	bool		on_commit_delete;
+
+	/* pg_statistic */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(RelFileNode rnode);
+static void gtt_storage_checkout(RelFileNode rnode, bool skiplock);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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;
+	int			natts = 0;
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_gtt to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temp table yet");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create stroage file", RelationGetRelationName(rel));
+
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		/* First time through: initialize the hash table */
+		HASHCTL		ctl;
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		gtt_storage_local_hash =
+			hash_create("global temp relation table",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_relstats_context =
+			AllocSetContextCreate(CacheMemoryContext,
+							"gtt relstats context",
+							ALLOCSET_DEFAULT_SIZES);
+	}
+
+	/* Look up or create an entry */
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_ENTER, &found);
+
+	if (found)
+	{
+		elog(ERROR, "backend %d relid %u already exists in global temp table local hash",
+					MyBackendId, relid);
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+	entry->spcnode = rnode.spcNode;
+	entry->relpages = 0;
+	entry->reltuples = 0;
+	entry->relallvisible = 0;
+	entry->relkind = rel->rd_rel->relkind;
+	natts = RelationGetNumberOfAttributes(rel);
+	entry->natts = natts;
+	entry->attnum = palloc0(sizeof(int) * natts);
+	entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+	/* only heap contain transaction information */
+	if (entry->relkind == RELKIND_RELATION ||
+		entry->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+		{
+			entry->on_commit_delete = true;
+			register_on_commit_action(rel->rd_node.relNode, ONCOMMIT_DELETE_ROWS);
+		}
+
+		entry->relfrozenxid = RecentXmin;
+		entry->relminmxid = GetOldestMultiXactId();
+		insert_gtt_relfrozenxid_to_ordered_list(entry->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+		gtt_storage_checkin(rnode);
+	}
+	else
+	{
+		entry->on_commit_delete = false;
+		entry->relfrozenxid = 0;
+		entry->relminmxid = 0;
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+void
+forget_gtt_storage_info(Oid relid)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry)
+	{
+		int		i;
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			RelFileNode rnode;
+
+			rnode.spcNode = entry->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = entry->relid;
+
+			gtt_storage_checkout(rnode, false);
+
+			Assert(TransactionIdIsNormal(entry->relfrozenxid));
+			remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+
+		for (i = 0; i < entry->natts; i++)
+		{
+			if (entry->att_stat_tups[i])
+			{
+				heap_freetuple(entry->att_stat_tups[i]);
+				entry->att_stat_tups[i] = NULL;
+			}
+		}
+
+		pfree(entry->attnum);
+		pfree(entry->att_stat_tups);
+	}
+
+	hash_search(gtt_storage_local_hash,
+			(void *) &(relid), HASH_REMOVE, NULL);
+
+	return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool found;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (gtt_storage_local_hash == NULL)
+		return false;
+
+	hash_search(gtt_storage_local_hash,
+			(void *) &(relid), HASH_FIND, &found);
+
+	return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS				status;
+	gtt_local_hash_entry		*entry;
+	int			nrels = 0,
+				maxrels = 0;
+	SMgrRelation	*srels = NULL;
+	RelFileNode		*rnodes = NULL;
+	char			*relkinds = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		SMgrRelation srel;
+		RelFileNode rnode;
+
+		rnode.spcNode = entry->spcnode;
+		rnode.dbNode = MyDatabaseId;
+		rnode.relNode = entry->relid;
+
+		srel = smgropen(rnode, MyBackendId);
+
+		/* allocate the initial array, or extend it, if needed */
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			srels = palloc(sizeof(SMgrRelation) * maxrels);
+			rnodes = palloc(sizeof(RelFileNode) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+			rnodes = repalloc(rnodes, sizeof(RelFileNode) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		srels[nrels] = srel;
+		rnodes[nrels] = rnode;
+		relkinds[nrels] = entry->relkind;
+		nrels++;
+	}
+
+	if (nrels > 0)
+	{
+		int i;
+
+		smgrdounlinkall(srels, nrels, false);
+
+		LWLockAcquire(&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.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (gtt_storage_local_hash == NULL)
+		return false;
+
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_FIND, &found);
+
+	if (!found)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	if (relpages)
+		*relpages = entry->relpages;
+
+	if (reltuples)
+		*reltuples = entry->reltuples;
+
+	if (relallvisible)
+		*relallvisible = entry->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = entry->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = entry->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	MemoryContext			oldcontext;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+						(void *) &reloid, HASH_FIND, &found);
+
+	if (!found)
+		return;
+
+	/* todo */
+	if (entry->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	Assert(entry->relid == reloid);
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == 0)
+		{
+			entry->attnum[i] = attnum;
+			break;
+		}
+		else if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < entry->natts);
+	Assert(entry->att_stat_tups[i] == NULL);
+	entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+						(void *) &reloid, HASH_FIND, &found);
+
+	if (!found)
+		return NULL;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			return entry->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int			i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	HeapTuple	tuple;
+	Relation	rel = NULL;
+	int		attnum = PG_GETARG_INT32(1);
+	Oid		reloid = PG_GETARG_OID(0);
+	char		rel_persistence;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	  tupdesc;
+	MemoryContext oldcontext;
+	Tuplestorestate *tupstore;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = table_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+
+	table_close(rel, NoLock);
+	table_close(pg_tatistic, AccessShareLock);
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate	*tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Relation	rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	if (get_gtt_relstats(reloid,
+						&relpages, &reltuples, &relallvisible,
+						&relfrozenxid, &relminmxid))
+	{
+		Datum	values[5];
+		bool	isnull[5];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = Int32GetDatum(relpages);
+		values[1] = Float4GetDatum((float4)reltuples);
+		values[2] = Int32GetDatum(relallvisible);
+		values[3] = UInt32GetDatum(relfrozenxid);
+		values[4] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+
+	tuplestore_donestoring(tupstore);
+	table_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(rel->rd_node);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	table_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(indexOid));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid relOid = index->rd_index->indrelid;
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	if (gtt_storage_attached(relOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index c9e6060..d814ff2 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM 
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c4420dd..977a984 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -586,14 +587,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 		 * pg_statistic for columns we didn't process, we leave them alone.)
 		 */
 		update_attstats(RelationGetRelid(onerel), inh,
-						attr_cnt, vacattrstats);
+						attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
 
 		for (ind = 0; ind < nindexes; ind++)
 		{
 			AnlIndexData *thisdata = &indexdata[ind];
 
 			update_attstats(RelationGetRelid(Irel[ind]), false,
-							thisdata->attr_cnt, thisdata->vacattrstats);
+							thisdata->attr_cnt, thisdata->vacattrstats,
+							RelationGetRelPersistence(Irel[ind]));
 		}
 
 		/*
@@ -1456,7 +1458,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
  *		by taking a self-exclusive lock on the relation in analyze_rel().
  */
 static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
 {
 	Relation	sd;
 	int			attno;
@@ -1558,31 +1560,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/* Update column statistic to localhash, not catalog */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index e9d7a7f..3088e26 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -391,6 +391,12 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/* not support cluster global temp table yet */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("not support cluster global temporary tables yet")));
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index ec20ba3..a29cc00 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2614,6 +2614,16 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 			!isTempNamespace(classtuple->relnamespace))
 			continue;
 
+		/* not support reindex on global temp table, so skip it */
+		if (classtuple->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			ereport(WARNING,
+				(errmsg("global temp table \"%s.%s\" skip reindexed",
+					get_namespace_name(get_rel_namespace(relid)),
+					get_rel_name(relid))));
+			continue;
+		}
+
 		/* Check user/system classification, and optionally skip */
 		if (objectKind == REINDEX_OBJECT_SYSTEM &&
 			!IsSystemClass(relid, classtuple))
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 329ab84..dfa257c 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -96,7 +96,8 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 	 * transaction.
 	 */
 	relpersistence = get_rel_persistence(relid);
-	if (relpersistence == RELPERSISTENCE_TEMP)
+	if (relpersistence == RELPERSISTENCE_TEMP ||
+		relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
 
 	/* Check permissions. */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..44f350d 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -94,7 +94,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +108,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -222,7 +223,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* now initialize the sequence's data */
 	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -327,7 +328,7 @@ ResetSequence(Oid seq_relid)
 	/*
 	 * Insert the modified tuple into the new storage file.
 	 */
-	fill_seq_with_data(seq_rel, tuple);
+	fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -340,17 +341,21 @@ ResetSequence(Oid seq_relid)
  * Initialize a sequence's relation with the specified tuple as content
  */
 static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 	sequence_magic *sm;
 	OffsetNumber offnum;
+	bool lockBuffer = false;
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ReadBuffer(rel, P_NEW);
-	Assert(BufferGetBlockNumber(buf) == 0);
+	if (buf == InvalidBuffer)
+	{
+		buf = ReadBuffer(rel, P_NEW);
+		Assert(BufferGetBlockNumber(buf) == 0);
+		lockBuffer = true;
+	}
 
 	page = BufferGetPage(buf);
 
@@ -360,7 +365,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* Now insert sequence tuple */
 
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+	if (lockBuffer)
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +416,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	END_CRIT_SECTION();
 
-	UnlockReleaseBuffer(buf);
+	if (lockBuffer)
+		UnlockReleaseBuffer(buf);
 }
 
 /*
@@ -502,7 +509,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * Insert the modified tuple into the new storage file.
 		 */
-		fill_seq_with_data(seqrel, newdatatuple);
+		fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
 	}
 
 	/* process OWNED BY if given */
@@ -1178,6 +1185,25 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
 	LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
 
 	page = BufferGetPage(*buf);
+	if (GlobalTempRelationPageIsNotInitialized(rel, page))
+	{
+		/* Initialize sequence for global temporary tables */
+		Datum		value[SEQ_COL_LASTCOL] = {0};
+		bool		null[SEQ_COL_LASTCOL] = {false};
+		HeapTuple	tuple;
+		int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+		/*
+		 * last_value from pg_sequence.seqstart
+		 * log_cnt = 0
+		 * is_called = false
+		 */
+		value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+		tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+		fill_seq_with_data(rel, tuple, *buf);
+		heap_freetuple(tuple);
+	}
 	sm = (sequence_magic *) PageGetSpecialPointer(page);
 
 	if (sm->magic != SEQ_MAGIC)
@@ -1954,3 +1980,23 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 70589dd..81bd3e5 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -552,6 +553,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 								  Relation partitionTbl);
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static bool has_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -597,6 +599,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	bool		has_oncommit_clause = false;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -607,8 +610,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Check consistency of arguments
 	 */
+	/* global temp table same as local temp table */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !(stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+			 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -638,7 +643,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	/* global temp table same as local temp table */
+	if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+		 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -739,6 +746,57 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* global temp table */
+	has_oncommit_clause = has_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* inherit table or parition table inherit on commit property from parent table*/
+		if (inheritOids && stmt->oncommit == ONCOMMIT_NOOP && !has_oncommit_clause)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			if (RELATION_GTT_ON_COMMIT_DELETE(relation))
+				stmt->oncommit = ONCOMMIT_DELETE_ROWS;
+			else
+				stmt->oncommit = ONCOMMIT_PRESERVE_ROWS;
+			table_close(relation, NoLock);
+		}
+
+		if (has_oncommit_clause)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "can not defeine global temp table with on commit and with clause at same time");
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temp table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (has_oncommit_clause)
+		elog(ERROR, "regular table cannot specifie on_commit_delete_rows");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1816,7 +1874,8 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
 		 * table or the current physical file to be thrown away anyway.
 		 */
 		if (rel->rd_createSubid == mySubid ||
-			rel->rd_newRelfilenodeSubid == mySubid)
+			rel->rd_newRelfilenodeSubid == mySubid ||
+			RELATION_IS_GLOBAL_TEMP(rel))
 		{
 			/* Immediate, non-rollbackable truncation is OK */
 			heap_truncate_one_rel(rel);
@@ -3375,6 +3434,13 @@ RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal, bo
 	 * specially.
 	 */
 	targetrelation = relation_open(myrelid, is_index ? ShareUpdateExclusiveLock : AccessExclusiveLock);
+
+	if (RELATION_IS_GLOBAL_TEMP(targetrelation))
+	{
+		if (is_other_backend_use_gtt(targetrelation->rd_node))
+			elog(ERROR, "can not rename relation when other backend attached this global temp table");
+	}
+
 	namespaceId = RelationGetNamespace(targetrelation);
 
 	/*
@@ -3550,6 +3616,13 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(rel->rd_node))
+			elog(ERROR, "can not alter relation when other backend attached this global temp table");
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -8123,6 +8196,13 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 				 errmsg("referenced relation \"%s\" is not a table",
 						RelationGetRelationName(pkrel))));
 
+	/* global temp table not support foreign key constraint yet */
+	if (RELATION_IS_GLOBAL_TEMP(pkrel))
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("referenced relation \"%s\" is not a global temp table",
+						RelationGetRelationName(pkrel))));
+
 	if (!allowSystemTableMods && IsSystemRelation(pkrel))
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -8162,6 +8242,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		/* global temp table not support foreign key constraint yet */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("not support foreign key constraints on global temp table yet")));
+			break;
 	}
 
 	/*
@@ -13199,7 +13285,7 @@ index_copy_data(Relation rel, RelFileNode newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
@@ -14606,7 +14692,9 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
 	 */
 	switch (rel->rd_rel->relpersistence)
 	{
+		/* global temp table same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -17246,3 +17334,20 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
 		table_close(rel, NoLock);
 	}
 }
+
+static bool
+has_oncommit_option(List *options)
+{
+	ListCell   *listptr;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (pg_strcasecmp(def->defname, "on_commit_delete_rows") == 0)
+			return true;
+	}
+
+	return false;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index d625d17..69ad24f 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/vacuum.h"
@@ -1217,6 +1218,17 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	 /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		is_gtt = true;
+		up_gtt_relstats(relation,
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1230,17 +1242,26 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relpages = (int32) num_pages;
+	else if (pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (is_gtt)
+		relation->rd_rel->reltuples = (float4) num_tuples;
+	else if (pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	else if (pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1285,7 +1306,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1296,7 +1318,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1394,6 +1417,10 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/* global temp table relstats not in pg_class */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1451,6 +1478,25 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/*
+	 * Global temp table get frozenxid from MyProc
+	 * to avoid the vacuum truncate clog that gtt need.
+	 */
+	if (max_active_gtt > 0)
+	{
+		TransactionId oldest_gtt_frozenxid =
+			list_all_session_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid) &&
+			TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+		{
+			ereport(WARNING,
+				(errmsg("global temp table oldest FrozenXid is far in the past"),
+				 errhint("please truncate them or kill those sessions that use them.")));
+			newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index ee5c3a6..0d7a2d4 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandTag((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 8286d9c..c3992a4 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -591,6 +591,8 @@ static void
 set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 						  RangeTblEntry *rte)
 {
+	char relpersistence;
+
 	/*
 	 * The flag has previously been initialized to false, so we can just
 	 * return if it becomes clear that we can't safely set it.
@@ -618,7 +620,11 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			/* global temp table is same as local temp table */
+			relpersistence = get_rel_persistence(rte->relid);
+
+			if (relpersistence == RELPERSISTENCE_TEMP ||
+				relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index d6f2153..310a9e2 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6312,7 +6312,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
+	/* global temp table is same as local temp table */
 	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 748bebf..a5ddbcd 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2579,6 +2579,11 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("materialized views must not use temporary tables or views")));
 
+		if (is_query_using_gtt(query))
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("materialized views must not use global temporary tables or views")));
+
 		/*
 		 * A materialized view would either need to save parameters for use in
 		 * maintaining/loading the data or prohibit them entirely.  The latter
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 1b0edf5..492639f 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3290,17 +3290,11 @@ OptTemp:	TEMPORARY					{ $$ = RELPERSISTENCE_TEMP; }
 			| LOCAL TEMP				{ $$ = RELPERSISTENCE_TEMP; }
 			| GLOBAL TEMPORARY
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
-					$$ = RELPERSISTENCE_TEMP;
+					$$ = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
-					$$ = RELPERSISTENCE_TEMP;
+					$$ = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED					{ $$ = RELPERSISTENCE_UNLOGGED; }
 			| /*EMPTY*/					{ $$ = RELPERSISTENCE_PERMANENT; }
@@ -11591,19 +11585,13 @@ OptTempTableName:
 				}
 			| GLOBAL TEMPORARY opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED opt_table qualified_name
 				{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b875a50..123945b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index ee2d2b5..9c9abaa 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 6d1f28c..ed837d1 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2086,6 +2086,11 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* autovacuum skip vacuum global temp table */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2152,7 +2157,9 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		/* autovacuum skip vacuum global temp table */
+		if (classForm->relpersistence == RELPERSISTENCE_TEMP || 
+			classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index aba3960..3c4b96c 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -52,6 +53,7 @@
 #include "utils/rel.h"
 #include "utils/resowner_private.h"
 #include "utils/timestamp.h"
+#include "utils/guc.h"
 
 
 /* Note: these two macros only work on shared buffers, not local ones! */
@@ -432,7 +434,7 @@ ForgetPrivateRefCountEntry(PrivateRefCountEntry *ref)
 static Buffer ReadBuffer_common(SMgrRelation reln, char relpersistence,
 								ForkNumber forkNum, BlockNumber blockNum,
 								ReadBufferMode mode, BufferAccessStrategy strategy,
-								bool *hit);
+								bool *hit, Relation rel);
 static bool PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy);
 static void PinBuffer_Locked(BufferDesc *buf);
 static void UnpinBuffer(BufferDesc *buf, bool fixOwner);
@@ -664,7 +666,8 @@ ReadBufferExtended(Relation reln, ForkNumber forkNum, BlockNumber blockNum,
 	 */
 	pgstat_count_buffer_read(reln);
 	buf = ReadBuffer_common(reln->rd_smgr, reln->rd_rel->relpersistence,
-							forkNum, blockNum, mode, strategy, &hit);
+							forkNum, blockNum, mode, strategy, &hit,
+							reln);
 	if (hit)
 		pgstat_count_buffer_hit(reln);
 	return buf;
@@ -692,7 +695,7 @@ ReadBufferWithoutRelcache(RelFileNode rnode, ForkNumber forkNum,
 	Assert(InRecovery);
 
 	return ReadBuffer_common(smgr, RELPERSISTENCE_PERMANENT, forkNum, blockNum,
-							 mode, strategy, &hit);
+							 mode, strategy, &hit, NULL);
 }
 
 
@@ -704,7 +707,8 @@ ReadBufferWithoutRelcache(RelFileNode rnode, ForkNumber forkNum,
 static Buffer
 ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 				  BlockNumber blockNum, ReadBufferMode mode,
-				  BufferAccessStrategy strategy, bool *hit)
+				  BufferAccessStrategy strategy, bool *hit,
+				  Relation rel)
 {
 	BufferDesc *bufHdr;
 	Block		bufBlock;
@@ -719,6 +723,15 @@ ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 
 	isExtend = (blockNum == P_NEW);
 
+	/* create storage when first read data page for gtt */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(isExtend || blockNum == 0) &&
+		forkNum == MAIN_FORKNUM &&
+		!gtt_storage_attached(smgr->smgr_rnode.node.relNode))
+	{
+		RelationCreateStorage(smgr->smgr_rnode.node, relpersistence, rel);
+	}
+
 	TRACE_POSTGRESQL_BUFFER_READ_START(forkNum, blockNum,
 									   smgr->smgr_rnode.node.spcNode,
 									   smgr->smgr_rnode.node.dbNode,
@@ -2809,6 +2822,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(relation->rd_node.relNode))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d5..7adb969 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -21,6 +21,7 @@
 #include "access/nbtree.h"
 #include "access/subtrans.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, active_gtt_shared_hash_size());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* global temporary table */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index c3adb2e..eb95e5f 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -62,6 +62,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -4087,3 +4088,77 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temp table.
+ */
+int
+list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct *arrayP = procArray;
+	TransactionId result = InvalidTransactionId;
+	int			index;
+	int			flags = 0;
+	int			i = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+		*n = 0;
+	}
+
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		volatile PGXACT *pgxact = &allPgXact[pgprocno];
+
+		if (pgxact->vacuumFlags & flags)
+			continue;
+
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->session_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->session_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->session_gtt_frozenxid, result))
+				result = proc->session_gtt_frozenxid;
+
+			if (max_size > 0)
+			{
+				pids[i] = proc->pid;
+				xids[i] = proc->session_gtt_frozenxid;
+				i++;
+			}
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (max_size > 0)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 32df8c8..e4e1125 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c
index c5b771c..e7725ce 100644
--- a/src/backend/storage/smgr/md.c
+++ b/src/backend/storage/smgr/md.c
@@ -646,6 +646,12 @@ mdread(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
 		 */
 		if (zero_damaged_pages || InRecovery)
 			MemSet(buffer, 0, BLCKSZ);
+		else if(SmgrIsTemp(reln) && blocknum == 0 && forknum == MAIN_FORKNUM)
+		{
+			/* global temp table init btree meta page */
+			MemSet(buffer, 0, BLCKSZ);
+			mdwrite(reln, forknum, blocknum, buffer, true);
+		}
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_DATA_CORRUPTED),
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 8406644..0d99321 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -1008,6 +1008,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 7c6f057..eb10cf2 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -112,6 +112,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -4578,12 +4579,25 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -4708,15 +4722,27 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6098,6 +6124,7 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	{
 		/* Simple variable --- look to stats for the underlying table */
 		RangeTblEntry *rte = planner_rt_fetch(index->rel->relid, root);
+		char	rel_persistence = get_rel_persistence(rte->relid);
 
 		Assert(rte->rtekind == RTE_RELATION);
 		relid = rte->relid;
@@ -6115,6 +6142,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6126,6 +6160,8 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	}
 	else
 	{
+		char	rel_persistence = get_rel_persistence(index->indexoid);
+
 		/* Expression --- maybe there are stats for the index itself */
 		relid = index->indexoid;
 		colnum = 1;
@@ -6141,6 +6177,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7054,6 +7097,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		/* attempt to lookup stats in relation for this index column */
 		if (attnum != 0)
 		{
+			char	rel_persistence = get_rel_persistence(rte->relid);
+
 			/* Simple variable -- look to stats for the underlying table */
 			if (get_relation_stats_hook &&
 				(*get_relation_stats_hook) (root, rte, attnum, &vardata))
@@ -7066,6 +7111,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7078,6 +7131,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		}
 		else
 		{
+			char	rel_persistence = get_rel_persistence(index->indexoid);
+
 			/*
 			 * Looks like we've found an expression column in the index. Let's
 			 * see if there's any stats for it.
@@ -7097,6 +7152,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 1e3e6d3..345e3e9 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -2878,6 +2879,18 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index df025a5..8df9c7c 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -63,6 +63,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1124,6 +1125,29 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				/*
+				 * For global temp table, all backend can use
+				 * this relation, so rd_islocaltemp is true
+				 * in every backend.
+				 */
+				relation->rd_islocaltemp = true;
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1178,6 +1202,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			gtt_fix_index_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -2217,6 +2242,8 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		gtt_fix_index_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3313,6 +3340,15 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			/*
+			 * For global temp table, all backend can use
+			 * this relation, so rd_islocaltemp is true
+			 * in every backend.
+			 */
+			rel->rd_islocaltemp = true;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3427,6 +3463,9 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
 
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+		elog(ERROR, "global temp table does not allow setting new relfilenode");
+
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
@@ -3467,7 +3506,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 				/* handle these directly, at least for now */
 				SMgrRelation srel;
 
-				srel = RelationCreateStorage(newrnode, persistence);
+				srel = RelationCreateStorage(newrnode, persistence, relation);
 				smgrclose(srel);
 			}
 			break;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index cacbe90..9c4220b 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -140,6 +140,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temp table feature.
+ * global temp table define can still storage in catalog
+ * just can not use.
+ * num > 0 means database can management num active global temp table.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2012,6 +2024,15 @@ static struct config_bool ConfigureNamesBool[] =
 static struct config_int ConfigureNamesInt[] =
 {
 	{
+		{"max_active_global_temporary_table", PGC_POSTMASTER, UNGROUPED,
+			gettext_noop("max active global temporary table."),
+			NULL
+		},
+		&max_active_gtt,
+		DEFAULT_NUM_ACTIVE_GTT, MIN_NUM_ACTIVE_GTT, MAX_NUM_ACTIVE_GTT,
+		NULL, NULL, NULL
+	},
+	{
 		{"archive_timeout", PGC_SIGHUP, WAL_ARCHIVING,
 			gettext_noop("Forces a switch to the next WAL file if a "
 						 "new file has not been started within N seconds."),
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ec3e2c6..d3697d2 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15583,6 +15583,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	{
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
+		char		*table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15634,9 +15635,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 			binary_upgrade_set_pg_class_oids(fout, q,
 											 tbinfo->dobj.catId.oid, false);
 
+		if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED)
+			table_type = "UNLOGGED ";
+		else if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			table_type = "GLOBAL TEMPORARY ";
+		else
+			table_type = "";
+
 		appendPQExpBuffer(q, "CREATE %s%s %s",
-						  tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ?
-						  "UNLOGGED " : "",
+						  table_type,
 						  reltypename,
 						  qualrelname);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index f3c7eb9..28134e2 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3723,7 +3723,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index dc03fbd..8bd6d09 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1016,6 +1016,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2378,6 +2380,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2586,6 +2591,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index a12fc1f..78958e4 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -165,6 +165,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 2228256..4d5d13a 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5504,6 +5504,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4191',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4192',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{relid,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4193',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4194',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 048003c..af48cdf 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -19,7 +19,7 @@
 #include "storage/smgr.h"
 #include "utils/relcache.h"
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationTruncate(Relation rel, BlockNumber nblocks);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..aa80cb5
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,41 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "utils/relcache.h"
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid);
+extern bool is_other_backend_use_gtt(RelFileNode node);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(RelFileNode node);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Relation relation,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void gtt_force_enable_index(Relation index);
+extern void gtt_fix_index_state(Relation index);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 870ecb5..92c590e 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 8fda8e4..172eae9 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_TBM,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_SXACT,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index d217801..8adde87 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -117,6 +117,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId session_gtt_frozenxid;	/* session level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ce93ace..0f7262e 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -281,6 +281,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 44ed04d..b308cb8 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	struct SMgrRelationData *rd_smgr;	/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -277,6 +277,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -535,11 +536,13 @@ typedef struct ViewOptions
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -602,6 +605,17 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..50ca9ac
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,7 @@
+reset search_path;
+drop schema gtt cascade;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..e21e540
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,282 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+ERROR:  cannot create indexes on global temporary tables using concurrent mode
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+ERROR:  not support cluster global temporary tables yet
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ok
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  regular table cannot specifie on_commit_delete_rows
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  can not defeine global temp table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test;
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  referenced relation "products" is not a global temp table
+-- ERROR
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  referenced relation "products" is not a global temp table
+-- ERROR
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ALL ERROR
+create index idx_err on gtt1 using hash (a);
+ERROR:  only support btree index on global temp table
+create index idx_err on gtt1 using gist (a);
+ERROR:  data type integer has no default operator class for access method "gist"
+HINT:  You must specify an operator class for the index or define a default operator class for the data type.
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+ERROR:  only support btree index on global temp table
+create index idx_tmp_t0_1 on tmp_t0 using gist (c0);
+ERROR:  only support btree index on global temp table
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain select * from gt1 where a=1;
+                                QUERY PLAN                                 
+---------------------------------------------------------------------------
+ Bitmap Heap Scan on gt1  (cost=12.17..482.50 rows=500 width=4)
+   Recheck Cond: (a = 1)
+   ->  Bitmap Index Scan on idx_gt1_1  (cost=0.00..12.04 rows=500 width=0)
+         Index Cond: (a = 1)
+(4 rows)
+
+explain select * from gt1 where a=200000;
+                                QUERY PLAN                                 
+---------------------------------------------------------------------------
+ Bitmap Heap Scan on gt1  (cost=12.17..482.50 rows=500 width=4)
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_gt1_1  (cost=0.00..12.04 rows=500 width=0)
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain select * from gt1 where a*10=300;
+                                QUERY PLAN                                 
+---------------------------------------------------------------------------
+ Bitmap Heap Scan on gt1  (cost=12.29..483.88 rows=500 width=4)
+   Recheck Cond: ((a * 10) = 300)
+   ->  Bitmap Index Scan on idx_gt1_3  (cost=0.00..12.17 rows=500 width=0)
+         Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain select * from gt1 where a*10=3;
+                                QUERY PLAN                                 
+---------------------------------------------------------------------------
+ Bitmap Heap Scan on gt1  (cost=12.29..483.88 rows=500 width=4)
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_gt1_3  (cost=0.00..12.17 rows=500 width=0)
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain select * from gt1 where a=1;
+                                QUERY PLAN                                
+--------------------------------------------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1  (cost=0.29..8.31 rows=1 width=4)
+   Index Cond: (a = 1)
+(2 rows)
+
+explain select * from gt1 where a=200000;
+                                QUERY PLAN                                
+--------------------------------------------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1  (cost=0.29..8.31 rows=1 width=4)
+   Index Cond: (a = 200000)
+(2 rows)
+
+explain select * from gt1 where a*10=300;
+                             QUERY PLAN                              
+---------------------------------------------------------------------
+ Index Scan using idx_gt1_3 on gt1  (cost=0.42..8.44 rows=1 width=4)
+   Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain select * from gt1 where a*10=3;
+                             QUERY PLAN                              
+---------------------------------------------------------------------
+ Index Scan using idx_gt1_3 on gt1  (cost=0.42..8.44 rows=1 width=4)
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 17 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to table gtt_function.gt1
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..30d8a7b
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,84 @@
+set search_path=gtt,sys;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..850ef3e
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,161 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          475136 |                 974848
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |           409600 |        409600 |                 409600
+(2 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..9fe5fd4
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,9 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..b258b7c
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,76 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+(1 row)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+(1 row)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2ab2115..4f4eb46 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1349,6 +1349,93 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d2b17dd..80e577f 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..f3cf710
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,6 @@
+
+
+reset search_path;
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..784c537
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,187 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ok
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test;
+
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ERROR
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ERROR
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ALL ERROR
+create index idx_err on gtt1 using hash (a);
+create index idx_err on gtt1 using gist (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_1 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain select * from gt1 where a=1;
+explain select * from gt1 where a=200000;
+explain select * from gt1 where a*10=300;
+explain select * from gt1 where a*10=3;
+analyze gt1;
+explain select * from gt1 where a=1;
+explain select * from gt1 where a=200000;
+explain select * from gt1 where a*10=300;
+explain select * from gt1 where a*10=3;
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d7d81de
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,42 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..5203c2b
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,76 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..2f4d883
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,16 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..f041892
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,42 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
#116曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Robert Haas (#101)
1 attachment(s)
Re: [Proposal] Global temporary tables

2020年1月29日 下午9:48,Robert Haas <robertmhaas@gmail.com> 写道:

On Tue, Jan 28, 2020 at 12:12 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com> wrote:

Opinion by Pavel
+ rel->rd_islocaltemp = true; <<<<<<< if this is valid, then the name of field "rd_islocaltemp" is not probably best
I renamed rd_islocaltemp

I don't see any change?

Rename rd_islocaltemp to rd_istemp in global_temporary_table_v8-pg13.patch

In view of commit 6919b7e3294702adc39effd16634b2715d04f012, I think
that this has approximately a 0% chance of being acceptable. If you're
setting a field in a way that is inconsistent with the current use of
the field, you're probably doing it wrong, because the field has an
existing purpose to which new code must conform. And if you're not
doing that, then you don't need to rename it.

Thank you for pointing it out.
I've rolled back the rename.
But I still need rd_localtemp to be true, The reason is that
1 GTT The GTT needs to support DML in read-only transactions ,like local temp table.
2 GTT does not need to hold the lock before modifying the index buffer ,also like local temp table.

Please give me feedback.

Wenjing

Attachments:

global_temporary_table_v9-pg13.patchapplication/octet-stream; name=global_temporary_table_v9-pg13.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 79430d2..babb5f3 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -158,6 +158,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * For global temp table only
+	 * use ShareUpdateExclusiveLock for ensure safety
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temp table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},	
 	/* list terminator */
 	{{NULL}}
 };
@@ -1486,6 +1499,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1586,13 +1601,18 @@ build_reloptions(Datum reloptions, bool validate,
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	/*
 	 * There are no options for partitioned tables yet, but this is able to do
 	 * some validation.
 	 */
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index dd975b1..1610e7d 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1013,7 +1013,9 @@ gistGetFakeLSN(Relation rel)
 {
 	static XLogRecPtr counter = FirstNormalUnloggedLSN;
 
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	/* global temp is same as local temp table */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 4871b7f..16b00c9 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -149,7 +149,9 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	/* global temp table is same as local temp table */
+	if (!(index->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		index->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 3fa4b76..b54882d 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -598,7 +598,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -651,7 +651,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 8ce5011..0d6ae76 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -62,6 +62,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -399,9 +400,9 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
 	Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
-	/* not every AM requires these to be valid, but heap does */
-	Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
-	Assert(MultiXactIdIsValid(onerel->rd_rel->relminmxid));
+	/* not every AM requires these to be valid, but regular heap does */
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ MultiXactIdIsValid(onerel->rd_rel->relminmxid));
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index f05cbe7..946c9d2 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -763,7 +763,14 @@ _bt_getbuf(Relation rel, BlockNumber blkno, int access)
 		/* Read an existing block of the relation */
 		buf = ReadBuffer(rel, blkno);
 		LockBuffer(buf, access);
-		_bt_checkpage(rel, buf);
+
+		/* global temp table may be not yet initialized for this backend. */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			blkno == BTREE_METAPAGE &&
+			PageIsNew(BufferGetPage(buf)))
+			_bt_initmetapage(BufferGetPage(buf), P_NONE, 0);
+		else
+			_bt_checkpage(rel, buf);
 	}
 	else
 	{
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 3813ead..fd731d8 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6325,6 +6325,10 @@ StartupXLOG(void)
 	else
 		recoveryTargetTLI = ControlFile->checkPointCopy.ThisTimeLineID;
 
+	/* clean temp relation files */
+	if (max_active_gtt > 0)
+		RemovePgTempFiles();
+
 	/*
 	 * Check for signal files, and if so set up state for offline recovery
 	 */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index f8f0b48..dc8bbb1 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -42,6 +42,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 7d6acae..3be8d63 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -393,7 +393,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/* global temp table is same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 0fdff29..210d019 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -404,6 +406,10 @@ heap_create(const char *relname,
 									 relpersistence,
 									 relkind);
 
+	/* global temp table not create storage file when catalog create */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		create_storage = false;
+
 	/*
 	 * Have the storage manager create the relation's disk file, if needed.
 	 *
@@ -427,7 +433,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -956,6 +962,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -994,8 +1001,18 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/* global temp table not remember transaction info in catalog */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1357,6 +1374,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1441,11 +1459,15 @@ heap_create_with_catalog(const char *relname,
 	 */
 	StoreConstraints(new_rel_desc, cooked_constraints, is_internal);
 
-	/*
-	 * If there's a special on-commit action, remember it
-	 */
-	if (oncommit != ONCOMMIT_NOOP)
-		register_on_commit_action(relid, oncommit);
+	/* global temp table register action when storage init */
+	if (relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		/*
+		 * If there's a special on-commit action, remember it
+		 */
+		if (oncommit != ONCOMMIT_NOOP)
+			register_on_commit_action(relid, oncommit);
+	}
 
 	/*
 	 * ok, the relation has been cataloged, so close our relations and return
@@ -1937,6 +1959,13 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/* We allow to drop global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(rel->rd_node))
+			elog(ERROR, "can not drop relation when other backend attached this global temp table");
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3165,7 +3194,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3176,8 +3205,12 @@ RelationTruncateIndexes(Relation heapRelation)
 		Relation	currentIndex;
 		IndexInfo  *indexInfo;
 
+		if (RELATION_IS_GLOBAL_TEMP(heapRelation) &&
+			!gtt_storage_attached(indexId))
+			continue;
+
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3223,8 +3256,13 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		/* truncate global temp table only need RowExclusiveLock */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3257,6 +3295,8 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
+	bool		truncate_toastrel = false;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3265,23 +3305,40 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	toastrelid = rel->rd_rel->reltoastrelid;
+
+	/*
+	 * Truncate global temp table only need RowExclusiveLock
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		lockmode = RowExclusiveLock;
+		if (OidIsValid(toastrelid) &&
+			gtt_storage_attached(toastrelid))
+			truncate_toastrel = true;
+	}
+	else if (OidIsValid(toastrelid))
+		truncate_toastrel = true;
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
-	toastrelid = rel->rd_rel->reltoastrelid;
-	if (OidIsValid(toastrelid))
+	if (truncate_toastrel)
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(rel, 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 8880586..0852073 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -52,6 +52,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -877,6 +878,22 @@ index_create(Relation heapRelation,
 						indexRelationName, RelationGetRelationName(heapRelation))));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		if (accessMethodObjectId != BTREE_AM_OID)
+			elog(ERROR, "only support btree index on global temp table");
+
+		/* No support create index on global temp table use concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot create indexes on global temporary tables using concurrent mode")));
+
+		/* if global temp table not init storage, then skip build index */
+		if (!gtt_storage_attached(heapRelation->rd_node.relNode))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -2054,6 +2071,13 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/* We allow to drop index on global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation))
+	{
+		if (is_other_backend_use_gtt(userHeapRelation->rd_node))
+			elog(ERROR, "can not drop index when other backend attached this global temp table.");
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2660,6 +2684,11 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	/* update index stats into localhash and rel_rd_rel for global temp table */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		is_gtt = true;
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2745,21 +2774,35 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
+		if (is_gtt)
+			rel->rd_rel->relpages = (int32) relpages;
+		else if (rd_rel->relpages != (int32) relpages)
 		{
 			rd_rel->relpages = (int32) relpages;
 			dirty = true;
 		}
-		if (rd_rel->reltuples != (float4) reltuples)
+
+		if (is_gtt)
+			rel->rd_rel->reltuples = (float4) reltuples;
+		else if (rd_rel->reltuples != (float4) reltuples)
 		{
 			rd_rel->reltuples = (float4) reltuples;
 			dirty = true;
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+
+		if (is_gtt)
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+		else if (rd_rel->relallvisible != (int32) relallvisible)
 		{
 			rd_rel->relallvisible = (int32) relallvisible;
 			dirty = true;
 		}
+
+		if (is_gtt)
+		{
+			up_gtt_relstats(rel, relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
+		}
 	}
 
 	/*
@@ -2873,6 +2916,15 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, index, val);
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		if (!gtt_storage_attached(indexRelation->rd_node.relNode))
+		{
+			gtt_force_enable_index(indexRelation);
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3468,6 +3520,15 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 				 errmsg("cannot reindex temporary tables of other sessions")));
 
 	/*
+	 * Because global temp table cannot change relfilenode
+	 * no support reindex on global temp table yet.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(iRel))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot reindex global temporary tables")));
+
+	/*
 	 * Also check for active uses of the index in the current transaction; we
 	 * don't want to reindex underneath an open indexscan.
 	 */
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index e70243a..301da79 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -647,6 +647,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		/* global temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp relations in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index fddfbf1..671c614 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -26,6 +26,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -75,7 +76,7 @@ static PendingRelDelete *pendingDeletes = NULL; /* head of linked list */
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -85,6 +86,8 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		/* global temp table use same storage strategy as local temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -117,6 +120,10 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/* remember global temp table storage info to localhash */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel)
+		remember_gtt_storage_info(rnode, rel);
+
 	return srel;
 }
 
@@ -486,8 +493,15 @@ smgrDoPendingDeletes(bool isCommit)
 		smgrdounlinkall(srels, nrels, false);
 
 		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* clean global temp table flags when transaction commit or rollback */
+			if (SmgrIsTemp(srels[i]) && 
+				gtt_storage_attached(srels[i]->smgr_rnode.node.relNode))
+				forget_gtt_storage_info(srels[i]->smgr_rnode.node.relNode);
+		}
+
 		pfree(srels);
 	}
 }
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..71be416
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1174 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  code to create and destroy physical storage for global temparary table
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/table.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_relstats_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+typedef struct gtt_ctl_data
+{
+	LWLock			lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct
+{
+	RelFileNode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+	Oid			relid;
+
+	Oid			spcnode;
+	/* pg_class stat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+	char		relkind;
+	bool		on_commit_delete;
+
+	/* pg_statistic */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(RelFileNode rnode);
+static void gtt_storage_checkout(RelFileNode rnode, bool skiplock);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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;
+	int			natts = 0;
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_gtt to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temp table yet");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create stroage file", RelationGetRelationName(rel));
+
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		/* First time through: initialize the hash table */
+		HASHCTL		ctl;
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		gtt_storage_local_hash =
+			hash_create("global temp relation table",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_relstats_context =
+			AllocSetContextCreate(CacheMemoryContext,
+							"gtt relstats context",
+							ALLOCSET_DEFAULT_SIZES);
+	}
+
+	/* Look up or create an entry */
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_ENTER, &found);
+
+	if (found)
+	{
+		elog(ERROR, "backend %d relid %u already exists in global temp table local hash",
+					MyBackendId, relid);
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+	entry->spcnode = rnode.spcNode;
+	entry->relpages = 0;
+	entry->reltuples = 0;
+	entry->relallvisible = 0;
+	entry->relkind = rel->rd_rel->relkind;
+	natts = RelationGetNumberOfAttributes(rel);
+	entry->natts = natts;
+	entry->attnum = palloc0(sizeof(int) * natts);
+	entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+	/* only heap contain transaction information */
+	if (entry->relkind == RELKIND_RELATION ||
+		entry->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+		{
+			entry->on_commit_delete = true;
+			register_on_commit_action(rel->rd_node.relNode, ONCOMMIT_DELETE_ROWS);
+		}
+
+		entry->relfrozenxid = RecentXmin;
+		entry->relminmxid = GetOldestMultiXactId();
+		insert_gtt_relfrozenxid_to_ordered_list(entry->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+		gtt_storage_checkin(rnode);
+	}
+	else
+	{
+		entry->on_commit_delete = false;
+		entry->relfrozenxid = 0;
+		entry->relminmxid = 0;
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+void
+forget_gtt_storage_info(Oid relid)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry)
+	{
+		int		i;
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			RelFileNode rnode;
+
+			rnode.spcNode = entry->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = entry->relid;
+
+			gtt_storage_checkout(rnode, false);
+
+			Assert(TransactionIdIsNormal(entry->relfrozenxid));
+			remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+
+		for (i = 0; i < entry->natts; i++)
+		{
+			if (entry->att_stat_tups[i])
+			{
+				heap_freetuple(entry->att_stat_tups[i]);
+				entry->att_stat_tups[i] = NULL;
+			}
+		}
+
+		pfree(entry->attnum);
+		pfree(entry->att_stat_tups);
+	}
+
+	hash_search(gtt_storage_local_hash,
+			(void *) &(relid), HASH_REMOVE, NULL);
+
+	return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool found;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (gtt_storage_local_hash == NULL)
+		return false;
+
+	hash_search(gtt_storage_local_hash,
+			(void *) &(relid), HASH_FIND, &found);
+
+	return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS				status;
+	gtt_local_hash_entry		*entry;
+	int			nrels = 0,
+				maxrels = 0;
+	SMgrRelation	*srels = NULL;
+	RelFileNode		*rnodes = NULL;
+	char			*relkinds = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		SMgrRelation srel;
+		RelFileNode rnode;
+
+		rnode.spcNode = entry->spcnode;
+		rnode.dbNode = MyDatabaseId;
+		rnode.relNode = entry->relid;
+
+		srel = smgropen(rnode, MyBackendId);
+
+		/* allocate the initial array, or extend it, if needed */
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			srels = palloc(sizeof(SMgrRelation) * maxrels);
+			rnodes = palloc(sizeof(RelFileNode) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+			rnodes = repalloc(rnodes, sizeof(RelFileNode) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		srels[nrels] = srel;
+		rnodes[nrels] = rnode;
+		relkinds[nrels] = entry->relkind;
+		nrels++;
+	}
+
+	if (nrels > 0)
+	{
+		int i;
+
+		smgrdounlinkall(srels, nrels, false);
+
+		LWLockAcquire(&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.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (gtt_storage_local_hash == NULL)
+		return false;
+
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_FIND, &found);
+
+	if (!found)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	if (relpages)
+		*relpages = entry->relpages;
+
+	if (reltuples)
+		*reltuples = entry->reltuples;
+
+	if (relallvisible)
+		*relallvisible = entry->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = entry->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = entry->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	MemoryContext			oldcontext;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+						(void *) &reloid, HASH_FIND, &found);
+
+	if (!found)
+		return;
+
+	/* todo */
+	if (entry->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	Assert(entry->relid == reloid);
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == 0)
+		{
+			entry->attnum[i] = attnum;
+			break;
+		}
+		else if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < entry->natts);
+	Assert(entry->att_stat_tups[i] == NULL);
+	entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+						(void *) &reloid, HASH_FIND, &found);
+
+	if (!found)
+		return NULL;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			return entry->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int			i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	HeapTuple	tuple;
+	Relation	rel = NULL;
+	int		attnum = PG_GETARG_INT32(1);
+	Oid		reloid = PG_GETARG_OID(0);
+	char		rel_persistence;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	  tupdesc;
+	MemoryContext oldcontext;
+	Tuplestorestate *tupstore;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = table_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+
+	table_close(rel, NoLock);
+	table_close(pg_tatistic, AccessShareLock);
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate	*tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Relation	rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	if (get_gtt_relstats(reloid,
+						&relpages, &reltuples, &relallvisible,
+						&relfrozenxid, &relminmxid))
+	{
+		Datum	values[5];
+		bool	isnull[5];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = Int32GetDatum(relpages);
+		values[1] = Float4GetDatum((float4)reltuples);
+		values[2] = Int32GetDatum(relallvisible);
+		values[3] = UInt32GetDatum(relfrozenxid);
+		values[4] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+
+	tuplestore_donestoring(tupstore);
+	table_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(rel->rd_node);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	table_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(indexOid));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid relOid = index->rd_index->indrelid;
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	if (gtt_storage_attached(relOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index c9e6060..d814ff2 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM 
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c4420dd..977a984 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -586,14 +587,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 		 * pg_statistic for columns we didn't process, we leave them alone.)
 		 */
 		update_attstats(RelationGetRelid(onerel), inh,
-						attr_cnt, vacattrstats);
+						attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
 
 		for (ind = 0; ind < nindexes; ind++)
 		{
 			AnlIndexData *thisdata = &indexdata[ind];
 
 			update_attstats(RelationGetRelid(Irel[ind]), false,
-							thisdata->attr_cnt, thisdata->vacattrstats);
+							thisdata->attr_cnt, thisdata->vacattrstats,
+							RelationGetRelPersistence(Irel[ind]));
 		}
 
 		/*
@@ -1456,7 +1458,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
  *		by taking a self-exclusive lock on the relation in analyze_rel().
  */
 static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
 {
 	Relation	sd;
 	int			attno;
@@ -1558,31 +1560,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/* Update column statistic to localhash, not catalog */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index e9d7a7f..3088e26 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -391,6 +391,12 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/* not support cluster global temp table yet */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("not support cluster global temporary tables yet")));
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index ec20ba3..a29cc00 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2614,6 +2614,16 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 			!isTempNamespace(classtuple->relnamespace))
 			continue;
 
+		/* not support reindex on global temp table, so skip it */
+		if (classtuple->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			ereport(WARNING,
+				(errmsg("global temp table \"%s.%s\" skip reindexed",
+					get_namespace_name(get_rel_namespace(relid)),
+					get_rel_name(relid))));
+			continue;
+		}
+
 		/* Check user/system classification, and optionally skip */
 		if (objectKind == REINDEX_OBJECT_SYSTEM &&
 			!IsSystemClass(relid, classtuple))
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 329ab84..dfa257c 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -96,7 +96,8 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 	 * transaction.
 	 */
 	relpersistence = get_rel_persistence(relid);
-	if (relpersistence == RELPERSISTENCE_TEMP)
+	if (relpersistence == RELPERSISTENCE_TEMP ||
+		relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
 
 	/* Check permissions. */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..44f350d 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -94,7 +94,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +108,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -222,7 +223,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* now initialize the sequence's data */
 	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -327,7 +328,7 @@ ResetSequence(Oid seq_relid)
 	/*
 	 * Insert the modified tuple into the new storage file.
 	 */
-	fill_seq_with_data(seq_rel, tuple);
+	fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -340,17 +341,21 @@ ResetSequence(Oid seq_relid)
  * Initialize a sequence's relation with the specified tuple as content
  */
 static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 	sequence_magic *sm;
 	OffsetNumber offnum;
+	bool lockBuffer = false;
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ReadBuffer(rel, P_NEW);
-	Assert(BufferGetBlockNumber(buf) == 0);
+	if (buf == InvalidBuffer)
+	{
+		buf = ReadBuffer(rel, P_NEW);
+		Assert(BufferGetBlockNumber(buf) == 0);
+		lockBuffer = true;
+	}
 
 	page = BufferGetPage(buf);
 
@@ -360,7 +365,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* Now insert sequence tuple */
 
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+	if (lockBuffer)
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +416,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	END_CRIT_SECTION();
 
-	UnlockReleaseBuffer(buf);
+	if (lockBuffer)
+		UnlockReleaseBuffer(buf);
 }
 
 /*
@@ -502,7 +509,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * Insert the modified tuple into the new storage file.
 		 */
-		fill_seq_with_data(seqrel, newdatatuple);
+		fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
 	}
 
 	/* process OWNED BY if given */
@@ -1178,6 +1185,25 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
 	LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
 
 	page = BufferGetPage(*buf);
+	if (GlobalTempRelationPageIsNotInitialized(rel, page))
+	{
+		/* Initialize sequence for global temporary tables */
+		Datum		value[SEQ_COL_LASTCOL] = {0};
+		bool		null[SEQ_COL_LASTCOL] = {false};
+		HeapTuple	tuple;
+		int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+		/*
+		 * last_value from pg_sequence.seqstart
+		 * log_cnt = 0
+		 * is_called = false
+		 */
+		value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+		tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+		fill_seq_with_data(rel, tuple, *buf);
+		heap_freetuple(tuple);
+	}
 	sm = (sequence_magic *) PageGetSpecialPointer(page);
 
 	if (sm->magic != SEQ_MAGIC)
@@ -1954,3 +1980,23 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 70589dd..81bd3e5 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -552,6 +553,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 								  Relation partitionTbl);
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static bool has_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -597,6 +599,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	bool		has_oncommit_clause = false;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -607,8 +610,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Check consistency of arguments
 	 */
+	/* global temp table same as local temp table */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !(stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+			 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -638,7 +643,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	/* global temp table same as local temp table */
+	if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+		 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -739,6 +746,57 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* global temp table */
+	has_oncommit_clause = has_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* inherit table or parition table inherit on commit property from parent table*/
+		if (inheritOids && stmt->oncommit == ONCOMMIT_NOOP && !has_oncommit_clause)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			if (RELATION_GTT_ON_COMMIT_DELETE(relation))
+				stmt->oncommit = ONCOMMIT_DELETE_ROWS;
+			else
+				stmt->oncommit = ONCOMMIT_PRESERVE_ROWS;
+			table_close(relation, NoLock);
+		}
+
+		if (has_oncommit_clause)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "can not defeine global temp table with on commit and with clause at same time");
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temp table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (has_oncommit_clause)
+		elog(ERROR, "regular table cannot specifie on_commit_delete_rows");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1816,7 +1874,8 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
 		 * table or the current physical file to be thrown away anyway.
 		 */
 		if (rel->rd_createSubid == mySubid ||
-			rel->rd_newRelfilenodeSubid == mySubid)
+			rel->rd_newRelfilenodeSubid == mySubid ||
+			RELATION_IS_GLOBAL_TEMP(rel))
 		{
 			/* Immediate, non-rollbackable truncation is OK */
 			heap_truncate_one_rel(rel);
@@ -3375,6 +3434,13 @@ RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal, bo
 	 * specially.
 	 */
 	targetrelation = relation_open(myrelid, is_index ? ShareUpdateExclusiveLock : AccessExclusiveLock);
+
+	if (RELATION_IS_GLOBAL_TEMP(targetrelation))
+	{
+		if (is_other_backend_use_gtt(targetrelation->rd_node))
+			elog(ERROR, "can not rename relation when other backend attached this global temp table");
+	}
+
 	namespaceId = RelationGetNamespace(targetrelation);
 
 	/*
@@ -3550,6 +3616,13 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(rel->rd_node))
+			elog(ERROR, "can not alter relation when other backend attached this global temp table");
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -8123,6 +8196,13 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 				 errmsg("referenced relation \"%s\" is not a table",
 						RelationGetRelationName(pkrel))));
 
+	/* global temp table not support foreign key constraint yet */
+	if (RELATION_IS_GLOBAL_TEMP(pkrel))
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("referenced relation \"%s\" is not a global temp table",
+						RelationGetRelationName(pkrel))));
+
 	if (!allowSystemTableMods && IsSystemRelation(pkrel))
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -8162,6 +8242,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		/* global temp table not support foreign key constraint yet */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("not support foreign key constraints on global temp table yet")));
+			break;
 	}
 
 	/*
@@ -13199,7 +13285,7 @@ index_copy_data(Relation rel, RelFileNode newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
@@ -14606,7 +14692,9 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
 	 */
 	switch (rel->rd_rel->relpersistence)
 	{
+		/* global temp table same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -17246,3 +17334,20 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
 		table_close(rel, NoLock);
 	}
 }
+
+static bool
+has_oncommit_option(List *options)
+{
+	ListCell   *listptr;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (pg_strcasecmp(def->defname, "on_commit_delete_rows") == 0)
+			return true;
+	}
+
+	return false;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index d625d17..69ad24f 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/vacuum.h"
@@ -1217,6 +1218,17 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	 /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		is_gtt = true;
+		up_gtt_relstats(relation,
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1230,17 +1242,26 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relpages = (int32) num_pages;
+	else if (pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (is_gtt)
+		relation->rd_rel->reltuples = (float4) num_tuples;
+	else if (pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	else if (pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1285,7 +1306,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1296,7 +1318,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1394,6 +1417,10 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/* global temp table relstats not in pg_class */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1451,6 +1478,25 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/*
+	 * Global temp table get frozenxid from MyProc
+	 * to avoid the vacuum truncate clog that gtt need.
+	 */
+	if (max_active_gtt > 0)
+	{
+		TransactionId oldest_gtt_frozenxid =
+			list_all_session_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid) &&
+			TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+		{
+			ereport(WARNING,
+				(errmsg("global temp table oldest FrozenXid is far in the past"),
+				 errhint("please truncate them or kill those sessions that use them.")));
+			newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index ee5c3a6..0d7a2d4 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandTag((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 8286d9c..c3992a4 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -591,6 +591,8 @@ static void
 set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 						  RangeTblEntry *rte)
 {
+	char relpersistence;
+
 	/*
 	 * The flag has previously been initialized to false, so we can just
 	 * return if it becomes clear that we can't safely set it.
@@ -618,7 +620,11 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			/* global temp table is same as local temp table */
+			relpersistence = get_rel_persistence(rte->relid);
+
+			if (relpersistence == RELPERSISTENCE_TEMP ||
+				relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index d6f2153..310a9e2 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6312,7 +6312,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
+	/* global temp table is same as local temp table */
 	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 748bebf..a5ddbcd 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2579,6 +2579,11 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("materialized views must not use temporary tables or views")));
 
+		if (is_query_using_gtt(query))
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("materialized views must not use global temporary tables or views")));
+
 		/*
 		 * A materialized view would either need to save parameters for use in
 		 * maintaining/loading the data or prohibit them entirely.  The latter
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 1b0edf5..492639f 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3290,17 +3290,11 @@ OptTemp:	TEMPORARY					{ $$ = RELPERSISTENCE_TEMP; }
 			| LOCAL TEMP				{ $$ = RELPERSISTENCE_TEMP; }
 			| GLOBAL TEMPORARY
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
-					$$ = RELPERSISTENCE_TEMP;
+					$$ = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
-					$$ = RELPERSISTENCE_TEMP;
+					$$ = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED					{ $$ = RELPERSISTENCE_UNLOGGED; }
 			| /*EMPTY*/					{ $$ = RELPERSISTENCE_PERMANENT; }
@@ -11591,19 +11585,13 @@ OptTempTableName:
 				}
 			| GLOBAL TEMPORARY opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED opt_table qualified_name
 				{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b875a50..123945b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index ee2d2b5..9c9abaa 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 6d1f28c..ed837d1 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2086,6 +2086,11 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* autovacuum skip vacuum global temp table */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2152,7 +2157,9 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		/* autovacuum skip vacuum global temp table */
+		if (classForm->relpersistence == RELPERSISTENCE_TEMP || 
+			classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index aba3960..3c4b96c 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -52,6 +53,7 @@
 #include "utils/rel.h"
 #include "utils/resowner_private.h"
 #include "utils/timestamp.h"
+#include "utils/guc.h"
 
 
 /* Note: these two macros only work on shared buffers, not local ones! */
@@ -432,7 +434,7 @@ ForgetPrivateRefCountEntry(PrivateRefCountEntry *ref)
 static Buffer ReadBuffer_common(SMgrRelation reln, char relpersistence,
 								ForkNumber forkNum, BlockNumber blockNum,
 								ReadBufferMode mode, BufferAccessStrategy strategy,
-								bool *hit);
+								bool *hit, Relation rel);
 static bool PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy);
 static void PinBuffer_Locked(BufferDesc *buf);
 static void UnpinBuffer(BufferDesc *buf, bool fixOwner);
@@ -664,7 +666,8 @@ ReadBufferExtended(Relation reln, ForkNumber forkNum, BlockNumber blockNum,
 	 */
 	pgstat_count_buffer_read(reln);
 	buf = ReadBuffer_common(reln->rd_smgr, reln->rd_rel->relpersistence,
-							forkNum, blockNum, mode, strategy, &hit);
+							forkNum, blockNum, mode, strategy, &hit,
+							reln);
 	if (hit)
 		pgstat_count_buffer_hit(reln);
 	return buf;
@@ -692,7 +695,7 @@ ReadBufferWithoutRelcache(RelFileNode rnode, ForkNumber forkNum,
 	Assert(InRecovery);
 
 	return ReadBuffer_common(smgr, RELPERSISTENCE_PERMANENT, forkNum, blockNum,
-							 mode, strategy, &hit);
+							 mode, strategy, &hit, NULL);
 }
 
 
@@ -704,7 +707,8 @@ ReadBufferWithoutRelcache(RelFileNode rnode, ForkNumber forkNum,
 static Buffer
 ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 				  BlockNumber blockNum, ReadBufferMode mode,
-				  BufferAccessStrategy strategy, bool *hit)
+				  BufferAccessStrategy strategy, bool *hit,
+				  Relation rel)
 {
 	BufferDesc *bufHdr;
 	Block		bufBlock;
@@ -719,6 +723,15 @@ ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 
 	isExtend = (blockNum == P_NEW);
 
+	/* create storage when first read data page for gtt */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(isExtend || blockNum == 0) &&
+		forkNum == MAIN_FORKNUM &&
+		!gtt_storage_attached(smgr->smgr_rnode.node.relNode))
+	{
+		RelationCreateStorage(smgr->smgr_rnode.node, relpersistence, rel);
+	}
+
 	TRACE_POSTGRESQL_BUFFER_READ_START(forkNum, blockNum,
 									   smgr->smgr_rnode.node.spcNode,
 									   smgr->smgr_rnode.node.dbNode,
@@ -2809,6 +2822,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(relation->rd_node.relNode))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d5..7adb969 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -21,6 +21,7 @@
 #include "access/nbtree.h"
 #include "access/subtrans.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, active_gtt_shared_hash_size());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* global temporary table */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index c3adb2e..eb95e5f 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -62,6 +62,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -4087,3 +4088,77 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temp table.
+ */
+int
+list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct *arrayP = procArray;
+	TransactionId result = InvalidTransactionId;
+	int			index;
+	int			flags = 0;
+	int			i = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+		*n = 0;
+	}
+
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		volatile PGXACT *pgxact = &allPgXact[pgprocno];
+
+		if (pgxact->vacuumFlags & flags)
+			continue;
+
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->session_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->session_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->session_gtt_frozenxid, result))
+				result = proc->session_gtt_frozenxid;
+
+			if (max_size > 0)
+			{
+				pids[i] = proc->pid;
+				xids[i] = proc->session_gtt_frozenxid;
+				i++;
+			}
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (max_size > 0)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 32df8c8..e4e1125 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c
index c5b771c..e7725ce 100644
--- a/src/backend/storage/smgr/md.c
+++ b/src/backend/storage/smgr/md.c
@@ -646,6 +646,12 @@ mdread(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
 		 */
 		if (zero_damaged_pages || InRecovery)
 			MemSet(buffer, 0, BLCKSZ);
+		else if(SmgrIsTemp(reln) && blocknum == 0 && forknum == MAIN_FORKNUM)
+		{
+			/* global temp table init btree meta page */
+			MemSet(buffer, 0, BLCKSZ);
+			mdwrite(reln, forknum, blocknum, buffer, true);
+		}
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_DATA_CORRUPTED),
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 8406644..0d99321 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -1008,6 +1008,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 7c6f057..eb10cf2 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -112,6 +112,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -4578,12 +4579,25 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -4708,15 +4722,27 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6098,6 +6124,7 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	{
 		/* Simple variable --- look to stats for the underlying table */
 		RangeTblEntry *rte = planner_rt_fetch(index->rel->relid, root);
+		char	rel_persistence = get_rel_persistence(rte->relid);
 
 		Assert(rte->rtekind == RTE_RELATION);
 		relid = rte->relid;
@@ -6115,6 +6142,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6126,6 +6160,8 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	}
 	else
 	{
+		char	rel_persistence = get_rel_persistence(index->indexoid);
+
 		/* Expression --- maybe there are stats for the index itself */
 		relid = index->indexoid;
 		colnum = 1;
@@ -6141,6 +6177,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7054,6 +7097,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		/* attempt to lookup stats in relation for this index column */
 		if (attnum != 0)
 		{
+			char	rel_persistence = get_rel_persistence(rte->relid);
+
 			/* Simple variable -- look to stats for the underlying table */
 			if (get_relation_stats_hook &&
 				(*get_relation_stats_hook) (root, rte, attnum, &vardata))
@@ -7066,6 +7111,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7078,6 +7131,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		}
 		else
 		{
+			char	rel_persistence = get_rel_persistence(index->indexoid);
+
 			/*
 			 * Looks like we've found an expression column in the index. Let's
 			 * see if there's any stats for it.
@@ -7097,6 +7152,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 1e3e6d3..345e3e9 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -2878,6 +2879,18 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index df025a5..8df9c7c 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -63,6 +63,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1124,6 +1125,29 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				/*
+				 * For global temp table, all backend can use
+				 * this relation, so rd_islocaltemp is true
+				 * in every backend.
+				 */
+				relation->rd_islocaltemp = true;
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1178,6 +1202,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			gtt_fix_index_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -2217,6 +2242,8 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		gtt_fix_index_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3313,6 +3340,15 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			/*
+			 * For global temp table, all backend can use
+			 * this relation, so rd_islocaltemp is true
+			 * in every backend.
+			 */
+			rel->rd_islocaltemp = true;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3427,6 +3463,9 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
 
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+		elog(ERROR, "global temp table does not allow setting new relfilenode");
+
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
@@ -3467,7 +3506,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 				/* handle these directly, at least for now */
 				SMgrRelation srel;
 
-				srel = RelationCreateStorage(newrnode, persistence);
+				srel = RelationCreateStorage(newrnode, persistence, relation);
 				smgrclose(srel);
 			}
 			break;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index cacbe90..9c4220b 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -140,6 +140,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temp table feature.
+ * global temp table define can still storage in catalog
+ * just can not use.
+ * num > 0 means database can management num active global temp table.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2012,6 +2024,15 @@ static struct config_bool ConfigureNamesBool[] =
 static struct config_int ConfigureNamesInt[] =
 {
 	{
+		{"max_active_global_temporary_table", PGC_POSTMASTER, UNGROUPED,
+			gettext_noop("max active global temporary table."),
+			NULL
+		},
+		&max_active_gtt,
+		DEFAULT_NUM_ACTIVE_GTT, MIN_NUM_ACTIVE_GTT, MAX_NUM_ACTIVE_GTT,
+		NULL, NULL, NULL
+	},
+	{
 		{"archive_timeout", PGC_SIGHUP, WAL_ARCHIVING,
 			gettext_noop("Forces a switch to the next WAL file if a "
 						 "new file has not been started within N seconds."),
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ec3e2c6..d3697d2 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15583,6 +15583,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	{
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
+		char		*table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15634,9 +15635,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 			binary_upgrade_set_pg_class_oids(fout, q,
 											 tbinfo->dobj.catId.oid, false);
 
+		if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED)
+			table_type = "UNLOGGED ";
+		else if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			table_type = "GLOBAL TEMPORARY ";
+		else
+			table_type = "";
+
 		appendPQExpBuffer(q, "CREATE %s%s %s",
-						  tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ?
-						  "UNLOGGED " : "",
+						  table_type,
 						  reltypename,
 						  qualrelname);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index f3c7eb9..28134e2 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3723,7 +3723,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index dc03fbd..8bd6d09 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1016,6 +1016,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2378,6 +2380,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2586,6 +2591,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index a12fc1f..78958e4 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -165,6 +165,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 2228256..4d5d13a 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5504,6 +5504,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4191',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4192',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{relid,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4193',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4194',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 048003c..af48cdf 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -19,7 +19,7 @@
 #include "storage/smgr.h"
 #include "utils/relcache.h"
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationTruncate(Relation rel, BlockNumber nblocks);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..aa80cb5
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,41 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "utils/relcache.h"
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid);
+extern bool is_other_backend_use_gtt(RelFileNode node);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(RelFileNode node);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Relation relation,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void gtt_force_enable_index(Relation index);
+extern void gtt_fix_index_state(Relation index);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 870ecb5..92c590e 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 8fda8e4..172eae9 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_TBM,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_SXACT,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index d217801..8adde87 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -117,6 +117,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId session_gtt_frozenxid;	/* session level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ce93ace..0f7262e 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -281,6 +281,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 44ed04d..b308cb8 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	struct SMgrRelationData *rd_smgr;	/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -277,6 +277,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -535,11 +536,13 @@ typedef struct ViewOptions
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -602,6 +605,17 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..50ca9ac
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,7 @@
+reset search_path;
+drop schema gtt cascade;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..e21e540
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,282 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+ERROR:  cannot create indexes on global temporary tables using concurrent mode
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+ERROR:  not support cluster global temporary tables yet
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ok
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  regular table cannot specifie on_commit_delete_rows
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  can not defeine global temp table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test;
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  referenced relation "products" is not a global temp table
+-- ERROR
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  referenced relation "products" is not a global temp table
+-- ERROR
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ALL ERROR
+create index idx_err on gtt1 using hash (a);
+ERROR:  only support btree index on global temp table
+create index idx_err on gtt1 using gist (a);
+ERROR:  data type integer has no default operator class for access method "gist"
+HINT:  You must specify an operator class for the index or define a default operator class for the data type.
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+ERROR:  only support btree index on global temp table
+create index idx_tmp_t0_1 on tmp_t0 using gist (c0);
+ERROR:  only support btree index on global temp table
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain select * from gt1 where a=1;
+                                QUERY PLAN                                 
+---------------------------------------------------------------------------
+ Bitmap Heap Scan on gt1  (cost=12.17..482.50 rows=500 width=4)
+   Recheck Cond: (a = 1)
+   ->  Bitmap Index Scan on idx_gt1_1  (cost=0.00..12.04 rows=500 width=0)
+         Index Cond: (a = 1)
+(4 rows)
+
+explain select * from gt1 where a=200000;
+                                QUERY PLAN                                 
+---------------------------------------------------------------------------
+ Bitmap Heap Scan on gt1  (cost=12.17..482.50 rows=500 width=4)
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_gt1_1  (cost=0.00..12.04 rows=500 width=0)
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain select * from gt1 where a*10=300;
+                                QUERY PLAN                                 
+---------------------------------------------------------------------------
+ Bitmap Heap Scan on gt1  (cost=12.29..483.88 rows=500 width=4)
+   Recheck Cond: ((a * 10) = 300)
+   ->  Bitmap Index Scan on idx_gt1_3  (cost=0.00..12.17 rows=500 width=0)
+         Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain select * from gt1 where a*10=3;
+                                QUERY PLAN                                 
+---------------------------------------------------------------------------
+ Bitmap Heap Scan on gt1  (cost=12.29..483.88 rows=500 width=4)
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_gt1_3  (cost=0.00..12.17 rows=500 width=0)
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain select * from gt1 where a=1;
+                                QUERY PLAN                                
+--------------------------------------------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1  (cost=0.29..8.31 rows=1 width=4)
+   Index Cond: (a = 1)
+(2 rows)
+
+explain select * from gt1 where a=200000;
+                                QUERY PLAN                                
+--------------------------------------------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1  (cost=0.29..8.31 rows=1 width=4)
+   Index Cond: (a = 200000)
+(2 rows)
+
+explain select * from gt1 where a*10=300;
+                             QUERY PLAN                              
+---------------------------------------------------------------------
+ Index Scan using idx_gt1_3 on gt1  (cost=0.42..8.44 rows=1 width=4)
+   Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain select * from gt1 where a*10=3;
+                             QUERY PLAN                              
+---------------------------------------------------------------------
+ Index Scan using idx_gt1_3 on gt1  (cost=0.42..8.44 rows=1 width=4)
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 17 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to table gtt_function.gt1
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..30d8a7b
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,84 @@
+set search_path=gtt,sys;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..850ef3e
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,161 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          475136 |                 974848
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |           409600 |        409600 |                 409600
+(2 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..9fe5fd4
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,9 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..b258b7c
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,76 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+(1 row)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+(1 row)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2ab2115..4f4eb46 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1349,6 +1349,93 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d2b17dd..80e577f 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..f3cf710
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,6 @@
+
+
+reset search_path;
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..784c537
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,187 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ok
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test;
+
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ERROR
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ERROR
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ALL ERROR
+create index idx_err on gtt1 using hash (a);
+create index idx_err on gtt1 using gist (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_1 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain select * from gt1 where a=1;
+explain select * from gt1 where a=200000;
+explain select * from gt1 where a*10=300;
+explain select * from gt1 where a*10=3;
+analyze gt1;
+explain select * from gt1 where a=1;
+explain select * from gt1 where a=200000;
+explain select * from gt1 where a*10=300;
+explain select * from gt1 where a*10=3;
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d7d81de
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,42 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..5203c2b
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,76 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..2f4d883
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,16 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..f041892
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,42 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
#117Pavel Stehule
pavel.stehule@gmail.com
In reply to: 曾文旌(义从) (#116)
Re: [Proposal] Global temporary tables

čt 30. 1. 2020 v 15:17 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
napsal:

2020年1月29日 下午9:48,Robert Haas <robertmhaas@gmail.com> 写道:

On Tue, Jan 28, 2020 at 12:12 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>

wrote:

Opinion by Pavel
+ rel->rd_islocaltemp = true; <<<<<<< if this is valid, then the name

of field "rd_islocaltemp" is not probably best

I renamed rd_islocaltemp

I don't see any change?

Rename rd_islocaltemp to rd_istemp in

global_temporary_table_v8-pg13.patch

In view of commit 6919b7e3294702adc39effd16634b2715d04f012, I think
that this has approximately a 0% chance of being acceptable. If you're
setting a field in a way that is inconsistent with the current use of
the field, you're probably doing it wrong, because the field has an
existing purpose to which new code must conform. And if you're not
doing that, then you don't need to rename it.

Thank you for pointing it out.
I've rolled back the rename.
But I still need rd_localtemp to be true, The reason is that
1 GTT The GTT needs to support DML in read-only transactions ,like local
temp table.
2 GTT does not need to hold the lock before modifying the index buffer
,also like local temp table.

Please give me feedback.

maybe some like

rel->rd_globaltemp = true;

and somewhere else

if (rel->rd_localtemp || rel->rd_globaltemp)
{
...
}

Show quoted text

Wenjing

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#118Robert Haas
robertmhaas@gmail.com
In reply to: Konstantin Knizhnik (#110)
Re: [Proposal] Global temporary tables

On Thu, Jan 30, 2020 at 4:33 AM Konstantin Knizhnik
<k.knizhnik@postgrespro.ru> wrote:

On 29.01.2020 21:16, Robert Haas wrote:

On Wed, Jan 29, 2020 at 10:30 AM Konstantin Knizhnik
<k.knizhnik@postgrespro.ru> wrote:

I think that the idea of calling ambuild() on the fly is not going to
work, because, again, I don't think that calling that from random
places in the code is safe.

It is not a random place in the code.
Actually it is just one place - _bt_getbuf
Why it can be unsafe if it affects only private backends data?

Because, as I already said, not every operation is safe at every point
in the code. This is true even when there's no concurrency involved.
For example, executing user-defined code is not safe while holding a
buffer lock, because the user-defined code might try to do something
that locks the same buffer, which would cause an undetected,
uninterruptible deadlock.

But GTT case is different. Heap and indexes can be easily initialized by
backend using existed functions.

That would be nice if we could make it work. Someone would need to
show, however, that it's safe.

You say that it not safe. But you have not explained why it is unsafe.
Yes, I agree that it is my responsibility to prove that it is safe.
And as I already wrote, I can not provide such proof now. I will be
pleased if you or anybody else can help to convince that this approach
is safe or demonstrate problems with this approach.

That's fair, but nobody's obliged to spend time on that.

But I really like to receive more constructive critics rather than "this
approach is wrong because it is unsafe".

I'm sure, and that's probably valid. Equally, however, I'd like to
receive more analysis of why it is safe than "I don't see anything
wrong with it so it's probably fine." And I think that's pretty valid,
too.

Actually index is not created on the fly.
Index is created is usual way, by executing "create index" command.
So all components of the Postgres (planner, executor,...) treat GTT
indexes in the same way as regular indexes.
Locking and invalidations policies are exactly the same for them.
The only difference is that content of GTT index is constructed on
demand using private backend data.
Is it safe or not? We are just reading data from local buffers/files and
writing them here.
May be I missed something but I do not see any unsafety here.
There are issues with updating statistic but them can be solved.

But that's not all you are doing. To build the index, you'll have to
sort the data. To sort the data, you'll have to call btree support
functions. Those functions can be user-defined, and can do complex
operations like catalog access that depend on a good transaction
state, no buffer locks being held, and nothing already in progress
within this backend that can get confused as a result of this
operation.

Just as a quick test, I tried doing this in _bt_getbuf:

+    if (InterruptHoldoffCount != 0)
+        elog(WARNING, "INTERRUPTS ARE HELD OFF");

That causes 103 out of 196 regression tests to fail, which means that
it's pretty common to arrive in _bt_getbuf() with interrupts held off.
At the very least, that means that the index build would be
uninterruptible, which already seems unacceptable. Probably, it means
that the calling code is already holding an LWLock, because that's
normally what causes HOLD_INTERRUPTS() to happen. And as I have
already explained, that is super-problematic, because of deadlock
risks, and because it risks putting other backends into
non-interruptible waits if they should happen to need the LWLock we're
holding here.

I really don't understand why the basic point here remains obscure. In
general, it tends to be unsafe to call high-level code from low-level
code, not just in PostgreSQL but in pretty much any program. Do you
think that we can safely add a GUC that executes a user-defined SQL
query every time an LWLock is acquired? If you do, why don't you try
adding code to do that to LWLockAcquire and testing it out a little
bit? Try making the SQL query do something like query pg_class, find a
table name that's not in use, and create a table by that name. Then
run the regression tests with the GUC set to run that query and see
how it goes. I always hate to say that things are "obvious," because
what's obvious to me may not be obvious to somebody else, but it is
clear to me, at least, that this has no chance of working. Even though
I can't say exactly what will break, or what will break first, I'm
very sure that a lot of things will break and that most of them are
unfixable.

Now, your idea is not quite as crazy as that, but it has the same
basic problem: you can't insert code into a low-level facility that
uses a high level facility which may in turn use and depend on that
very same low-level facility to not be in the middle of an operation.
If you do, it's going to break somehow.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#119曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Pavel Stehule (#117)
1 attachment(s)
Re: [Proposal] Global temporary tables

2020年1月30日 下午10:21,Pavel Stehule <pavel.stehule@gmail.com> 写道:

čt 30. 1. 2020 v 15:17 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:

2020年1月29日 下午9:48,Robert Haas <robertmhaas@gmail.com <mailto:robertmhaas@gmail.com>> 写道:

On Tue, Jan 28, 2020 at 12:12 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> wrote:

Opinion by Pavel
+ rel->rd_islocaltemp = true; <<<<<<< if this is valid, then the name of field "rd_islocaltemp" is not probably best
I renamed rd_islocaltemp

I don't see any change?

Rename rd_islocaltemp to rd_istemp in global_temporary_table_v8-pg13.patch

In view of commit 6919b7e3294702adc39effd16634b2715d04f012, I think
that this has approximately a 0% chance of being acceptable. If you're
setting a field in a way that is inconsistent with the current use of
the field, you're probably doing it wrong, because the field has an
existing purpose to which new code must conform. And if you're not
doing that, then you don't need to rename it.

Thank you for pointing it out.
I've rolled back the rename.
But I still need rd_localtemp to be true, The reason is that
1 GTT The GTT needs to support DML in read-only transactions ,like local temp table.
2 GTT does not need to hold the lock before modifying the index buffer ,also like local temp table.

Please give me feedback.

maybe some like

rel->rd_globaltemp = true;

and somewhere else

if (rel->rd_localtemp || rel->rd_globaltemp)
{
...
}

I tried to optimize code in global_temporary_table_v10-pg13.patch

Please give me feedback.

Wenjing

Show quoted text

Wenjing

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;
The Enterprise PostgreSQL Company

Attachments:

global_temporary_table_v10-pg13.patchapplication/octet-stream; name=global_temporary_table_v10-pg13.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 79430d2..babb5f3 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -158,6 +158,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * For global temp table only
+	 * use ShareUpdateExclusiveLock for ensure safety
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temp table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},	
 	/* list terminator */
 	{{NULL}}
 };
@@ -1486,6 +1499,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1586,13 +1601,18 @@ build_reloptions(Datum reloptions, bool validate,
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	/*
 	 * There are no options for partitioned tables yet, but this is able to do
 	 * some validation.
 	 */
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index dd975b1..1610e7d 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1013,7 +1013,9 @@ gistGetFakeLSN(Relation rel)
 {
 	static XLogRecPtr counter = FirstNormalUnloggedLSN;
 
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	/* global temp is same as local temp table */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 4871b7f..16b00c9 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -149,7 +149,9 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	/* global temp table is same as local temp table */
+	if (!(index->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		index->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 3fa4b76..b54882d 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -598,7 +598,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -651,7 +651,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 8ce5011..0d6ae76 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -62,6 +62,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -399,9 +400,9 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
 	Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
-	/* not every AM requires these to be valid, but heap does */
-	Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
-	Assert(MultiXactIdIsValid(onerel->rd_rel->relminmxid));
+	/* not every AM requires these to be valid, but regular heap does */
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ MultiXactIdIsValid(onerel->rd_rel->relminmxid));
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index f05cbe7..946c9d2 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -763,7 +763,14 @@ _bt_getbuf(Relation rel, BlockNumber blkno, int access)
 		/* Read an existing block of the relation */
 		buf = ReadBuffer(rel, blkno);
 		LockBuffer(buf, access);
-		_bt_checkpage(rel, buf);
+
+		/* global temp table may be not yet initialized for this backend. */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			blkno == BTREE_METAPAGE &&
+			PageIsNew(BufferGetPage(buf)))
+			_bt_initmetapage(BufferGetPage(buf), P_NONE, 0);
+		else
+			_bt_checkpage(rel, buf);
 	}
 	else
 	{
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 3813ead..fd731d8 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6325,6 +6325,10 @@ StartupXLOG(void)
 	else
 		recoveryTargetTLI = ControlFile->checkPointCopy.ThisTimeLineID;
 
+	/* clean temp relation files */
+	if (max_active_gtt > 0)
+		RemovePgTempFiles();
+
 	/*
 	 * Check for signal files, and if so set up state for offline recovery
 	 */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index f8f0b48..dc8bbb1 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -42,6 +42,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 7d6acae..3be8d63 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -393,7 +393,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/* global temp table is same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 046b3d3..8ca7268 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -404,6 +406,10 @@ heap_create(const char *relname,
 									 relpersistence,
 									 relkind);
 
+	/* global temp table not create storage file when catalog create */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		create_storage = false;
+
 	/*
 	 * Have the storage manager create the relation's disk file, if needed.
 	 *
@@ -427,7 +433,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -957,6 +963,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -995,8 +1002,18 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/* global temp table not remember transaction info in catalog */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1358,6 +1375,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1442,11 +1460,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
@@ -1938,6 +1960,13 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/* We allow to drop global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(rel->rd_node))
+			elog(ERROR, "can not drop relation when other backend attached this global temp table");
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3165,7 +3194,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3176,8 +3205,12 @@ RelationTruncateIndexes(Relation heapRelation)
 		Relation	currentIndex;
 		IndexInfo  *indexInfo;
 
+		if (RELATION_IS_GLOBAL_TEMP(heapRelation) &&
+			!gtt_storage_attached(indexId))
+			continue;
+
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3223,8 +3256,13 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		/* truncate global temp table only need RowExclusiveLock */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3257,6 +3295,8 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
+	bool		truncate_toastrel = false;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3265,23 +3305,40 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	toastrelid = rel->rd_rel->reltoastrelid;
+
+	/*
+	 * Truncate global temp table only need RowExclusiveLock
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		lockmode = RowExclusiveLock;
+		if (OidIsValid(toastrelid) &&
+			gtt_storage_attached(toastrelid))
+			truncate_toastrel = true;
+	}
+	else if (OidIsValid(toastrelid))
+		truncate_toastrel = true;
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
-	toastrelid = rel->rd_rel->reltoastrelid;
-	if (OidIsValid(toastrelid))
+	if (truncate_toastrel)
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(rel, 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 8880586..0852073 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -52,6 +52,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -877,6 +878,22 @@ index_create(Relation heapRelation,
 						indexRelationName, RelationGetRelationName(heapRelation))));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		if (accessMethodObjectId != BTREE_AM_OID)
+			elog(ERROR, "only support btree index on global temp table");
+
+		/* No support create index on global temp table use concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot create indexes on global temporary tables using concurrent mode")));
+
+		/* if global temp table not init storage, then skip build index */
+		if (!gtt_storage_attached(heapRelation->rd_node.relNode))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -2054,6 +2071,13 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/* We allow to drop index on global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation))
+	{
+		if (is_other_backend_use_gtt(userHeapRelation->rd_node))
+			elog(ERROR, "can not drop index when other backend attached this global temp table.");
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2660,6 +2684,11 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	/* update index stats into localhash and rel_rd_rel for global temp table */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		is_gtt = true;
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2745,21 +2774,35 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
+		if (is_gtt)
+			rel->rd_rel->relpages = (int32) relpages;
+		else if (rd_rel->relpages != (int32) relpages)
 		{
 			rd_rel->relpages = (int32) relpages;
 			dirty = true;
 		}
-		if (rd_rel->reltuples != (float4) reltuples)
+
+		if (is_gtt)
+			rel->rd_rel->reltuples = (float4) reltuples;
+		else if (rd_rel->reltuples != (float4) reltuples)
 		{
 			rd_rel->reltuples = (float4) reltuples;
 			dirty = true;
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+
+		if (is_gtt)
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+		else if (rd_rel->relallvisible != (int32) relallvisible)
 		{
 			rd_rel->relallvisible = (int32) relallvisible;
 			dirty = true;
 		}
+
+		if (is_gtt)
+		{
+			up_gtt_relstats(rel, relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
+		}
 	}
 
 	/*
@@ -2873,6 +2916,15 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, index, val);
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		if (!gtt_storage_attached(indexRelation->rd_node.relNode))
+		{
+			gtt_force_enable_index(indexRelation);
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3468,6 +3520,15 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 				 errmsg("cannot reindex temporary tables of other sessions")));
 
 	/*
+	 * Because global temp table cannot change relfilenode
+	 * no support reindex on global temp table yet.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(iRel))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot reindex global temporary tables")));
+
+	/*
 	 * Also check for active uses of the index in the current transaction; we
 	 * don't want to reindex underneath an open indexscan.
 	 */
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index e70243a..301da79 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -647,6 +647,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		/* global temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp relations in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index fddfbf1..671c614 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -26,6 +26,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -75,7 +76,7 @@ static PendingRelDelete *pendingDeletes = NULL; /* head of linked list */
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -85,6 +86,8 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		/* global temp table use same storage strategy as local temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -117,6 +120,10 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/* remember global temp table storage info to localhash */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel)
+		remember_gtt_storage_info(rnode, rel);
+
 	return srel;
 }
 
@@ -486,8 +493,15 @@ smgrDoPendingDeletes(bool isCommit)
 		smgrdounlinkall(srels, nrels, false);
 
 		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* clean global temp table flags when transaction commit or rollback */
+			if (SmgrIsTemp(srels[i]) && 
+				gtt_storage_attached(srels[i]->smgr_rnode.node.relNode))
+				forget_gtt_storage_info(srels[i]->smgr_rnode.node.relNode);
+		}
+
 		pfree(srels);
 	}
 }
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..71be416
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1174 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  code to create and destroy physical storage for global temparary table
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/table.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_relstats_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+typedef struct gtt_ctl_data
+{
+	LWLock			lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct
+{
+	RelFileNode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+	Oid			relid;
+
+	Oid			spcnode;
+	/* pg_class stat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+	char		relkind;
+	bool		on_commit_delete;
+
+	/* pg_statistic */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(RelFileNode rnode);
+static void gtt_storage_checkout(RelFileNode rnode, bool skiplock);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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;
+	int			natts = 0;
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_gtt to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temp table yet");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create stroage file", RelationGetRelationName(rel));
+
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		/* First time through: initialize the hash table */
+		HASHCTL		ctl;
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		gtt_storage_local_hash =
+			hash_create("global temp relation table",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_relstats_context =
+			AllocSetContextCreate(CacheMemoryContext,
+							"gtt relstats context",
+							ALLOCSET_DEFAULT_SIZES);
+	}
+
+	/* Look up or create an entry */
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_ENTER, &found);
+
+	if (found)
+	{
+		elog(ERROR, "backend %d relid %u already exists in global temp table local hash",
+					MyBackendId, relid);
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+	entry->spcnode = rnode.spcNode;
+	entry->relpages = 0;
+	entry->reltuples = 0;
+	entry->relallvisible = 0;
+	entry->relkind = rel->rd_rel->relkind;
+	natts = RelationGetNumberOfAttributes(rel);
+	entry->natts = natts;
+	entry->attnum = palloc0(sizeof(int) * natts);
+	entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+	/* only heap contain transaction information */
+	if (entry->relkind == RELKIND_RELATION ||
+		entry->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+		{
+			entry->on_commit_delete = true;
+			register_on_commit_action(rel->rd_node.relNode, ONCOMMIT_DELETE_ROWS);
+		}
+
+		entry->relfrozenxid = RecentXmin;
+		entry->relminmxid = GetOldestMultiXactId();
+		insert_gtt_relfrozenxid_to_ordered_list(entry->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+		gtt_storage_checkin(rnode);
+	}
+	else
+	{
+		entry->on_commit_delete = false;
+		entry->relfrozenxid = 0;
+		entry->relminmxid = 0;
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+void
+forget_gtt_storage_info(Oid relid)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry)
+	{
+		int		i;
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			RelFileNode rnode;
+
+			rnode.spcNode = entry->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = entry->relid;
+
+			gtt_storage_checkout(rnode, false);
+
+			Assert(TransactionIdIsNormal(entry->relfrozenxid));
+			remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+
+		for (i = 0; i < entry->natts; i++)
+		{
+			if (entry->att_stat_tups[i])
+			{
+				heap_freetuple(entry->att_stat_tups[i]);
+				entry->att_stat_tups[i] = NULL;
+			}
+		}
+
+		pfree(entry->attnum);
+		pfree(entry->att_stat_tups);
+	}
+
+	hash_search(gtt_storage_local_hash,
+			(void *) &(relid), HASH_REMOVE, NULL);
+
+	return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool found;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (gtt_storage_local_hash == NULL)
+		return false;
+
+	hash_search(gtt_storage_local_hash,
+			(void *) &(relid), HASH_FIND, &found);
+
+	return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS				status;
+	gtt_local_hash_entry		*entry;
+	int			nrels = 0,
+				maxrels = 0;
+	SMgrRelation	*srels = NULL;
+	RelFileNode		*rnodes = NULL;
+	char			*relkinds = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		SMgrRelation srel;
+		RelFileNode rnode;
+
+		rnode.spcNode = entry->spcnode;
+		rnode.dbNode = MyDatabaseId;
+		rnode.relNode = entry->relid;
+
+		srel = smgropen(rnode, MyBackendId);
+
+		/* allocate the initial array, or extend it, if needed */
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			srels = palloc(sizeof(SMgrRelation) * maxrels);
+			rnodes = palloc(sizeof(RelFileNode) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+			rnodes = repalloc(rnodes, sizeof(RelFileNode) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		srels[nrels] = srel;
+		rnodes[nrels] = rnode;
+		relkinds[nrels] = entry->relkind;
+		nrels++;
+	}
+
+	if (nrels > 0)
+	{
+		int i;
+
+		smgrdounlinkall(srels, nrels, false);
+
+		LWLockAcquire(&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.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (gtt_storage_local_hash == NULL)
+		return false;
+
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_FIND, &found);
+
+	if (!found)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	if (relpages)
+		*relpages = entry->relpages;
+
+	if (reltuples)
+		*reltuples = entry->reltuples;
+
+	if (relallvisible)
+		*relallvisible = entry->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = entry->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = entry->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	MemoryContext			oldcontext;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+						(void *) &reloid, HASH_FIND, &found);
+
+	if (!found)
+		return;
+
+	/* todo */
+	if (entry->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	Assert(entry->relid == reloid);
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == 0)
+		{
+			entry->attnum[i] = attnum;
+			break;
+		}
+		else if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < entry->natts);
+	Assert(entry->att_stat_tups[i] == NULL);
+	entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+						(void *) &reloid, HASH_FIND, &found);
+
+	if (!found)
+		return NULL;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			return entry->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int			i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	HeapTuple	tuple;
+	Relation	rel = NULL;
+	int		attnum = PG_GETARG_INT32(1);
+	Oid		reloid = PG_GETARG_OID(0);
+	char		rel_persistence;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	  tupdesc;
+	MemoryContext oldcontext;
+	Tuplestorestate *tupstore;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = table_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+
+	table_close(rel, NoLock);
+	table_close(pg_tatistic, AccessShareLock);
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate	*tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Relation	rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	if (get_gtt_relstats(reloid,
+						&relpages, &reltuples, &relallvisible,
+						&relfrozenxid, &relminmxid))
+	{
+		Datum	values[5];
+		bool	isnull[5];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = Int32GetDatum(relpages);
+		values[1] = Float4GetDatum((float4)reltuples);
+		values[2] = Int32GetDatum(relallvisible);
+		values[3] = UInt32GetDatum(relfrozenxid);
+		values[4] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+
+	tuplestore_donestoring(tupstore);
+	table_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(rel->rd_node);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	table_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(indexOid));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid relOid = index->rd_index->indrelid;
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	if (gtt_storage_attached(relOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index c9e6060..d814ff2 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM 
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c4420dd..977a984 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -586,14 +587,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 		 * pg_statistic for columns we didn't process, we leave them alone.)
 		 */
 		update_attstats(RelationGetRelid(onerel), inh,
-						attr_cnt, vacattrstats);
+						attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
 
 		for (ind = 0; ind < nindexes; ind++)
 		{
 			AnlIndexData *thisdata = &indexdata[ind];
 
 			update_attstats(RelationGetRelid(Irel[ind]), false,
-							thisdata->attr_cnt, thisdata->vacattrstats);
+							thisdata->attr_cnt, thisdata->vacattrstats,
+							RelationGetRelPersistence(Irel[ind]));
 		}
 
 		/*
@@ -1456,7 +1458,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
  *		by taking a self-exclusive lock on the relation in analyze_rel().
  */
 static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
 {
 	Relation	sd;
 	int			attno;
@@ -1558,31 +1560,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/* Update column statistic to localhash, not catalog */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index e9d7a7f..3088e26 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -391,6 +391,12 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/* not support cluster global temp table yet */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("not support cluster global temporary tables yet")));
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 40a8ec1..5348458 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1062,7 +1062,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, stmt->filename, stmt->is_program,
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index ec20ba3..a29cc00 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2614,6 +2614,16 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 			!isTempNamespace(classtuple->relnamespace))
 			continue;
 
+		/* not support reindex on global temp table, so skip it */
+		if (classtuple->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			ereport(WARNING,
+				(errmsg("global temp table \"%s.%s\" skip reindexed",
+					get_namespace_name(get_rel_namespace(relid)),
+					get_rel_name(relid))));
+			continue;
+		}
+
 		/* Check user/system classification, and optionally skip */
 		if (objectKind == REINDEX_OBJECT_SYSTEM &&
 			!IsSystemClass(relid, classtuple))
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 329ab84..dfa257c 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -96,7 +96,8 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 	 * transaction.
 	 */
 	relpersistence = get_rel_persistence(relid);
-	if (relpersistence == RELPERSISTENCE_TEMP)
+	if (relpersistence == RELPERSISTENCE_TEMP ||
+		relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
 
 	/* Check permissions. */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..c55dcf7 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -94,7 +94,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +108,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -222,7 +223,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* now initialize the sequence's data */
 	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -327,7 +328,7 @@ ResetSequence(Oid seq_relid)
 	/*
 	 * Insert the modified tuple into the new storage file.
 	 */
-	fill_seq_with_data(seq_rel, tuple);
+	fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -340,17 +341,21 @@ ResetSequence(Oid seq_relid)
  * Initialize a sequence's relation with the specified tuple as content
  */
 static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 	sequence_magic *sm;
 	OffsetNumber offnum;
+	bool lockBuffer = false;
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ReadBuffer(rel, P_NEW);
-	Assert(BufferGetBlockNumber(buf) == 0);
+	if (buf == InvalidBuffer)
+	{
+		buf = ReadBuffer(rel, P_NEW);
+		Assert(BufferGetBlockNumber(buf) == 0);
+		lockBuffer = true;
+	}
 
 	page = BufferGetPage(buf);
 
@@ -360,7 +365,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* Now insert sequence tuple */
 
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+	if (lockBuffer)
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +416,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	END_CRIT_SECTION();
 
-	UnlockReleaseBuffer(buf);
+	if (lockBuffer)
+		UnlockReleaseBuffer(buf);
 }
 
 /*
@@ -502,7 +509,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * Insert the modified tuple into the new storage file.
 		 */
-		fill_seq_with_data(seqrel, newdatatuple);
+		fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
 	}
 
 	/* process OWNED BY if given */
@@ -611,7 +618,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +943,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1178,6 +1185,25 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
 	LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
 
 	page = BufferGetPage(*buf);
+	if (GlobalTempRelationPageIsNotInitialized(rel, page))
+	{
+		/* Initialize sequence for global temporary tables */
+		Datum		value[SEQ_COL_LASTCOL] = {0};
+		bool		null[SEQ_COL_LASTCOL] = {false};
+		HeapTuple	tuple;
+		int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+		/*
+		 * last_value from pg_sequence.seqstart
+		 * log_cnt = 0
+		 * is_called = false
+		 */
+		value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+		tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+		fill_seq_with_data(rel, tuple, *buf);
+		heap_freetuple(tuple);
+	}
 	sm = (sequence_magic *) PageGetSpecialPointer(page);
 
 	if (sm->magic != SEQ_MAGIC)
@@ -1954,3 +1980,23 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f599393..331f028 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -553,6 +554,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 								  Relation partitionTbl);
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static bool has_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -598,6 +600,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
@@ -608,8 +611,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Check consistency of arguments
 	 */
+	/* global temp table same as local temp table */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !(stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+			 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -639,7 +644,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	/* global temp table same as local temp table */
+	if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+		 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -740,6 +747,57 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* global temp table */
+	has_oncommit_clause = has_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* inherit table or parition table inherit on commit property from parent table*/
+		if (inheritOids && stmt->oncommit == ONCOMMIT_NOOP && !has_oncommit_clause)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			if (RELATION_GTT_ON_COMMIT_DELETE(relation))
+				stmt->oncommit = ONCOMMIT_DELETE_ROWS;
+			else
+				stmt->oncommit = ONCOMMIT_PRESERVE_ROWS;
+			table_close(relation, NoLock);
+		}
+
+		if (has_oncommit_clause)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "can not defeine global temp table with on commit and with clause at same time");
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temp table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (has_oncommit_clause)
+		elog(ERROR, "regular table cannot specifie on_commit_delete_rows");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1824,7 +1882,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);
@@ -3392,6 +3451,13 @@ RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal, bo
 	 * specially.
 	 */
 	targetrelation = relation_open(myrelid, is_index ? ShareUpdateExclusiveLock : AccessExclusiveLock);
+
+	if (RELATION_IS_GLOBAL_TEMP(targetrelation))
+	{
+		if (is_other_backend_use_gtt(targetrelation->rd_node))
+			elog(ERROR, "can not rename relation when other backend attached this global temp table");
+	}
+
 	namespaceId = RelationGetNamespace(targetrelation);
 
 	/*
@@ -3567,6 +3633,13 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(rel->rd_node))
+			elog(ERROR, "can not alter relation when other backend attached this global temp table");
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -8140,6 +8213,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),
@@ -8179,6 +8259,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		/* 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;
 	}
 
 	/*
@@ -13215,7 +13301,7 @@ index_copy_data(Relation rel, RelFileNode newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
@@ -14622,7 +14708,9 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
 	 */
 	switch (rel->rd_rel->relpersistence)
 	{
+		/* global temp table same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -17263,3 +17351,20 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
 		table_close(rel, NoLock);
 	}
 }
+
+static bool
+has_oncommit_option(List *options)
+{
+	ListCell   *listptr;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (pg_strcasecmp(def->defname, "on_commit_delete_rows") == 0)
+			return true;
+	}
+
+	return false;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index d625d17..69ad24f 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/vacuum.h"
@@ -1217,6 +1218,17 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	 /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		is_gtt = true;
+		up_gtt_relstats(relation,
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1230,17 +1242,26 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relpages = (int32) num_pages;
+	else if (pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (is_gtt)
+		relation->rd_rel->reltuples = (float4) num_tuples;
+	else if (pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	else if (pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1285,7 +1306,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1296,7 +1318,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1394,6 +1417,10 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/* global temp table relstats not in pg_class */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1451,6 +1478,25 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/*
+	 * Global temp table get frozenxid from MyProc
+	 * to avoid the vacuum truncate clog that gtt need.
+	 */
+	if (max_active_gtt > 0)
+	{
+		TransactionId oldest_gtt_frozenxid =
+			list_all_session_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid) &&
+			TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+		{
+			ereport(WARNING,
+				(errmsg("global temp table oldest FrozenXid is far in the past"),
+				 errhint("please truncate them or kill those sessions that use them.")));
+			newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index ee5c3a6..0d7a2d4 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandTag((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 8286d9c..c3992a4 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -591,6 +591,8 @@ static void
 set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 						  RangeTblEntry *rte)
 {
+	char relpersistence;
+
 	/*
 	 * The flag has previously been initialized to false, so we can just
 	 * return if it becomes clear that we can't safely set it.
@@ -618,7 +620,11 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			/* global temp table is same as local temp table */
+			relpersistence = get_rel_persistence(rte->relid);
+
+			if (relpersistence == RELPERSISTENCE_TEMP ||
+				relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index d6f2153..310a9e2 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6312,7 +6312,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
+	/* global temp table is same as local temp table */
 	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 748bebf..a5ddbcd 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2579,6 +2579,11 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("materialized views must not use temporary tables or views")));
 
+		if (is_query_using_gtt(query))
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("materialized views must not use global temporary tables or views")));
+
 		/*
 		 * A materialized view would either need to save parameters for use in
 		 * maintaining/loading the data or prohibit them entirely.  The latter
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 1b0edf5..492639f 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3290,17 +3290,11 @@ OptTemp:	TEMPORARY					{ $$ = RELPERSISTENCE_TEMP; }
 			| LOCAL TEMP				{ $$ = RELPERSISTENCE_TEMP; }
 			| GLOBAL TEMPORARY
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
-					$$ = RELPERSISTENCE_TEMP;
+					$$ = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
-					$$ = RELPERSISTENCE_TEMP;
+					$$ = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED					{ $$ = RELPERSISTENCE_UNLOGGED; }
 			| /*EMPTY*/					{ $$ = RELPERSISTENCE_PERMANENT; }
@@ -11591,19 +11585,13 @@ OptTempTableName:
 				}
 			| GLOBAL TEMPORARY opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED opt_table qualified_name
 				{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b875a50..123945b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index ee2d2b5..9c9abaa 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 6d1f28c..ed837d1 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2086,6 +2086,11 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* autovacuum skip vacuum global temp table */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2152,7 +2157,9 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		/* autovacuum skip vacuum global temp table */
+		if (classForm->relpersistence == RELPERSISTENCE_TEMP || 
+			classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index aba3960..3c4b96c 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -52,6 +53,7 @@
 #include "utils/rel.h"
 #include "utils/resowner_private.h"
 #include "utils/timestamp.h"
+#include "utils/guc.h"
 
 
 /* Note: these two macros only work on shared buffers, not local ones! */
@@ -432,7 +434,7 @@ ForgetPrivateRefCountEntry(PrivateRefCountEntry *ref)
 static Buffer ReadBuffer_common(SMgrRelation reln, char relpersistence,
 								ForkNumber forkNum, BlockNumber blockNum,
 								ReadBufferMode mode, BufferAccessStrategy strategy,
-								bool *hit);
+								bool *hit, Relation rel);
 static bool PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy);
 static void PinBuffer_Locked(BufferDesc *buf);
 static void UnpinBuffer(BufferDesc *buf, bool fixOwner);
@@ -664,7 +666,8 @@ ReadBufferExtended(Relation reln, ForkNumber forkNum, BlockNumber blockNum,
 	 */
 	pgstat_count_buffer_read(reln);
 	buf = ReadBuffer_common(reln->rd_smgr, reln->rd_rel->relpersistence,
-							forkNum, blockNum, mode, strategy, &hit);
+							forkNum, blockNum, mode, strategy, &hit,
+							reln);
 	if (hit)
 		pgstat_count_buffer_hit(reln);
 	return buf;
@@ -692,7 +695,7 @@ ReadBufferWithoutRelcache(RelFileNode rnode, ForkNumber forkNum,
 	Assert(InRecovery);
 
 	return ReadBuffer_common(smgr, RELPERSISTENCE_PERMANENT, forkNum, blockNum,
-							 mode, strategy, &hit);
+							 mode, strategy, &hit, NULL);
 }
 
 
@@ -704,7 +707,8 @@ ReadBufferWithoutRelcache(RelFileNode rnode, ForkNumber forkNum,
 static Buffer
 ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 				  BlockNumber blockNum, ReadBufferMode mode,
-				  BufferAccessStrategy strategy, bool *hit)
+				  BufferAccessStrategy strategy, bool *hit,
+				  Relation rel)
 {
 	BufferDesc *bufHdr;
 	Block		bufBlock;
@@ -719,6 +723,15 @@ ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 
 	isExtend = (blockNum == P_NEW);
 
+	/* create storage when first read data page for gtt */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(isExtend || blockNum == 0) &&
+		forkNum == MAIN_FORKNUM &&
+		!gtt_storage_attached(smgr->smgr_rnode.node.relNode))
+	{
+		RelationCreateStorage(smgr->smgr_rnode.node, relpersistence, rel);
+	}
+
 	TRACE_POSTGRESQL_BUFFER_READ_START(forkNum, blockNum,
 									   smgr->smgr_rnode.node.spcNode,
 									   smgr->smgr_rnode.node.dbNode,
@@ -2809,6 +2822,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(relation->rd_node.relNode))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d5..7adb969 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -21,6 +21,7 @@
 #include "access/nbtree.h"
 #include "access/subtrans.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, active_gtt_shared_hash_size());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* global temporary table */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 4a5b26c..9754168 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -62,6 +62,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -4087,3 +4088,77 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temp table.
+ */
+int
+list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct *arrayP = procArray;
+	TransactionId result = InvalidTransactionId;
+	int			index;
+	int			flags = 0;
+	int			i = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+		*n = 0;
+	}
+
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		volatile PGXACT *pgxact = &allPgXact[pgprocno];
+
+		if (pgxact->vacuumFlags & flags)
+			continue;
+
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->session_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->session_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->session_gtt_frozenxid, result))
+				result = proc->session_gtt_frozenxid;
+
+			if (max_size > 0)
+			{
+				pids[i] = proc->pid;
+				xids[i] = proc->session_gtt_frozenxid;
+				i++;
+			}
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (max_size > 0)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 32df8c8..e4e1125 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c
index c5b771c..e7725ce 100644
--- a/src/backend/storage/smgr/md.c
+++ b/src/backend/storage/smgr/md.c
@@ -646,6 +646,12 @@ mdread(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
 		 */
 		if (zero_damaged_pages || InRecovery)
 			MemSet(buffer, 0, BLCKSZ);
+		else if(SmgrIsTemp(reln) && blocknum == 0 && forknum == MAIN_FORKNUM)
+		{
+			/* global temp table init btree meta page */
+			MemSet(buffer, 0, BLCKSZ);
+			mdwrite(reln, forknum, blocknum, buffer, true);
+		}
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_DATA_CORRUPTED),
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 8406644..0d99321 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -1008,6 +1008,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 7c6f057..eb10cf2 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -112,6 +112,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -4578,12 +4579,25 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -4708,15 +4722,27 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6098,6 +6124,7 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	{
 		/* Simple variable --- look to stats for the underlying table */
 		RangeTblEntry *rte = planner_rt_fetch(index->rel->relid, root);
+		char	rel_persistence = get_rel_persistence(rte->relid);
 
 		Assert(rte->rtekind == RTE_RELATION);
 		relid = rte->relid;
@@ -6115,6 +6142,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6126,6 +6160,8 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	}
 	else
 	{
+		char	rel_persistence = get_rel_persistence(index->indexoid);
+
 		/* Expression --- maybe there are stats for the index itself */
 		relid = index->indexoid;
 		colnum = 1;
@@ -6141,6 +6177,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7054,6 +7097,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		/* attempt to lookup stats in relation for this index column */
 		if (attnum != 0)
 		{
+			char	rel_persistence = get_rel_persistence(rte->relid);
+
 			/* Simple variable -- look to stats for the underlying table */
 			if (get_relation_stats_hook &&
 				(*get_relation_stats_hook) (root, rte, attnum, &vardata))
@@ -7066,6 +7111,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7078,6 +7131,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		}
 		else
 		{
+			char	rel_persistence = get_rel_persistence(index->indexoid);
+
 			/*
 			 * Looks like we've found an expression column in the index. Let's
 			 * see if there's any stats for it.
@@ -7097,6 +7152,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index fb0599f..13f4d8a 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -2878,6 +2879,18 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index df025a5..40658bc 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -63,6 +63,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1124,6 +1125,24 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1178,6 +1197,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			gtt_fix_index_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -2217,6 +2237,8 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		gtt_fix_index_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3313,6 +3335,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3427,6 +3453,9 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
 
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+		elog(ERROR, "global temp table does not allow setting new relfilenode");
+
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
@@ -3467,7 +3496,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 a16fe8c..74e77e9 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -140,6 +140,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temp table feature.
+ * global temp table define can still storage in catalog
+ * just can not use.
+ * num > 0 means database can management num active global temp table.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2012,6 +2024,15 @@ static struct config_bool ConfigureNamesBool[] =
 static struct config_int ConfigureNamesInt[] =
 {
 	{
+		{"max_active_global_temporary_table", PGC_POSTMASTER, UNGROUPED,
+			gettext_noop("max active global temporary table."),
+			NULL
+		},
+		&max_active_gtt,
+		DEFAULT_NUM_ACTIVE_GTT, MIN_NUM_ACTIVE_GTT, MAX_NUM_ACTIVE_GTT,
+		NULL, NULL, NULL
+	},
+	{
 		{"archive_timeout", PGC_SIGHUP, WAL_ARCHIVING,
 			gettext_noop("Forces a switch to the next WAL file if a "
 						 "new file has not been started within N seconds."),
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ec3e2c6..d3697d2 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15583,6 +15583,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	{
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
+		char		*table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15634,9 +15635,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 			binary_upgrade_set_pg_class_oids(fout, q,
 											 tbinfo->dobj.catId.oid, false);
 
+		if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED)
+			table_type = "UNLOGGED ";
+		else if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			table_type = "GLOBAL TEMPORARY ";
+		else
+			table_type = "";
+
 		appendPQExpBuffer(q, "CREATE %s%s %s",
-						  tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ?
-						  "UNLOGGED " : "",
+						  table_type,
 						  reltypename,
 						  qualrelname);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index f3c7eb9..28134e2 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3723,7 +3723,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index dc03fbd..8bd6d09 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1016,6 +1016,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2378,6 +2380,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2586,6 +2591,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index a12fc1f..78958e4 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -165,6 +165,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 2228256..4d5d13a 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5504,6 +5504,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4191',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4192',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{relid,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4193',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4194',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 048003c..af48cdf 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -19,7 +19,7 @@
 #include "storage/smgr.h"
 #include "utils/relcache.h"
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationTruncate(Relation rel, BlockNumber nblocks);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..aa80cb5
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,41 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "utils/relcache.h"
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid);
+extern bool is_other_backend_use_gtt(RelFileNode node);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(RelFileNode node);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Relation relation,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void gtt_force_enable_index(Relation index);
+extern void gtt_fix_index_state(Relation index);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 870ecb5..92c590e 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 8fda8e4..172eae9 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_TBM,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_SXACT,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index d217801..8adde87 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -117,6 +117,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId session_gtt_frozenxid;	/* session level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ce93ace..0f7262e 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -281,6 +281,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 44ed04d..6b6d2da 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	struct SMgrRelationData *rd_smgr;	/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -277,6 +277,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -535,11 +536,13 @@ typedef struct ViewOptions
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -547,6 +550,7 @@ typedef struct ViewOptions
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -559,6 +563,14 @@ typedef struct ViewOptions
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is either local temp relation of this session
+ * 		or global temp relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -602,6 +614,17 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..50ca9ac
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,7 @@
+reset search_path;
+drop schema gtt cascade;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..e21e540
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,282 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+ERROR:  cannot create indexes on global temporary tables using concurrent mode
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+ERROR:  not support cluster global temporary tables yet
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ok
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  regular table cannot specifie on_commit_delete_rows
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  can not defeine global temp table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test;
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  referenced relation "products" is not a global temp table
+-- ERROR
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  referenced relation "products" is not a global temp table
+-- ERROR
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ALL ERROR
+create index idx_err on gtt1 using hash (a);
+ERROR:  only support btree index on global temp table
+create index idx_err on gtt1 using gist (a);
+ERROR:  data type integer has no default operator class for access method "gist"
+HINT:  You must specify an operator class for the index or define a default operator class for the data type.
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+ERROR:  only support btree index on global temp table
+create index idx_tmp_t0_1 on tmp_t0 using gist (c0);
+ERROR:  only support btree index on global temp table
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain select * from gt1 where a=1;
+                                QUERY PLAN                                 
+---------------------------------------------------------------------------
+ Bitmap Heap Scan on gt1  (cost=12.17..482.50 rows=500 width=4)
+   Recheck Cond: (a = 1)
+   ->  Bitmap Index Scan on idx_gt1_1  (cost=0.00..12.04 rows=500 width=0)
+         Index Cond: (a = 1)
+(4 rows)
+
+explain select * from gt1 where a=200000;
+                                QUERY PLAN                                 
+---------------------------------------------------------------------------
+ Bitmap Heap Scan on gt1  (cost=12.17..482.50 rows=500 width=4)
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_gt1_1  (cost=0.00..12.04 rows=500 width=0)
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain select * from gt1 where a*10=300;
+                                QUERY PLAN                                 
+---------------------------------------------------------------------------
+ Bitmap Heap Scan on gt1  (cost=12.29..483.88 rows=500 width=4)
+   Recheck Cond: ((a * 10) = 300)
+   ->  Bitmap Index Scan on idx_gt1_3  (cost=0.00..12.17 rows=500 width=0)
+         Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain select * from gt1 where a*10=3;
+                                QUERY PLAN                                 
+---------------------------------------------------------------------------
+ Bitmap Heap Scan on gt1  (cost=12.29..483.88 rows=500 width=4)
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_gt1_3  (cost=0.00..12.17 rows=500 width=0)
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain select * from gt1 where a=1;
+                                QUERY PLAN                                
+--------------------------------------------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1  (cost=0.29..8.31 rows=1 width=4)
+   Index Cond: (a = 1)
+(2 rows)
+
+explain select * from gt1 where a=200000;
+                                QUERY PLAN                                
+--------------------------------------------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1  (cost=0.29..8.31 rows=1 width=4)
+   Index Cond: (a = 200000)
+(2 rows)
+
+explain select * from gt1 where a*10=300;
+                             QUERY PLAN                              
+---------------------------------------------------------------------
+ Index Scan using idx_gt1_3 on gt1  (cost=0.42..8.44 rows=1 width=4)
+   Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain select * from gt1 where a*10=3;
+                             QUERY PLAN                              
+---------------------------------------------------------------------
+ Index Scan using idx_gt1_3 on gt1  (cost=0.42..8.44 rows=1 width=4)
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 17 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to table gtt_function.gt1
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..30d8a7b
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,84 @@
+set search_path=gtt,sys;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..850ef3e
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,161 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          475136 |                 974848
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |           409600 |        409600 |                 409600
+(2 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..9fe5fd4
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,9 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..b258b7c
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,76 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+(1 row)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+(1 row)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2ab2115..4f4eb46 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1349,6 +1349,93 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d2b17dd..80e577f 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..f3cf710
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,6 @@
+
+
+reset search_path;
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..784c537
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,187 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ok
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test;
+
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ERROR
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ERROR
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ALL ERROR
+create index idx_err on gtt1 using hash (a);
+create index idx_err on gtt1 using gist (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_1 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain select * from gt1 where a=1;
+explain select * from gt1 where a=200000;
+explain select * from gt1 where a*10=300;
+explain select * from gt1 where a*10=3;
+analyze gt1;
+explain select * from gt1 where a=1;
+explain select * from gt1 where a=200000;
+explain select * from gt1 where a*10=300;
+explain select * from gt1 where a*10=3;
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d7d81de
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,42 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..5203c2b
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,76 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..2f4d883
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,16 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..f041892
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,42 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
#120曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Konstantin Knizhnik (#91)
Re: [Proposal] Global temporary tables

2020年1月27日 下午5:38,Konstantin Knizhnik <k.knizhnik@postgrespro.ru> 写道:

On 25.01.2020 18:15, 曾文旌(义从) wrote:

I wonder why do we need some special check for GTT here.

From my point of view cleanup at startup of local storage of temp tables should be performed in the same way for local and global temp tables.

After oom kill, In autovacuum, the Isolated local temp table will be cleaned like orphan temporary tables. The definition of local temp table is deleted with the storage file.
But GTT can not do that. So we have the this implementation in my patch.
If you have other solutions, please let me know.

I wonder if it is possible that autovacuum or some other Postgres process is killed by OOM and postmaster is not noticing it can doens't restart Postgres instance?
as far as I know, crash of any process connected to Postgres shared memory (and autovacuum definitely has such connection) cause Postgres restart.

Postmaster will not restart after oom happen, but the startup process will. GTT data files are cleaned up in the startup process.

In my design
1 Because different sessions have different transaction information, I choose to store the transaction information of GTT in MyProc,not catalog.
2 About the XID wraparound problem, the reason is the design of the temp table storage(local temp table and global temp table) that makes it can not to do vacuum by autovacuum.
It should be completely solve at the storage level.

My point of view is that vacuuming of temp tables is common problem for local and global temp tables.
So it has to be addressed in the common way and so we should not try to fix this problem only for GTT.

I think I agree with you this point.
However, this does not mean that GTT transaction information stored in pg_class is correct.
If you keep it that way, like in global_private_temp-8.patch, It may cause data loss in GTT after aotuvauum.

In fact, The dba can still complete the DDL of the GTT.
I've provided a set of functions for this case.
If the dba needs to modify a GTT A(or drop GTT or create index on GTT), he needs to do:
1 Use the pg_gtt_attached_pids view to list the pids for the session that is using the GTT A.
2 Use pg_terminate_backend(pid)terminate they except itself.
3 Do alter GTT A.

IMHO forced terminated of client sessions is not acceptable solution.
And it is not an absolutely necessary requirement.
So from my point of view we should not add such limitations to GTT design.

This limitation makes it possible for the GTT to do all the DDL.
IMHO even oracle's GTT has similar limitations.

What are the reasons of using RowExclusiveLock for GTT instead of AccessExclusiveLock?
Yes, GTT data is access only by one backend so no locking here seems to be needed at all.
But I wonder what are the motivations/benefits of using weaker lock level here?

1 Truncate GTT deletes only the data in the session, so no need use high-level lock.
2 I think it still needs to be block by DDL of GTT, which is why I use RowExclusiveLock.

Sorry, I do not understand your arguments: we do not need exclusive lock because we drop only local (private) data
but we need some kind of lock. I agree with 1) and not 2).

Yes, we don't need lock for private data, but metadata need.

There should be no conflicts in any case...

+        /* We allow to create index on global temp table only this session use it */
+        if (is_other_backend_use_gtt(heapRelation->rd_node))
+            elog(ERROR, "can not create index when have other backend attached this global temp table");
+

The same argument as in case of dropping GTT: I do not think that prohibiting DLL operations on GTT used by more than one backend is bad idea.

The idea was to give the GTT almost all the features of a regular table with few code changes.
The current version DBA can still do all DDL for GTT, I've already described.

I absolutely agree with you that GTT should be given the same features as regular tables.
The irony is that this most natural and convenient behavior is most easy to implement without putting some extra restrictions.
Just let indexes for GTT be constructed on demand. It it can be done using the same function used for regular index creation.

The limitation on index creation have been improved in global_temporary_table_v10-pg13.patch.

+    /* 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))));
+

Why do we need to prohibit foreign key constraint on GTT?

It may be possible to support FK on GTT in later versions. Before that, I need to check some code.

Ok, may be approach to prohibit everything except minimally required functionality is safe and reliable.
But frankly speaking I prefer different approach: if I do not see any contradictions of new feature with existed operations
and it is passing tests, then we should not prohibit this operations for new feature.

I have already described my point in previous emails.

1. The core problem is that the data contains transaction information (xid), which needs to be vacuum(freeze) regularly to avoid running out of xid.
The autovacuum supports vacuum regular table but local temp does not. autovacuum also does not support GTT.

2. However, the difference between the local temp table and the global temp table(GTT) is that
a) For local temp table: one table hava one piece of data. the frozenxid of one local temp table is store in the catalog(pg_class).
b) For global temp table: each session has a separate copy of data, one GTT may contain maxbackend frozenxid.
and I don't think it's a good idea to keep frozenxid of GTT in the catalog(pg_class).
It becomes a question: how to handle GTT transaction information?

I agree that problem 1 should be completely solved by a some feature, such as local transactions. It is definitely not included in the GTT patch.
But, I think we need to ensure the durability of GTT data. For example, data in GTT cannot be lost due to the clog being cleaned up. It belongs to problem 2.

For problem 2
If we ignore the frozenxid of GTT, when vacuum truncates the clog that GTT need, the GTT data in some sessions is completely lost.
Perhaps we could consider let aotuvacuum terminate those sessions that contain "too old" data,
But It's not very friendly, so I didn't choose to implement it in the first version.
Maybe you have a better idea.

Sorry, I do not have better idea.
I prefer not to address this problem in first version of the patch at all.
fozen_xid of temp table is never changed unless user explicitly invoke vacuum on it.
I do not think that anybody is doing it (because it accentually contains temporary data which is not expected to live long time.
Certainly it is possible to imagine situation when session use GTT to store some local data which is valid during all session life time (which can be large enough).
But I am not sure that it is popular scenario.

As global_private_temp-8.patch, think about:
1 session X tale several hours doing some statistical work with the GTT A, which generated some data using transaction 100, The work is not over.
2 Then session Y vacuumed A, and the GTT's relfrozenxid (in pg_class) was updated to 1000 0000.
3 Then the aotuvacuum happened, the clog before 1000 0000 was cleaned up.
4 The data in session A could be lost due to missing clog, The analysis task failed.

However This is likely to happen because you allowed the GTT do vacuum.
And this is not a common problem, that not happen with local temp tables.
I feel uneasy about leaving such a question. We can improve it.

Show quoted text

--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com <http://www.postgrespro.com/&gt;
The Russian Postgres Company

#121Pavel Stehule
pavel.stehule@gmail.com
In reply to: 曾文旌(义从) (#119)
Re: [Proposal] Global temporary tables

so 1. 2. 2020 v 14:39 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
napsal:

2020年1月30日 下午10:21,Pavel Stehule <pavel.stehule@gmail.com> 写道:

čt 30. 1. 2020 v 15:17 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
napsal:

2020年1月29日 下午9:48,Robert Haas <robertmhaas@gmail.com> 写道:

On Tue, Jan 28, 2020 at 12:12 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>

wrote:

Opinion by Pavel
+ rel->rd_islocaltemp = true; <<<<<<< if this is valid, then the

name of field "rd_islocaltemp" is not probably best

I renamed rd_islocaltemp

I don't see any change?

Rename rd_islocaltemp to rd_istemp in

global_temporary_table_v8-pg13.patch

In view of commit 6919b7e3294702adc39effd16634b2715d04f012, I think
that this has approximately a 0% chance of being acceptable. If you're
setting a field in a way that is inconsistent with the current use of
the field, you're probably doing it wrong, because the field has an
existing purpose to which new code must conform. And if you're not
doing that, then you don't need to rename it.

Thank you for pointing it out.
I've rolled back the rename.
But I still need rd_localtemp to be true, The reason is that
1 GTT The GTT needs to support DML in read-only transactions ,like local
temp table.
2 GTT does not need to hold the lock before modifying the index buffer
,also like local temp table.

Please give me feedback.

maybe some like

rel->rd_globaltemp = true;

and somewhere else

if (rel->rd_localtemp || rel->rd_globaltemp)
{
...
}

I tried to optimize code in global_temporary_table_v10-pg13.patch

Please give me feedback.

I tested this patch and I have not any objections - from my user
perspective it is work as I expect

+#define RELATION_IS_TEMP(relation) \
+ ((relation)->rd_islocaltemp || \
+ (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)

It looks little bit unbalanced

maybe is better to inject rd_isglobaltemp to relation structure

and then

it should to like

+#define RELATION_IS_TEMP(relation) \
+ ((relation)->rd_islocaltemp || \
+ (relation)->rd_isglobaltemp))

But I have not idea if it helps in complex

Show quoted text

Wenjing

Wenjing

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#122Konstantin Knizhnik
k.knizhnik@postgrespro.ru
In reply to: Robert Haas (#118)
Re: [Proposal] Global temporary tables

On 31.01.2020 22:38, Robert Haas wrote:

Now, your idea is not quite as crazy as that, but it has the same
basic problem: you can't insert code into a low-level facility that
uses a high level facility which may in turn use and depend on that
very same low-level facility to not be in the middle of an operation.
If you do, it's going to break somehow.

Thank you for explanation.
You convinced me that building indexes from _bt_getbuf is not good idea.
What do you think about idea to check and build indexes for GTT prior to
query execution?

In this case we do not need to patch code of all indexes - it can be
done just in one place.
We can use build function of access method to initialize index and
populate it with data.

So right now when building query execution plan, optimizer checks if
index is valid.
If index belongs to GTT, it an check that first page of the index is
initialized and if not - call build method for this index.

If building index during building query plan is not desirable, we can
just construct list of indexes which should be checked and
perform check itself and building indexes somewhere after building plan
but for execution of the query.

Do you seem some problems with such approach?

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

#123Konstantin Knizhnik
k.knizhnik@postgrespro.ru
In reply to: 曾文旌(义从) (#120)
Re: [Proposal] Global temporary tables

On 01.02.2020 19:14, 曾文旌(义从) wrote:

2020年1月27日 下午5:38,Konstantin Knizhnik <k.knizhnik@postgrespro.ru
<mailto:k.knizhnik@postgrespro.ru>> 写道:

On 25.01.2020 18:15, 曾文旌(义从) wrote:

I wonder why do we need some special check for GTT here.

From my point of view cleanup at startup of local storage of temp
tables should be performed in the same way for local and global
temp tables.

After oom kill, In autovacuum, the Isolated local temp table will be
cleaned like orphan temporary tables. The definition of local temp
table is deleted with the storage file.
But GTT can not do that. So we have the this implementation in my patch.
If you have other solutions, please let me know.

I wonder if it is possible that autovacuum or some other Postgres
process is killed by OOM and postmaster is not noticing it can
doens't restart Postgres instance?
as far as I know, crash of any process connected to Postgres shared
memory (and autovacuum definitely has such connection) cause Postgres
restart.

Postmaster will not restart after oom happen, but the startup process
will. GTT data files are cleaned up in the startup process.

Yes, exactly.
But it is still not clear to me why do we need some special handling for
GTT?
Shared memory is reinitialized and storage of temporary tables is removed.
It is true for both local and global temp tables.

In my design
1 Because different sessions have different transaction information,
I choose to store the transaction information of GTT in MyProc,not
catalog.
2 About the XID wraparound problem, the reason is the design of the
temp table storage(local temp table and global temp table) that
makes it can not to do vacuum by autovacuum.
It should be completely solve at the storage level.

My point of view is that vacuuming of temp tables is common problem
for local and global temp tables.
So it has to be addressed in the common way and so we should not try
to fix this problem only for GTT.

I think I agree with you this point.
However, this does not mean that GTT transaction information stored in
pg_class is correct.
If you keep it that way, like in global_private_temp-8.patch, It may
cause data loss in GTT after aotuvauum.

In my patch autovacuum is prohibited for GTT.

IMHO forced terminated of client sessions is not acceptable solution.

And it is not an absolutely necessary requirement.
So from my point of view we should not add such limitations to GTT
design.

This limitation makes it possible for the GTT to do all the DDL.
IMHO even oracle's GTT has similar limitations.

I have checked that Oracle is not preventing creation of index for GTT
if there are some active sessions working with this table. And this
index becomes visible for all this sessions.

As global_private_temp-8.patch, think about:
1 session X tale several hours doing some statistical work with the
GTT A, which generated some data using transaction 100, The work is
not over.
2 Then session Y vacuumed A, and the GTT's relfrozenxid (in pg_class)
was updated to 1000 0000.
3 Then the aotuvacuum happened, the clog  before 1000 0000 was cleaned up.
4 The data in session A could be lost due to missing clog, The
analysis task failed.

However This is likely to happen because you allowed the GTT do vacuum.
And this is not a common problem, that not happen with local temp tables.
I feel uneasy about leaving such a question. We can improve it.

May be the easies solution is to prohibit explicit vacuum of GTT?

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

#124曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Pavel Stehule (#121)
Re: [Proposal] Global temporary tables

2020年2月2日 上午2:00,Pavel Stehule <pavel.stehule@gmail.com> 写道:

so 1. 2. 2020 v 14:39 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:

2020年1月30日 下午10:21,Pavel Stehule <pavel.stehule@gmail.com <mailto:pavel.stehule@gmail.com>> 写道:

čt 30. 1. 2020 v 15:17 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:

2020年1月29日 下午9:48,Robert Haas <robertmhaas@gmail.com <mailto:robertmhaas@gmail.com>> 写道:

On Tue, Jan 28, 2020 at 12:12 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> wrote:

Opinion by Pavel
+ rel->rd_islocaltemp = true; <<<<<<< if this is valid, then the name of field "rd_islocaltemp" is not probably best
I renamed rd_islocaltemp

I don't see any change?

Rename rd_islocaltemp to rd_istemp in global_temporary_table_v8-pg13.patch

In view of commit 6919b7e3294702adc39effd16634b2715d04f012, I think
that this has approximately a 0% chance of being acceptable. If you're
setting a field in a way that is inconsistent with the current use of
the field, you're probably doing it wrong, because the field has an
existing purpose to which new code must conform. And if you're not
doing that, then you don't need to rename it.

Thank you for pointing it out.
I've rolled back the rename.
But I still need rd_localtemp to be true, The reason is that
1 GTT The GTT needs to support DML in read-only transactions ,like local temp table.
2 GTT does not need to hold the lock before modifying the index buffer ,also like local temp table.

Please give me feedback.

maybe some like

rel->rd_globaltemp = true;

and somewhere else

if (rel->rd_localtemp || rel->rd_globaltemp)
{
...
}

I tried to optimize code in global_temporary_table_v10-pg13.patch

Please give me feedback.

I tested this patch and I have not any objections - from my user perspective it is work as I expect

+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_islocaltemp || \
+	(relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)

It looks little bit unbalanced

maybe is better to inject rd_isglobaltemp to relation structure

and then

it should to like

+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_islocaltemp || \
+	(relation)->rd_isglobaltemp))

But I have not idea if it helps in complex

In my opinion
For local temp table we need (relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP
and because one local temp table belongs to only one session, need to mark one sessions rd_islocaltemp = true ,and other to rd_islocaltemp = false.

But For GTT, just need (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_GLOBAL_TEMP
One GTT can be used for every session, so no need rd_isglobaltemp anymore. This seems duplicated and redundant.

Show quoted text

Wenjing

Wenjing

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;
The Enterprise PostgreSQL Company

#125Pavel Stehule
pavel.stehule@gmail.com
In reply to: 曾文旌(义从) (#124)
Re: [Proposal] Global temporary tables

po 3. 2. 2020 v 14:03 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
napsal:

2020年2月2日 上午2:00,Pavel Stehule <pavel.stehule@gmail.com> 写道:

so 1. 2. 2020 v 14:39 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
napsal:

2020年1月30日 下午10:21,Pavel Stehule <pavel.stehule@gmail.com> 写道:

čt 30. 1. 2020 v 15:17 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
napsal:

2020年1月29日 下午9:48,Robert Haas <robertmhaas@gmail.com> 写道:

On Tue, Jan 28, 2020 at 12:12 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>

wrote:

Opinion by Pavel
+ rel->rd_islocaltemp = true; <<<<<<< if this is valid, then the

name of field "rd_islocaltemp" is not probably best

I renamed rd_islocaltemp

I don't see any change?

Rename rd_islocaltemp to rd_istemp in

global_temporary_table_v8-pg13.patch

In view of commit 6919b7e3294702adc39effd16634b2715d04f012, I think
that this has approximately a 0% chance of being acceptable. If you're
setting a field in a way that is inconsistent with the current use of
the field, you're probably doing it wrong, because the field has an
existing purpose to which new code must conform. And if you're not
doing that, then you don't need to rename it.

Thank you for pointing it out.
I've rolled back the rename.
But I still need rd_localtemp to be true, The reason is that
1 GTT The GTT needs to support DML in read-only transactions ,like local
temp table.
2 GTT does not need to hold the lock before modifying the index buffer
,also like local temp table.

Please give me feedback.

maybe some like

rel->rd_globaltemp = true;

and somewhere else

if (rel->rd_localtemp || rel->rd_globaltemp)
{
...
}

I tried to optimize code in global_temporary_table_v10-pg13.patch

Please give me feedback.

I tested this patch and I have not any objections - from my user
perspective it is work as I expect

+#define RELATION_IS_TEMP(relation) \
+ ((relation)->rd_islocaltemp || \
+ (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)

It looks little bit unbalanced

maybe is better to inject rd_isglobaltemp to relation structure

and then

it should to like

+#define RELATION_IS_TEMP(relation) \
+ ((relation)->rd_islocaltemp || \
+ (relation)->rd_isglobaltemp))

But I have not idea if it helps in complex

In my opinion
For local temp table we need (relation)->rd_rel->relpersistence ==
RELPERSISTENCE_TEMP
and because one local temp table belongs to only one session, need to mark
one sessions rd_islocaltemp = true ,and other to rd_islocaltemp = false.

so it means so table is assigned to current session or not. In this moment
I think so name "islocaltemp" is not the best - because there can be "local
temp table" that has this value false.

The name should be better describe so this table as attached to only
current session, and not for other, or is accessed by all session.

In this case I can understand why for GTT is possible to write
..rd_islocaltemp = true. But is signal so rd_islocaltemp is not good name
and rd_istemp is not good name too.

But For GTT, just need (relation)->rd_rel->relpersistence ==
RELPERSISTENCE_GLOBAL_GLOBAL_TEMP
One GTT can be used for every session, so no need rd_isglobaltemp
anymore. This seems duplicated and redundant.

I didn't understand well the sematic of rd_islocaltemp so my ideas in this
topics was not good. Now I think so rd_islocalname is not good name and can
be renamed if some body find better name. "istemptable" is not good too,
because there is important if relation is attached to the session or not.

Show quoted text

Wenjing

Wenjing

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#126曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Konstantin Knizhnik (#123)
Re: [Proposal] Global temporary tables

2020年2月3日 下午4:16,Konstantin Knizhnik <k.knizhnik@postgrespro.ru> 写道:

On 01.02.2020 19:14, 曾文旌(义从) wrote:

2020年1月27日 下午5:38,Konstantin Knizhnik <k.knizhnik@postgrespro.ru <mailto:k.knizhnik@postgrespro.ru>> 写道:

On 25.01.2020 18:15, 曾文旌(义从) wrote:

I wonder why do we need some special check for GTT here.

From my point of view cleanup at startup of local storage of temp tables should be performed in the same way for local and global temp tables.

After oom kill, In autovacuum, the Isolated local temp table will be cleaned like orphan temporary tables. The definition of local temp table is deleted with the storage file.
But GTT can not do that. So we have the this implementation in my patch.
If you have other solutions, please let me know.

I wonder if it is possible that autovacuum or some other Postgres process is killed by OOM and postmaster is not noticing it can doens't restart Postgres instance?
as far as I know, crash of any process connected to Postgres shared memory (and autovacuum definitely has such connection) cause Postgres restart.

Postmaster will not restart after oom happen, but the startup process will. GTT data files are cleaned up in the startup process.

Yes, exactly.
But it is still not clear to me why do we need some special handling for GTT?
Shared memory is reinitialized and storage of temporary tables is removed.
It is true for both local and global temp tables.

Of course not. The local temp table cleans up the entire table (including catalog buffer and datafile). GTT is not.

In my design
1 Because different sessions have different transaction information, I choose to store the transaction information of GTT in MyProc,not catalog.
2 About the XID wraparound problem, the reason is the design of the temp table storage(local temp table and global temp table) that makes it can not to do vacuum by autovacuum.
It should be completely solve at the storage level.

My point of view is that vacuuming of temp tables is common problem for local and global temp tables.
So it has to be addressed in the common way and so we should not try to fix this problem only for GTT.

I think I agree with you this point.
However, this does not mean that GTT transaction information stored in pg_class is correct.
If you keep it that way, like in global_private_temp-8.patch, It may cause data loss in GTT after aotuvauum.

In my patch autovacuum is prohibited for GTT.

But vacuum GTT is not prohibited.

IMHO forced terminated of client sessions is not acceptable solution.

And it is not an absolutely necessary requirement.
So from my point of view we should not add such limitations to GTT design.

This limitation makes it possible for the GTT to do all the DDL.
IMHO even oracle's GTT has similar limitations.

I have checked that Oracle is not preventing creation of index for GTT if there are some active sessions working with this table. And this index becomes visible for all this sessions.

1 Yes The creation of inde gtt has been improved in global_temporary_table_v10-pg13.patch
2 But alter GTT ; drop GTT ; drop index on GTT is blocked by other sessions

SQL> drop table gtt;
drop table gtt
*
ERROR at line 1:
ORA-14452: attempt to create, alter or drop an index on temporary table already
in use

SQL> ALTER TABLE gtt add b int ;
ALTER TABLE gtt add b int
*
ERROR at line 1:
ORA-14450: attempt to access a transactional temp table already in use

SQL> drop index idx_gtt;
drop index idx_gtt
*
ERROR at line 1:
ORA-14452: attempt to create, alter or drop an index on temporary table already
in use

I'm not saying we should do this, but from an implementation perspective we face similar issues.
If a dba changes a GTT, he can do it. Therefore, I think it is acceptable to do so.

As global_private_temp-8.patch, think about:
1 session X tale several hours doing some statistical work with the GTT A, which generated some data using transaction 100, The work is not over.
2 Then session Y vacuumed A, and the GTT's relfrozenxid (in pg_class) was updated to 1000 0000.
3 Then the aotuvacuum happened, the clog before 1000 0000 was cleaned up.
4 The data in session A could be lost due to missing clog, The analysis task failed.

However This is likely to happen because you allowed the GTT do vacuum.
And this is not a common problem, that not happen with local temp tables.
I feel uneasy about leaving such a question. We can improve it.

May be the easies solution is to prohibit explicit vacuum of GTT?

I think vacuum is an important part of GTT.

Looking back at previous emails, robert once said that vacuum GTT is pretty important.
/messages/by-id/CA+Tgmob=L1k0cpXRcipdsaE07ok+On=tTjRiw7FtD_D2T=Jwhg@mail.gmail.com </messages/by-id/CA+Tgmob=L1k0cpXRcipdsaE07ok+On=tTjRiw7FtD_D2T=Jwhg@mail.gmail.com&gt;

Show quoted text

--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com <http://www.postgrespro.com/&gt;
The Russian Postgres Company

#127Konstantin Knizhnik
k.knizhnik@postgrespro.ru
In reply to: 曾文旌(义从) (#126)
Re: [Proposal] Global temporary tables

On 04.02.2020 18:01, 曾文旌(义从) wrote:

Yes, exactly.
But it is still not clear to me why do we need some special handling
for GTT?
Shared memory is reinitialized and storage of temporary tables is
removed.
It is true for both local and global temp tables.

Of course not. The local temp table cleans up the entire table
(including catalog buffer and datafile). GTT is not.

What do you mean by "catalog buffer"?
Yes, cleanup of local temp table requires deletion of correspondent
entry from catalog and GTT should not do it.
But  I am speaking only about cleanup of data files of temp relations.
It is done in the same way for local and global temp tables.

In my patch autovacuum is prohibited for GTT.

But vacuum GTT is not prohibited.

Yes, but the simplest solution is to prohibit also explicit vacuum of
GTT, isn't it?

IMHO forced terminated of client sessions is not acceptable solution.

And it is not an absolutely necessary requirement.
So from my point of view we should not add such limitations to GTT
design.

This limitation makes it possible for the GTT to do all the DDL.
IMHO even oracle's GTT has similar limitations.

I have checked that Oracle is not preventing creation of index for
GTT if there are some active sessions working with this table. And
this index becomes visible for all this sessions.

1 Yes The creation of inde gtt has been improved
in global_temporary_table_v10-pg13.patch
2 But alter GTT ; drop GTT ; drop index on GTT is blocked by other
sessions

Yes, you are right.
Orale documetation says:

  1) DDL operation on global temporary tables

It is not possible to perform a DDL operation (except |TRUNCATE

<https://www.oracletutorial.com/oracle-basics/oracle-truncate-table/&gt;|)
on an existing global temporary table if one or more sessions are
currently bound to that table.

But looks like create index is not considered as DDL operation on GTT
and is also supported by Oracle.

Your approach with prohibiting such accessed using shared cache is
certainly better then my attempt to prohibit such DDLs for GTT at all.
I just what to eliminate maintenance of such shared cache to simplify
the patch.

But I still think that we should allow truncation of GTT and
creating/dropping indexes on it without any limitations.

May be the easies solution is to prohibit explicit vacuum of GTT?

I think vacuum is an important part of GTT.

Looking back at previous emails, robert once said that vacuum GTT is
pretty important.
/messages/by-id/CA+Tgmob=L1k0cpXRcipdsaE07ok+On=tTjRiw7FtD_D2T=Jwhg@mail.gmail.com
</messages/by-id/CA+Tgmob=L1k0cpXRcipdsaE07ok+On=tTjRiw7FtD_D2T=Jwhg@mail.gmail.com&gt;

Well, may be I am not right.
I never saw use cases where temp table are used not like append-only
storage (when temp table tuples are updated multiple times).
But I think that if such problem actually exists then solution is to
support autovacuum for temp tables, rather than allow manual vacuum.
Certainly it can not be done by another  worker because it has no access
to private backend's data. But it can done incrementally by backend itself.

#128Robert Haas
robertmhaas@gmail.com
In reply to: 曾文旌(义从) (#120)
Re: [Proposal] Global temporary tables

On Sat, Feb 1, 2020 at 11:14 AM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com> wrote:

As global_private_temp-8.patch, think about:
1 session X tale several hours doing some statistical work with the GTT A, which generated some data using transaction 100, The work is not over.
2 Then session Y vacuumed A, and the GTT's relfrozenxid (in pg_class) was updated to 1000 0000.
3 Then the aotuvacuum happened, the clog before 1000 0000 was cleaned up.
4 The data in session A could be lost due to missing clog, The analysis task failed.

However This is likely to happen because you allowed the GTT do vacuum.
And this is not a common problem, that not happen with local temp tables.
I feel uneasy about leaving such a question. We can improve it.

Each session is going to need to maintain its own notion of the
relfrozenxid and relminmxid of each GTT to which it is attached.
Storing the values in pg_class makes no sense and is completely
unacceptable.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#129Robert Haas
robertmhaas@gmail.com
In reply to: Konstantin Knizhnik (#122)
Re: [Proposal] Global temporary tables

On Mon, Feb 3, 2020 at 3:08 AM Konstantin Knizhnik
<k.knizhnik@postgrespro.ru> wrote:

Thank you for explanation.
You convinced me that building indexes from _bt_getbuf is not good idea.
What do you think about idea to check and build indexes for GTT prior to
query execution?

In this case we do not need to patch code of all indexes - it can be
done just in one place.
We can use build function of access method to initialize index and
populate it with data.

So right now when building query execution plan, optimizer checks if
index is valid.
If index belongs to GTT, it an check that first page of the index is
initialized and if not - call build method for this index.

If building index during building query plan is not desirable, we can
just construct list of indexes which should be checked and
perform check itself and building indexes somewhere after building plan
but for execution of the query.

Do you seem some problems with such approach?

My guess it that the right time to do this work is just after we
acquire locks, at the end of parse analysis. I think trying to do it
during execution is too late, since the planner looks at indexes, and
trying to do it in the planner instead of before we start planning
seems more likely to cause bugs and has no real advantages. It's just
better to do complicated things (like creating indexes) separately
rather than in the middle of some other complicated thing (like
planning). I could tie my shoelaces the first time they get tangled up
with my break pedal but it's better to do it before I get in the car.

And I'm still inclined to do it by flat-copying files rather than
calling ambuild. It will be slightly faster, but also importantly, it
will guarantee that (1) every backend gets exactly the same initial
state and (2) it has fewer ways to fail because it doesn't involve
calling any user-defined code. Those seem like fairly compelling
advantages, and I don't see what the disadvantages are. I think
calling ambuild() at the point in time proposed in the preceding
paragraph would be fairly safe and would probably work OK most of the
time, but I can't think of any reason it would be better.

Incidentally, what I'd be inclined to do is - if the session is
running a query that does only read-only operations, let it continue
to point to the "master" copy of the GTT and its indexes, which is
stored in the relfilenodes indicated for those relations in pg_class.
If it's going to acquire a lock heavier than AccessShareLock, then
give it is own copies of the table and indexes, stored in a temporary
relfilenode (tXXX_YYY) and redirect all future access to that GTT by
this backend to there. Maybe there's some reason this won't work, but
it seems nice to avoid saying that we've "attached" to the GTT if all
we did is read the empty table.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#130Konstantin Knizhnik
k.knizhnik@postgrespro.ru
In reply to: Robert Haas (#129)
Re: [Proposal] Global temporary tables

On 05.02.2020 00:38, Robert Haas wrote:

My guess it that the right time to do this work is just after we
acquire locks, at the end of parse analysis. I think trying to do it
during execution is too late, since the planner looks at indexes, and
trying to do it in the planner instead of before we start planning
seems more likely to cause bugs and has no real advantages. It's just
better to do complicated things (like creating indexes) separately
rather than in the middle of some other complicated thing (like
planning). I could tie my shoelaces the first time they get tangled up
with my break pedal but it's better to do it before I get in the car.

I have implemented this approach in my new patch

/messages/by-id/3e88b59f-73e8-685e-4983-9026f94c57c5@postgrespro.ru

I have added check whether index is initialized or not to plancat.c
where optimizer checks if index is valid.
Now it should work for all kinds of indexes (B-Tree, hash, user defined
access methods...).

And I'm still inclined to do it by flat-copying files rather than
calling ambuild. It will be slightly faster, but also importantly, it
will guarantee that (1) every backend gets exactly the same initial
state and (2) it has fewer ways to fail because it doesn't involve
calling any user-defined code. Those seem like fairly compelling
advantages, and I don't see what the disadvantages are. I think
calling ambuild() at the point in time proposed in the preceding
paragraph would be fairly safe and would probably work OK most of the
time, but I can't think of any reason it would be better.

There is very important reason (from my point of view): allow other
sessions to use created index and
so provide compatible behavior with regular tables (and with Oracle).
So we should be able to populate index with existed GTT data.
And ambuild will do it.

Incidentally, what I'd be inclined to do is - if the session is
running a query that does only read-only operations, let it continue
to point to the "master" copy of the GTT and its indexes, which is
stored in the relfilenodes indicated for those relations in pg_class.
If it's going to acquire a lock heavier than AccessShareLock, then
give it is own copies of the table and indexes, stored in a temporary
relfilenode (tXXX_YYY) and redirect all future access to that GTT by
this backend to there. Maybe there's some reason this won't work, but
it seems nice to avoid saying that we've "attached" to the GTT if all
we did is read the empty table.

Sorry, I do not understand the benefits of such optimization. It seems
to be very rare situation when session will try to access temp table
which was not previously filled with data. But even if it happen,
keeping "master" copy will not safe much: we in any case have shared
metadata and no data. Yes, with current approach, first access to GTT
will cause creation of empty indexes. But It is just initialization of
1-3 pages. I do not think that delaying index initialization can be
really useful.

In any case, calling ambuild is the simplest and most universal
approach, providing desired and compatible behavior.
I really do not understand why we should try yo invent some alternative
solution.

#131曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Robert Haas (#128)
Re: [Proposal] Global temporary tables

2020年2月5日 上午4:57,Robert Haas <robertmhaas@gmail.com> 写道:

On Sat, Feb 1, 2020 at 11:14 AM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com> wrote:

As global_private_temp-8.patch, think about:
1 session X tale several hours doing some statistical work with the GTT A, which generated some data using transaction 100, The work is not over.
2 Then session Y vacuumed A, and the GTT's relfrozenxid (in pg_class) was updated to 1000 0000.
3 Then the aotuvacuum happened, the clog before 1000 0000 was cleaned up.
4 The data in session A could be lost due to missing clog, The analysis task failed.

However This is likely to happen because you allowed the GTT do vacuum.
And this is not a common problem, that not happen with local temp tables.
I feel uneasy about leaving such a question. We can improve it.

Each session is going to need to maintain its own notion of the
relfrozenxid and relminmxid of each GTT to which it is attached.
Storing the values in pg_class makes no sense and is completely
unacceptable.

Yes, I've implemented it in global_temporary_table_v10-pg13.patch

Show quoted text

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#132曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Konstantin Knizhnik (#127)
Re: [Proposal] Global temporary tables

2020年2月5日 上午12:47,Konstantin Knizhnik <k.knizhnik@postgrespro.ru> 写道:

On 04.02.2020 18:01, 曾文旌(义从) wrote:

Yes, exactly.
But it is still not clear to me why do we need some special handling for GTT?
Shared memory is reinitialized and storage of temporary tables is removed.
It is true for both local and global temp tables.

Of course not. The local temp table cleans up the entire table (including catalog buffer and datafile). GTT is not.

What do you mean by "catalog buffer"?
Yes, cleanup of local temp table requires deletion of correspondent entry from catalog and GTT should not do it.
But I am speaking only about cleanup of data files of temp relations. It is done in the same way for local and global temp tables.

For native pg, the data file of temp table will not be cleaned up direct after oom happen.
Because the orphan local temp table(include catalog, local buffer, datafile) will be cleaned up by deleting the orphan temp schame in autovacuum.
So for GTT ,we cannot do the same with just deleting data files. This is why I dealt with it specifically.

In my patch autovacuum is prohibited for GTT.

But vacuum GTT is not prohibited.

Yes, but the simplest solution is to prohibit also explicit vacuum of GTT, isn't it?

IMHO forced terminated of client sessions is not acceptable solution.

And it is not an absolutely necessary requirement.
So from my point of view we should not add such limitations to GTT design.

This limitation makes it possible for the GTT to do all the DDL.
IMHO even oracle's GTT has similar limitations.

I have checked that Oracle is not preventing creation of index for GTT if there are some active sessions working with this table. And this index becomes visible for all this sessions.

1 Yes The creation of inde gtt has been improved in global_temporary_table_v10-pg13.patch
2 But alter GTT ; drop GTT ; drop index on GTT is blocked by other sessions

Yes, you are right.
Orale documetation says:

1) DDL operation on global temporary tables
It is not possible to perform a DDL operation (except TRUNCATE <https://www.oracletutorial.com/oracle-basics/oracle-truncate-table/&gt;) on an existing global temporary table if one or more sessions are currently bound to that table.

But looks like create index is not considered as DDL operation on GTT and is also supported by Oracle.

Your approach with prohibiting such accessed using shared cache is certainly better then my attempt to prohibit such DDLs for GTT at all.
I just what to eliminate maintenance of such shared cache to simplify the patch.

But I still think that we should allow truncation of GTT and creating/dropping indexes on it without any limitations.

I think the goal of this work is this.
But, the first step is let GTT get as many features as possible on regular tables, even with some limitations.

Show quoted text

May be the easies solution is to prohibit explicit vacuum of GTT?

I think vacuum is an important part of GTT.

Looking back at previous emails, robert once said that vacuum GTT is pretty important.
/messages/by-id/CA+Tgmob=L1k0cpXRcipdsaE07ok+On=tTjRiw7FtD_D2T=Jwhg@mail.gmail.com </messages/by-id/CA+Tgmob=L1k0cpXRcipdsaE07ok+On=tTjRiw7FtD_D2T=Jwhg@mail.gmail.com&gt;

Well, may be I am not right.
I never saw use cases where temp table are used not like append-only storage (when temp table tuples are updated multiple times).
But I think that if such problem actually exists then solution is to support autovacuum for temp tables, rather than allow manual vacuum.
Certainly it can not be done by another worker because it has no access to private backend's data. But it can done incrementally by backend itself.

#133Robert Haas
robertmhaas@gmail.com
In reply to: Konstantin Knizhnik (#130)
Re: [Proposal] Global temporary tables

On Wed, Feb 5, 2020 at 2:28 AM Konstantin Knizhnik
<k.knizhnik@postgrespro.ru> wrote:

There is very important reason (from my point of view): allow other
sessions to use created index and
so provide compatible behavior with regular tables (and with Oracle).
So we should be able to populate index with existed GTT data.
And ambuild will do it.

I don't understand. A global temporary table, as I understand it, is a
table for which each session sees separate contents. So you would
never need to populate it with existing data.

Besides, even if you did, how are you going to get the data for the
table? If you get the table data by flat-copying the table, then you
could copy the index files too. And you would want to, because if the
table contains a large amount of data, building indexes will be
expensive. If the index is *empty*, a file copy will not be much
cheaper than calling ambuild(), but if it's got a lot of data in it,
it will.

Sorry, I do not understand the benefits of such optimization. It seems
to be very rare situation when session will try to access temp table
which was not previously filled with data. But even if it happen,
keeping "master" copy will not safe much: we in any case have shared
metadata and no data. Yes, with current approach, first access to GTT
will cause creation of empty indexes. But It is just initialization of
1-3 pages. I do not think that delaying index initialization can be
really useful.

You might be right, but you're misunderstanding the nature of my
concern. We probably can't allow DDL on a GTT unless no sessions are
attached. Having sessions that just read the empty GTT be considered
as "not attached" might make it easier for some users to find a time
when no backend is attached and thus DDL is possible.

In any case, calling ambuild is the simplest and most universal
approach, providing desired and compatible behavior.

Calling ambuild is definitely not simpler than a plain file copy. I
don't know how you can contend otherwise.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#134Robert Haas
robertmhaas@gmail.com
In reply to: 曾文旌(义从) (#132)
Re: [Proposal] Global temporary tables

On Wed, Feb 5, 2020 at 8:21 AM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com> wrote:

What do you mean by "catalog buffer"?
Yes, cleanup of local temp table requires deletion of correspondent entry from catalog and GTT should not do it.
But I am speaking only about cleanup of data files of temp relations. It is done in the same way for local and global temp tables.

For native pg, the data file of temp table will not be cleaned up direct after oom happen.
Because the orphan local temp table(include catalog, local buffer, datafile) will be cleaned up by deleting the orphan temp schame in autovacuum.
So for GTT ,we cannot do the same with just deleting data files. This is why I dealt with it specifically.

After a crash restart, all temporary relfilenodes (e.g t12345_67890)
are removed. I think GTTs should use relfilenodes of this general
form, and then they'll be cleaned up by the existing code. For a
regular temporary table, there is also the problem of removing the
catalog entries, but GTTs shouldn't have this problem, because a GTT
doesn't have any catalog entries for individual sessions, just for the
main object, which isn't going away just because the system restarted.
Right?

In my patch autovacuum is prohibited for GTT.

But vacuum GTT is not prohibited.

That sounds right to me.

This thread is getting very hard to follow because neither Konstantin
nor Wenjing seem to be using the standard method of quoting. When I
reply, I get the whole thing quoted with "> " but can't easily tell
the difference between what Wenjing wrote and what Konstantin wrote,
because both of your mailers are quoting using indentation rather than
"> " and it gets wiped out by my mailer. Please see if you can get
your mailer to do what is normally done on this mailing list.

Thanks,

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#135Konstantin Knizhnik
k.knizhnik@postgrespro.ru
In reply to: Robert Haas (#133)
Re: [Proposal] Global temporary tables

On 05.02.2020 17:10, Robert Haas wrote:

On Wed, Feb 5, 2020 at 2:28 AM Konstantin Knizhnik
<k.knizhnik@postgrespro.ru> wrote:

There is very important reason (from my point of view): allow other
sessions to use created index and
so provide compatible behavior with regular tables (and with Oracle).
So we should be able to populate index with existed GTT data.
And ambuild will do it.

I don't understand. A global temporary table, as I understand it, is a
table for which each session sees separate contents. So you would
never need to populate it with existing data.

Session 1:
create global temp table gtt(x integer);
insert into gtt values (generate_series(1,100000));

Session 2:
insert into gtt values (generate_series(1,200000));

Session1:
create index on gtt(x);
explain select * from gtt where x = 1;

Session2:
explain select * from gtt where x = 1;
??? Should we use index here?

My answer is - yes.
Just because:
- Such behavior is compatible with regular tables. So it will not
confuse users and doesn't require some complex explanations.
- It is compatible with Oracle.
- It is what DBA usually want when creating index.
-
There are several arguments against such behavior:
- Concurrent building of index in multiple sessions can consume a lot of
memory
- Building index can increase query execution time (which can be not
expected by clients)

I have discussion about it with Pavel here in Pgcon Moscow but we can
not convince each other.
May be we should provide a choice to the user, by means of GUC or index
creating parameter.

Besides, even if you did, how are you going to get the data for the
table? If you get the table data by flat-copying the table, then you
could copy the index files too. And you would want to, because if the
table contains a large amount of data, building indexes will be
expensive. If the index is *empty*, a file copy will not be much
cheaper than calling ambuild(), but if it's got a lot of data in it,
it will.

Sorry, I do not understand you.
ambuild is called locally by each backend on first access to the GTT index.
It is done at the moment of building query execution plan when we check
whether index is valid.
May be it will be sensible to postpone this check and do it for indexes
which are actually used in query execution plan.

Sorry, I do not understand the benefits of such optimization. It seems
to be very rare situation when session will try to access temp table
which was not previously filled with data. But even if it happen,
keeping "master" copy will not safe much: we in any case have shared
metadata and no data. Yes, with current approach, first access to GTT
will cause creation of empty indexes. But It is just initialization of
1-3 pages. I do not think that delaying index initialization can be
really useful.

You might be right, but you're misunderstanding the nature of my
concern. We probably can't allow DDL on a GTT unless no sessions are
attached. Having sessions that just read the empty GTT be considered
as "not attached" might make it easier for some users to find a time
when no backend is attached and thus DDL is possible.

Ok, now I understand the problem your are going to address.
But still I never saw use cases when empty temp tables are accessed.
Usually we save in temp table some intermediate results of complex query.
Certainly it can happen that query returns empty result.
But usually temp table are used when we expect huge result (otherwise
materializing result in temp table is not needed).
So I do not think that such optimization can help much in performing DDL
for GTT.

In any case, calling ambuild is the simplest and most universal
approach, providing desired and compatible behavior.

Calling ambuild is definitely not simpler than a plain file copy. I
don't know how you can contend otherwise.

This is code fragment whichbuild GTT index on demand:

    if (index->rd_rel->relpersistence == RELPERSISTENCE_SESSION)
    {
        Buffer metapage = ReadBuffer(index, 0);
        bool isNew = PageIsNew(BufferGetPage(metapage));
        ReleaseBuffer(metapage);
        if (isNew)
        {
            Relation heap;
DropRelFileNodeAllLocalBuffers(index->rd_smgr->smgr_rnode.node);
            heap = RelationIdGetRelation(index->rd_index->indrelid);
            index->rd_indam->ambuild(heap, index, BuildIndexInfo(index));
            RelationClose(heap);
        }
    }

That is all - just 10 line of code.
I can make a bet that maintaining separate fork for indexes and copying
data from it will require much more coding.

#136Pavel Stehule
pavel.stehule@gmail.com
In reply to: Konstantin Knizhnik (#135)
Re: [Proposal] Global temporary tables

st 5. 2. 2020 v 16:48 odesílatel Konstantin Knizhnik <
k.knizhnik@postgrespro.ru> napsal:

On 05.02.2020 17:10, Robert Haas wrote:

On Wed, Feb 5, 2020 at 2:28 AM Konstantin Knizhnik
<k.knizhnik@postgrespro.ru> wrote:

There is very important reason (from my point of view): allow other
sessions to use created index and
so provide compatible behavior with regular tables (and with Oracle).
So we should be able to populate index with existed GTT data.
And ambuild will do it.

I don't understand. A global temporary table, as I understand it, is a
table for which each session sees separate contents. So you would
never need to populate it with existing data.

Session 1:
create global temp table gtt(x integer);
insert into gtt values (generate_series(1,100000));

Session 2:
insert into gtt values (generate_series(1,200000));

Session1:
create index on gtt(x);
explain select * from gtt where x = 1;

Session2:
explain select * from gtt where x = 1;
??? Should we use index here?

My answer is - yes.
Just because:
- Such behavior is compatible with regular tables. So it will not
confuse users and doesn't require some complex explanations.
- It is compatible with Oracle.
- It is what DBA usually want when creating index.
-
There are several arguments against such behavior:
- Concurrent building of index in multiple sessions can consume a lot of
memory
- Building index can increase query execution time (which can be not
expected by clients)

I have discussion about it with Pavel here in Pgcon Moscow but we can
not convince each other.
May be we should provide a choice to the user, by means of GUC or index
creating parameter.

I prefer some creating index parameter for enforcing creating indexes to
living other session.

In this case I think so too much strongly the best design depends on
context so there cannot to exists one design (both proposed behaves has
sense and has contrary advantages and disadvantages). Unfortunately only
one behave can be default.

Regards

Pavel

Show quoted text

Besides, even if you did, how are you going to get the data for the
table? If you get the table data by flat-copying the table, then you
could copy the index files too. And you would want to, because if the
table contains a large amount of data, building indexes will be
expensive. If the index is *empty*, a file copy will not be much
cheaper than calling ambuild(), but if it's got a lot of data in it,
it will.

Sorry, I do not understand you.
ambuild is called locally by each backend on first access to the GTT index.
It is done at the moment of building query execution plan when we check
whether index is valid.
May be it will be sensible to postpone this check and do it for indexes
which are actually used in query execution plan.

Sorry, I do not understand the benefits of such optimization. It seems
to be very rare situation when session will try to access temp table
which was not previously filled with data. But even if it happen,
keeping "master" copy will not safe much: we in any case have shared
metadata and no data. Yes, with current approach, first access to GTT
will cause creation of empty indexes. But It is just initialization of
1-3 pages. I do not think that delaying index initialization can be
really useful.

You might be right, but you're misunderstanding the nature of my
concern. We probably can't allow DDL on a GTT unless no sessions are
attached. Having sessions that just read the empty GTT be considered
as "not attached" might make it easier for some users to find a time
when no backend is attached and thus DDL is possible.

Ok, now I understand the problem your are going to address.
But still I never saw use cases when empty temp tables are accessed.
Usually we save in temp table some intermediate results of complex query.
Certainly it can happen that query returns empty result.
But usually temp table are used when we expect huge result (otherwise
materializing result in temp table is not needed).
So I do not think that such optimization can help much in performing DDL
for GTT.

In any case, calling ambuild is the simplest and most universal
approach, providing desired and compatible behavior.

Calling ambuild is definitely not simpler than a plain file copy. I
don't know how you can contend otherwise.

This is code fragment whichbuild GTT index on demand:

if (index->rd_rel->relpersistence == RELPERSISTENCE_SESSION)
{
Buffer metapage = ReadBuffer(index, 0);
bool isNew = PageIsNew(BufferGetPage(metapage));
ReleaseBuffer(metapage);
if (isNew)
{
Relation heap;
DropRelFileNodeAllLocalBuffers(index->rd_smgr->smgr_rnode.node);
heap = RelationIdGetRelation(index->rd_index->indrelid);
index->rd_indam->ambuild(heap, index, BuildIndexInfo(index));
RelationClose(heap);
}
}

That is all - just 10 line of code.
I can make a bet that maintaining separate fork for indexes and copying
data from it will require much more coding.

#137曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Robert Haas (#134)
Re: [Proposal] Global temporary tables

2020年2月5日 下午10:15,Robert Haas <robertmhaas@gmail.com> 写道:

On Wed, Feb 5, 2020 at 8:21 AM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com> wrote:

What do you mean by "catalog buffer"?
Yes, cleanup of local temp table requires deletion of correspondent entry from catalog and GTT should not do it.
But I am speaking only about cleanup of data files of temp relations. It is done in the same way for local and global temp tables.

For native pg, the data file of temp table will not be cleaned up direct after oom happen.
Because the orphan local temp table(include catalog, local buffer, datafile) will be cleaned up by deleting the orphan temp schame in autovacuum.
So for GTT ,we cannot do the same with just deleting data files. This is why I dealt with it specifically.

After a crash restart, all temporary relfilenodes (e.g t12345_67890)
are removed. I think GTTs should use relfilenodes of this general
form, and then they'll be cleaned up by the existing code. For a
regular temporary table, there is also the problem of removing the
catalog entries, but GTTs shouldn't have this problem, because a GTT
doesn't have any catalog entries for individual sessions, just for the
main object, which isn't going away just because the system restarted.
Right?

Wenjing wrote:
I have implemented its processing in global_temporary_table_v10-pg13.patch
When oom happen, all backend will be killed.
Then, I choose to clean up these files(all like t12345_67890) in startup process.

Wenjing

In my patch autovacuum is prohibited for GTT.

But vacuum GTT is not prohibited.

That sounds right to me.

Wenjing wrote:
Also implemented in global_temporary_table_v10-pg13.patch

Wenjing

Show quoted text

This thread is getting very hard to follow because neither Konstantin
nor Wenjing seem to be using the standard method of quoting. When I
reply, I get the whole thing quoted with "> " but can't easily tell
the difference between what Wenjing wrote and what Konstantin wrote,
because both of your mailers are quoting using indentation rather than
"> " and it gets wiped out by my mailer. Please see if you can get
your mailer to do what is normally done on this mailing list.

Thanks,

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#138Robert Haas
robertmhaas@gmail.com
In reply to: Konstantin Knizhnik (#135)
Re: [Proposal] Global temporary tables

On Wed, Feb 5, 2020 at 10:48 AM Konstantin Knizhnik
<k.knizhnik@postgrespro.ru> wrote:

I don't understand. A global temporary table, as I understand it, is a
table for which each session sees separate contents. So you would
never need to populate it with existing data.

Session 1:
create global temp table gtt(x integer);
insert into gtt values (generate_series(1,100000));

Session 2:
insert into gtt values (generate_series(1,200000));

Session1:
create index on gtt(x);
explain select * from gtt where x = 1;

Session2:
explain select * from gtt where x = 1;
??? Should we use index here?

OK, I see where you're coming from now.

My answer is - yes.
Just because:
- Such behavior is compatible with regular tables. So it will not
confuse users and doesn't require some complex explanations.
- It is compatible with Oracle.
- It is what DBA usually want when creating index.
-
There are several arguments against such behavior:
- Concurrent building of index in multiple sessions can consume a lot of
memory
- Building index can increase query execution time (which can be not
expected by clients)

I think those are good arguments, especially the second one. There's
no limit on how long building a new index might take, and it could be
several minutes. A user who was running a query that could have
completed in a few seconds or even milliseconds will be unhappy to
suddenly wait a long time for a new index to be built. And that is an
entirely realistic scenario, because the new index might be better,
but only marginally.

Also, an important point to which I've already alluded a few times is
that creating an index can fail. Now, one way it can fail is that
there could be some problem writing to disk, or you could run out of
memory, or whatever. However, it can also fail because the new index
is UNIQUE and the data this backend has in the table doesn't conform
to the associated constraint. It will be confusing if all access to a
table suddenly starts complaining about uniqueness violations.

That is all - just 10 line of code.

I don't believe that the feature you are proposing can be correctly
implemented in 10 lines of code. I would be pleasantly surprised if it
can be done in 1000.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#139Konstantin Knizhnik
k.knizhnik@postgrespro.ru
In reply to: Robert Haas (#138)
Re: [Proposal] Global temporary tables

On 07.02.2020 18:15, Robert Haas wrote:

On Wed, Feb 5, 2020 at 10:48 AM Konstantin Knizhnik
<k.knizhnik@postgrespro.ru> wrote:
My answer is - yes.

Just because:
- Such behavior is compatible with regular tables. So it will not
confuse users and doesn't require some complex explanations.
- It is compatible with Oracle.
- It is what DBA usually want when creating index.
-
There are several arguments against such behavior:
- Concurrent building of index in multiple sessions can consume a lot of
memory
- Building index can increase query execution time (which can be not
expected by clients)

I think those are good arguments, especially the second one. There's
no limit on how long building a new index might take, and it could be
several minutes. A user who was running a query that could have
completed in a few seconds or even milliseconds will be unhappy to
suddenly wait a long time for a new index to be built. And that is an
entirely realistic scenario, because the new index might be better,
but only marginally.

Yes, I agree that this arguments are important.
But IMHO less important than incompatible behavior (Pavel doesn't agree
with word "incompatible" in this context
since semantic of temp tables is in any case different with semantic of
regular tables).

Just want to notice that if we have huge GTT (so that creation of index
takes significant amount of time)
sequential scan of this table also will not be fast.

But in any case, if we agree that we can control thus behavior using GUC
or index property,
then it is ok for me.

Also, an important point to which I've already alluded a few times is
that creating an index can fail. Now, one way it can fail is that
there could be some problem writing to disk, or you could run out of
memory, or whatever. However, it can also fail because the new index
is UNIQUE and the data this backend has in the table doesn't conform
to the associated constraint. It will be confusing if all access to a
table suddenly starts complaining about uniqueness violations.

Yes, building index can fail (as any other operation with database).
What's wring with it?
If it is fatal error, then backend is terminated and content of its temp
table is disappeared.
If it is non-fatal error, then current transaction is aborted:

Session1:
postgres=# create global temp table gtt(x integer);
CREATE TABLE
postgres=# insert into gtt values (generate_series(1,100000));
INSERT 0 100000

Session2:
postgres=# insert into gtt values (generate_series(1,100000));
INSERT 0 100000
postgres=# insert into gtt values (1);
INSERT 0 1

Session1:
postgres=# create unique index on gtt(x);
CREATE INDEX

Sessin2:
postgres=# explain select * from gtt where x=1;
ERROR:  could not create unique index "gtt_x_idx"
DETAIL:  Key (x)=(1) is duplicated.

I don't believe that the feature you are proposing can be correctly
implemented in 10 lines of code. I would be pleasantly surprised if it
can be done in 1000.

Right now I do not see any sources of extra complexity.
Will be pleased if you can point them to me.

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

#140Robert Haas
robertmhaas@gmail.com
In reply to: Konstantin Knizhnik (#139)
Re: [Proposal] Global temporary tables

On Fri, Feb 7, 2020 at 12:28 PM Konstantin Knizhnik
<k.knizhnik@postgrespro.ru> wrote:

But in any case, if we agree that we can control thus behavior using GUC
or index property,
then it is ok for me.

Nope, I am not going to agree to that, and I don't believe that any
other committer will, either.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#141Pavel Stehule
pavel.stehule@gmail.com
In reply to: Konstantin Knizhnik (#139)
Re: [Proposal] Global temporary tables

pá 7. 2. 2020 v 18:28 odesílatel Konstantin Knizhnik <
k.knizhnik@postgrespro.ru> napsal:

On 07.02.2020 18:15, Robert Haas wrote:

On Wed, Feb 5, 2020 at 10:48 AM Konstantin Knizhnik
<k.knizhnik@postgrespro.ru> wrote:
My answer is - yes.

Just because:
- Such behavior is compatible with regular tables. So it will not
confuse users and doesn't require some complex explanations.
- It is compatible with Oracle.
- It is what DBA usually want when creating index.
-
There are several arguments against such behavior:
- Concurrent building of index in multiple sessions can consume a lot of
memory
- Building index can increase query execution time (which can be not
expected by clients)

I think those are good arguments, especially the second one. There's
no limit on how long building a new index might take, and it could be
several minutes. A user who was running a query that could have
completed in a few seconds or even milliseconds will be unhappy to
suddenly wait a long time for a new index to be built. And that is an
entirely realistic scenario, because the new index might be better,
but only marginally.

Yes, I agree that this arguments are important.
But IMHO less important than incompatible behavior (Pavel doesn't agree
with word "incompatible" in this context
since semantic of temp tables is in any case different with semantic of
regular tables).

Just want to notice that if we have huge GTT (so that creation of index
takes significant amount of time)
sequential scan of this table also will not be fast.

But in any case, if we agree that we can control thus behavior using GUC
or index property,
then it is ok for me.

Also, an important point to which I've already alluded a few times is
that creating an index can fail. Now, one way it can fail is that
there could be some problem writing to disk, or you could run out of
memory, or whatever. However, it can also fail because the new index
is UNIQUE and the data this backend has in the table doesn't conform
to the associated constraint. It will be confusing if all access to a
table suddenly starts complaining about uniqueness violations.

Yes, building index can fail (as any other operation with database).
What's wring with it?
If it is fatal error, then backend is terminated and content of its temp
table is disappeared.
If it is non-fatal error, then current transaction is aborted:

Session1:
postgres=# create global temp table gtt(x integer);
CREATE TABLE
postgres=# insert into gtt values (generate_series(1,100000));
INSERT 0 100000

Session2:
postgres=# insert into gtt values (generate_series(1,100000));
INSERT 0 100000
postgres=# insert into gtt values (1);
INSERT 0 1

What when session 2 has active transaction? Then to be correct, you should
to wait with index creation to end of transaction.

Session1:
postgres=# create unique index on gtt(x);
CREATE INDEX

Sessin2:
postgres=# explain select * from gtt where x=1;
ERROR: could not create unique index "gtt_x_idx"
DETAIL: Key (x)=(1) is duplicated.

This is little bit unexpected behave (probably nobody expect so any SELECT
fail with error "could not create index" - I understand exactly to reason
and context, but this side effect is something what I afraid.

Show quoted text

I don't believe that the feature you are proposing can be correctly
implemented in 10 lines of code. I would be pleasantly surprised if it
can be done in 1000.

Right now I do not see any sources of extra complexity.
Will be pleased if you can point them to me.

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

#142Konstantin Knizhnik
k.knizhnik@postgrespro.ru
In reply to: Pavel Stehule (#141)
Re: [Proposal] Global temporary tables

On 07.02.2020 21:37, Pavel Stehule wrote:

What when session 2 has active transaction? Then to be correct, you
should to wait with index creation to end of transaction.

Session1:
postgres=# create unique index on gtt(x);
CREATE INDEX

Sessin2:
postgres=# explain select * from gtt where x=1;
ERROR:  could not create unique index "gtt_x_idx"
DETAIL:  Key (x)=(1) is duplicated.

This is little bit unexpected behave (probably nobody expect so any
SELECT fail with error "could not create index" - I understand exactly
to reason and context, but this side effect is something what I afraid.

The more I thinking creation of indexes for GTT on-demand, the more
contractions I see.
So looks like there are only two safe alternatives:
1. Allow DDL for GTT (including index creation) only if there are no
other sessions using this GTT ("using" means that no data was inserted
in GTT by this session). Things can be even more complicated if we take
in account inter-table dependencies (like foreign key constraint).
2. Create indexes for GTT locally.

2) seems to be very contradictory (global table metadata, but private
indexes) and hard to implement because in this case we have to maintain
some private copy of index catalog to keep information about private
indexes.

1) is currently implemented by Wenjing. Frankly speaking I still find
such limitation too restrictive and inconvenient for users. From my
point of view Oracle developers have implemented better compromise. But
if I am the only person voting for such solution, then let's stop this
discussion.
But in any case I think that calling ambuild to construct index for
empty table is better solution than implementation of all indexes (and
still not solving the problem with custom indexes).

#143Pavel Stehule
pavel.stehule@gmail.com
In reply to: Konstantin Knizhnik (#142)
Re: [Proposal] Global temporary tables

ne 9. 2. 2020 v 13:05 odesílatel Konstantin Knizhnik <
k.knizhnik@postgrespro.ru> napsal:

On 07.02.2020 21:37, Pavel Stehule wrote:

What when session 2 has active transaction? Then to be correct, you should
to wait with index creation to end of transaction.

Session1:
postgres=# create unique index on gtt(x);
CREATE INDEX

Sessin2:
postgres=# explain select * from gtt where x=1;
ERROR: could not create unique index "gtt_x_idx"
DETAIL: Key (x)=(1) is duplicated.

This is little bit unexpected behave (probably nobody expect so any SELECT
fail with error "could not create index" - I understand exactly to reason
and context, but this side effect is something what I afraid.

The more I thinking creation of indexes for GTT on-demand, the more
contractions I see.
So looks like there are only two safe alternatives:
1. Allow DDL for GTT (including index creation) only if there are no other
sessions using this GTT ("using" means that no data was inserted in GTT by
this session). Things can be even more complicated if we take in account
inter-table dependencies (like foreign key constraint).
2. Create indexes for GTT locally.

2) seems to be very contradictory (global table metadata, but private
indexes) and hard to implement because in this case we have to maintain
some private copy of index catalog to keep information about private
indexes.

1) is currently implemented by Wenjing. Frankly speaking I still find such
limitation too restrictive and inconvenient for users. From my point of
view Oracle developers have implemented better compromise. But if I am the
only person voting for such solution, then let's stop this discussion.

Thank you. I respect your opinion.

But in any case I think that calling ambuild to construct index for empty
table is better solution than implementation of all indexes (and still not
solving the problem with custom indexes).

I know nothing about this area - I expect so you and Wenjing will find
good solution.

We have to start with something what is simple, usable, and if it possible
it is well placed to Postgres's architecture.

at @1 .. when all tables are empty for other sessions, then I don't see any
problem. From practical reason, I think so requirement to don't use table
in other sessions is too hard, and I can be nice (maybe it is) if creating
index should not be blocked, but if I create index too late, then index is
for other session (when the table is used) invalid (again it can be done in
future).

I am sure, so there are not end of all days - and there is a space for
future enhancing and testing other variants. I can imagine more different
variations with different advantages/disadvantages. Just for begin I prefer
design that has concept closer to current Postgres.

Regards

Pavel

#144Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#117)
Re: [Proposal] Global temporary tables

čt 30. 1. 2020 v 15:21 odesílatel Pavel Stehule <pavel.stehule@gmail.com>
napsal:

čt 30. 1. 2020 v 15:17 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
napsal:

2020年1月29日 下午9:48,Robert Haas <robertmhaas@gmail.com> 写道:

On Tue, Jan 28, 2020 at 12:12 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>

wrote:

Opinion by Pavel
+ rel->rd_islocaltemp = true; <<<<<<< if this is valid, then the

name of field "rd_islocaltemp" is not probably best

I renamed rd_islocaltemp

I don't see any change?

Rename rd_islocaltemp to rd_istemp in

global_temporary_table_v8-pg13.patch

In view of commit 6919b7e3294702adc39effd16634b2715d04f012, I think
that this has approximately a 0% chance of being acceptable. If you're
setting a field in a way that is inconsistent with the current use of
the field, you're probably doing it wrong, because the field has an
existing purpose to which new code must conform. And if you're not
doing that, then you don't need to rename it.

Thank you for pointing it out.
I've rolled back the rename.
But I still need rd_localtemp to be true, The reason is that
1 GTT The GTT needs to support DML in read-only transactions ,like local
temp table.
2 GTT does not need to hold the lock before modifying the index buffer
,also like local temp table.

Please give me feedback.

maybe some like

rel->rd_globaltemp = true;

and somewhere else

if (rel->rd_localtemp || rel->rd_globaltemp)
{
...
}

I tested this patch again and I am very well satisfied with behave.

what doesn't work still - TRUNCATE statement

postgres=# insert into foo select generate_series(1,10000);
INSERT 0 10000
postgres=# \dt+ foo
List of relations
┌────────┬──────┬───────┬───────┬─────────────┬────────┬─────────────┐
│ Schema │ Name │ Type │ Owner │ Persistence │ Size │ Description │
╞════════╪══════╪═══════╪═══════╪═════════════╪════════╪═════════════╡
│ public │ foo │ table │ pavel │ session │ 384 kB │ │
└────────┴──────┴───────┴───────┴─────────────┴────────┴─────────────┘
(1 row)

postgres=# truncate foo;
TRUNCATE TABLE
postgres=# \dt+ foo
List of relations
┌────────┬──────┬───────┬───────┬─────────────┬───────┬─────────────┐
│ Schema │ Name │ Type │ Owner │ Persistence │ Size │ Description │
╞════════╪══════╪═══════╪═══════╪═════════════╪═══════╪═════════════╡
│ public │ foo │ table │ pavel │ session │ 16 kB │ │
└────────┴──────┴───────┴───────┴─────────────┴───────┴─────────────┘
(1 row)

I expect zero size after truncate.

Regards

Pavel

Show quoted text

Wenjing

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#145曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Pavel Stehule (#144)
Re: [Proposal] Global temporary tables

2020年2月14日 下午5:19,Pavel Stehule <pavel.stehule@gmail.com> 写道:

čt 30. 1. 2020 v 15:21 odesílatel Pavel Stehule <pavel.stehule@gmail.com <mailto:pavel.stehule@gmail.com>> napsal:

čt 30. 1. 2020 v 15:17 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:

2020年1月29日 下午9:48,Robert Haas <robertmhaas@gmail.com <mailto:robertmhaas@gmail.com>> 写道:

On Tue, Jan 28, 2020 at 12:12 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> wrote:

Opinion by Pavel
+ rel->rd_islocaltemp = true; <<<<<<< if this is valid, then the name of field "rd_islocaltemp" is not probably best
I renamed rd_islocaltemp

I don't see any change?

Rename rd_islocaltemp to rd_istemp in global_temporary_table_v8-pg13.patch

In view of commit 6919b7e3294702adc39effd16634b2715d04f012, I think
that this has approximately a 0% chance of being acceptable. If you're
setting a field in a way that is inconsistent with the current use of
the field, you're probably doing it wrong, because the field has an
existing purpose to which new code must conform. And if you're not
doing that, then you don't need to rename it.

Thank you for pointing it out.
I've rolled back the rename.
But I still need rd_localtemp to be true, The reason is that
1 GTT The GTT needs to support DML in read-only transactions ,like local temp table.
2 GTT does not need to hold the lock before modifying the index buffer ,also like local temp table.

Please give me feedback.

maybe some like

rel->rd_globaltemp = true;

and somewhere else

if (rel->rd_localtemp || rel->rd_globaltemp)
{
...
}

I tested this patch again and I am very well satisfied with behave.

what doesn't work still - TRUNCATE statement

postgres=# insert into foo select generate_series(1,10000);
INSERT 0 10000
postgres=# \dt+ foo
List of relations
┌────────┬──────┬───────┬───────┬─────────────┬────────┬─────────────┐
│ Schema │ Name │ Type │ Owner │ Persistence │ Size │ Description │
╞════════╪══════╪═══════╪═══════╪═════════════╪════════╪═════════════╡
│ public │ foo │ table │ pavel │ session │ 384 kB │ │
└────────┴──────┴───────┴───────┴─────────────┴────────┴─────────────┘
(1 row)

postgres=# truncate foo;
TRUNCATE TABLE
postgres=# \dt+ foo
List of relations
┌────────┬──────┬───────┬───────┬─────────────┬───────┬─────────────┐
│ Schema │ Name │ Type │ Owner │ Persistence │ Size │ Description │
╞════════╪══════╪═══════╪═══════╪═════════════╪═══════╪═════════════╡
│ public │ foo │ table │ pavel │ session │ 16 kB │ │
└────────┴──────┴───────┴───────┴─────────────┴───────┴─────────────┘
(1 row)

I expect zero size after truncate.

Thanks for review.

I can explain, I don't think it's a bug.
The current implementation of the truncated GTT retains two blocks of FSM pages.
The same is true for truncating regular tables in subtransactions.
This is an implementation that truncates the table without changing the relfilenode of the table.

Wenjing

Show quoted text

Regards

Pavel

Wenjing

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;
The Enterprise PostgreSQL Company

#146Pavel Stehule
pavel.stehule@gmail.com
In reply to: 曾文旌(义从) (#145)
Re: [Proposal] Global temporary tables

postgres=# insert into foo select generate_series(1,10000);
INSERT 0 10000
postgres=# \dt+ foo
List of relations
┌────────┬──────┬───────┬───────┬─────────────┬────────┬─────────────┐
│ Schema │ Name │ Type │ Owner │ Persistence │ Size │ Description │
╞════════╪══════╪═══════╪═══════╪═════════════╪════════╪═════════════╡
│ public │ foo │ table │ pavel │ session │ 384 kB │ │
└────────┴──────┴───────┴───────┴─────────────┴────────┴─────────────┘
(1 row)

postgres=# truncate foo;
TRUNCATE TABLE
postgres=# \dt+ foo
List of relations
┌────────┬──────┬───────┬───────┬─────────────┬───────┬─────────────┐
│ Schema │ Name │ Type │ Owner │ Persistence │ Size │ Description │
╞════════╪══════╪═══════╪═══════╪═════════════╪═══════╪═════════════╡
│ public │ foo │ table │ pavel │ session │ 16 kB │ │
└────────┴──────┴───────┴───────┴─────────────┴───────┴─────────────┘
(1 row)

I expect zero size after truncate.

Thanks for review.

I can explain, I don't think it's a bug.
The current implementation of the truncated GTT retains two blocks of FSM
pages.
The same is true for truncating regular tables in subtransactions.
This is an implementation that truncates the table without changing the
relfilenode of the table.

This is not extra important feature - now this is little bit a surprise,
because I was not under transaction.

Changing relfilenode, I think, is necessary, minimally for future VACUUM
FULL support.

Regards

Pavel Stehule

Show quoted text

Wenjing

Regards

Pavel

Wenjing

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#147曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Konstantin Knizhnik (#142)
1 attachment(s)
Re: [Proposal] Global temporary tables

2020年2月9日 下午8:05,Konstantin Knizhnik <k.knizhnik@postgrespro.ru> 写道:

On 07.02.2020 21:37, Pavel Stehule wrote:

What when session 2 has active transaction? Then to be correct, you should to wait with index creation to end of transaction.

Session1:
postgres=# create unique index on gtt(x);
CREATE INDEX

Sessin2:
postgres=# explain select * from gtt where x=1;
ERROR: could not create unique index "gtt_x_idx"
DETAIL: Key (x)=(1) is duplicated.

This is little bit unexpected behave (probably nobody expect so any SELECT fail with error "could not create index" - I understand exactly to reason and context, but this side effect is something what I afraid.

The more I thinking creation of indexes for GTT on-demand, the more contractions I see.
So looks like there are only two safe alternatives:
1. Allow DDL for GTT (including index creation) only if there are no other sessions using this GTT ("using" means that no data was inserted in GTT by this session). Things can be even more complicated if we take in account inter-table dependencies (like foreign key constraint).
2. Create indexes for GTT locally.

2) seems to be very contradictory (global table metadata, but private indexes) and hard to implement because in this case we have to maintain some private copy of index catalog to keep information about private indexes.

1) is currently implemented by Wenjing. Frankly speaking I still find such limitation too restrictive and inconvenient for users. From my point of view Oracle developers have implemented better compromise. But if I am the only person voting for such solution, then let's stop this discussion.
But in any case I think that calling ambuild to construct index for empty table is better solution than implementation of all indexes (and still not solving the problem with custom indexes).

I made some improvements
1 Support for all indexes on GTT (using index_build build empty index).
2 Remove some ugly code in md.c bufmgr.c

Please give me feedback.

Wenjing

Attachments:

global_temporary_table_v11-pg13.patchapplication/octet-stream; name=global_temporary_table_v11-pg13.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 79430d2..babb5f3 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -158,6 +158,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * For global temp table only
+	 * use ShareUpdateExclusiveLock for ensure safety
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temp table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},	
 	/* list terminator */
 	{{NULL}}
 };
@@ -1486,6 +1499,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1586,13 +1601,18 @@ build_reloptions(Datum reloptions, bool validate,
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	/*
 	 * There are no options for partitioned tables yet, but this is able to do
 	 * some validation.
 	 */
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index dd975b1..1610e7d 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1013,7 +1013,9 @@ gistGetFakeLSN(Relation rel)
 {
 	static XLogRecPtr counter = FirstNormalUnloggedLSN;
 
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	/* global temp is same as local temp table */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 4871b7f..16b00c9 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -149,7 +149,9 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	/* global temp table is same as local temp table */
+	if (!(index->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		index->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 3fa4b76..b54882d 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -598,7 +598,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -651,7 +651,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index a23cdef..a7d7098 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -62,6 +62,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -399,9 +400,9 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
 	Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
-	/* not every AM requires these to be valid, but heap does */
-	Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
-	Assert(MultiXactIdIsValid(onerel->rd_rel->relminmxid));
+	/* not every AM requires these to be valid, but regular heap does */
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ MultiXactIdIsValid(onerel->rd_rel->relminmxid));
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index f05cbe7..f2d3b83 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -27,6 +27,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -589,6 +590,10 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(rel->rd_node.relNode))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 3813ead..fd731d8 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6325,6 +6325,10 @@ StartupXLOG(void)
 	else
 		recoveryTargetTLI = ControlFile->checkPointCopy.ThisTimeLineID;
 
+	/* clean temp relation files */
+	if (max_active_gtt > 0)
+		RemovePgTempFiles();
+
 	/*
 	 * Check for signal files, and if so set up state for offline recovery
 	 */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index f8f0b48..dc8bbb1 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -42,6 +42,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 7d6acae..3be8d63 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -393,7 +393,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/* global temp table is same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index e31478b..5850605 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -427,7 +429,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -957,6 +959,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -995,8 +998,18 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/* global temp table not remember transaction info in catalog */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1358,6 +1371,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1938,6 +1952,13 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/* We allow to drop global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(rel->rd_node))
+			elog(ERROR, "can not drop relation when other backend attached this global temp table");
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3165,7 +3186,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3176,8 +3197,12 @@ RelationTruncateIndexes(Relation heapRelation)
 		Relation	currentIndex;
 		IndexInfo  *indexInfo;
 
+		if (RELATION_IS_GLOBAL_TEMP(heapRelation) &&
+			!gtt_storage_attached(indexId))
+			continue;
+
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3223,8 +3248,13 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		/* truncate global temp table only need RowExclusiveLock */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3257,6 +3287,8 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
+	bool		truncate_toastrel = false;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3265,23 +3297,40 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	toastrelid = rel->rd_rel->reltoastrelid;
+
+	/*
+	 * Truncate global temp table only need RowExclusiveLock
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		lockmode = RowExclusiveLock;
+		if (OidIsValid(toastrelid) &&
+			gtt_storage_attached(toastrelid))
+			truncate_toastrel = true;
+	}
+	else if (OidIsValid(toastrelid))
+		truncate_toastrel = true;
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
-	toastrelid = rel->rd_rel->reltoastrelid;
-	if (OidIsValid(toastrelid))
+	if (truncate_toastrel)
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(rel, 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 8880586..a23b499 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -52,6 +52,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -877,6 +878,19 @@ index_create(Relation heapRelation,
 						indexRelationName, RelationGetRelationName(heapRelation))));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temp table use concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot create indexes on global temporary tables using concurrent mode")));
+
+		/* if global temp table not init storage, then skip build index */
+		if (!gtt_storage_attached(heapRelation->rd_node.relNode))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -2054,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.
@@ -2660,6 +2681,11 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	/* update index stats into localhash and rel_rd_rel for global temp table */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		is_gtt = true;
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2745,21 +2771,35 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
+		if (is_gtt)
+			rel->rd_rel->relpages = (int32) relpages;
+		else if (rd_rel->relpages != (int32) relpages)
 		{
 			rd_rel->relpages = (int32) relpages;
 			dirty = true;
 		}
-		if (rd_rel->reltuples != (float4) reltuples)
+
+		if (is_gtt)
+			rel->rd_rel->reltuples = (float4) reltuples;
+		else if (rd_rel->reltuples != (float4) reltuples)
 		{
 			rd_rel->reltuples = (float4) reltuples;
 			dirty = true;
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+
+		if (is_gtt)
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+		else if (rd_rel->relallvisible != (int32) relallvisible)
 		{
 			rd_rel->relallvisible = (int32) relallvisible;
 			dirty = true;
 		}
+
+		if (is_gtt)
+		{
+			up_gtt_relstats(rel, relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
+		}
 	}
 
 	/*
@@ -2873,6 +2913,15 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, index, val);
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		if (!gtt_storage_attached(indexRelation->rd_node.relNode))
+		{
+			gtt_force_enable_index(indexRelation);
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3468,6 +3517,15 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 				 errmsg("cannot reindex temporary tables of other sessions")));
 
 	/*
+	 * Because global temp table cannot change relfilenode
+	 * no support reindex on global temp table yet.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(iRel))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot reindex global temporary tables")));
+
+	/*
 	 * Also check for active uses of the index in the current transaction; we
 	 * don't want to reindex underneath an open indexscan.
 	 */
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index e70243a..301da79 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -647,6 +647,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		/* global temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp relations in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index fddfbf1..671c614 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -26,6 +26,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -75,7 +76,7 @@ static PendingRelDelete *pendingDeletes = NULL; /* head of linked list */
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -85,6 +86,8 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		/* global temp table use same storage strategy as local temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -117,6 +120,10 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/* remember global temp table storage info to localhash */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel)
+		remember_gtt_storage_info(rnode, rel);
+
 	return srel;
 }
 
@@ -486,8 +493,15 @@ smgrDoPendingDeletes(bool isCommit)
 		smgrdounlinkall(srels, nrels, false);
 
 		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* clean global temp table flags when transaction commit or rollback */
+			if (SmgrIsTemp(srels[i]) && 
+				gtt_storage_attached(srels[i]->smgr_rnode.node.relNode))
+				forget_gtt_storage_info(srels[i]->smgr_rnode.node.relNode);
+		}
+
 		pfree(srels);
 	}
 }
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..e877b0c
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1260 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  code to create and destroy physical storage for global temparary table
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_relstats_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+typedef struct gtt_ctl_data
+{
+	LWLock			lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct
+{
+	RelFileNode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+	Oid			relid;
+
+	Oid			spcnode;
+	/* pg_class stat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+	char		relkind;
+	bool		on_commit_delete;
+
+	/* pg_statistic */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(RelFileNode rnode);
+static void gtt_storage_checkout(RelFileNode rnode, bool skiplock);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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;
+	int			natts = 0;
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_gtt to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temp table yet");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create stroage file", RelationGetRelationName(rel));
+
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		/* First time through: initialize the hash table */
+		HASHCTL		ctl;
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		gtt_storage_local_hash =
+			hash_create("global temp relation table",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_relstats_context =
+			AllocSetContextCreate(CacheMemoryContext,
+							"gtt relstats context",
+							ALLOCSET_DEFAULT_SIZES);
+	}
+
+	/* Look up or create an entry */
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_ENTER, &found);
+
+	if (found)
+	{
+		elog(ERROR, "backend %d relid %u already exists in global temp table local hash",
+					MyBackendId, relid);
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+	entry->spcnode = rnode.spcNode;
+	entry->relpages = 0;
+	entry->reltuples = 0;
+	entry->relallvisible = 0;
+	entry->relkind = rel->rd_rel->relkind;
+	natts = RelationGetNumberOfAttributes(rel);
+	entry->natts = natts;
+	entry->attnum = palloc0(sizeof(int) * natts);
+	entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+	/* only heap contain transaction information */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+		{
+			entry->on_commit_delete = true;
+			register_on_commit_action(rel->rd_node.relNode, ONCOMMIT_DELETE_ROWS);
+		}
+
+		entry->relfrozenxid = RecentXmin;
+		entry->relminmxid = GetOldestMultiXactId();
+		insert_gtt_relfrozenxid_to_ordered_list(entry->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+		gtt_storage_checkin(rnode);
+	}
+	else
+	{
+		entry->on_commit_delete = false;
+		entry->relfrozenxid = 0;
+		entry->relminmxid = 0;
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+void
+forget_gtt_storage_info(Oid relid)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry)
+	{
+		int		i;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			RelFileNode rnode;
+
+			rnode.spcNode = entry->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = entry->relid;
+
+			gtt_storage_checkout(rnode, false);
+
+			Assert(TransactionIdIsNormal(entry->relfrozenxid));
+			remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+
+		for (i = 0; i < entry->natts; i++)
+		{
+			if (entry->att_stat_tups[i])
+			{
+				heap_freetuple(entry->att_stat_tups[i]);
+				entry->att_stat_tups[i] = NULL;
+			}
+		}
+
+		pfree(entry->attnum);
+		pfree(entry->att_stat_tups);
+	}
+
+	hash_search(gtt_storage_local_hash,
+			(void *) &(relid), HASH_REMOVE, NULL);
+
+	return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool found;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (gtt_storage_local_hash == NULL)
+		return false;
+
+	hash_search(gtt_storage_local_hash,
+			(void *) &(relid), HASH_FIND, &found);
+
+	return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS				status;
+	gtt_local_hash_entry		*entry;
+	int			nrels = 0,
+				maxrels = 0;
+	SMgrRelation	*srels = NULL;
+	RelFileNode		*rnodes = NULL;
+	char			*relkinds = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		SMgrRelation srel;
+		RelFileNode rnode;
+
+		rnode.spcNode = entry->spcnode;
+		rnode.dbNode = MyDatabaseId;
+		rnode.relNode = entry->relid;
+
+		srel = smgropen(rnode, MyBackendId);
+
+		/* allocate the initial array, or extend it, if needed */
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			srels = palloc(sizeof(SMgrRelation) * maxrels);
+			rnodes = palloc(sizeof(RelFileNode) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+			rnodes = repalloc(rnodes, sizeof(RelFileNode) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		srels[nrels] = srel;
+		rnodes[nrels] = rnode;
+		relkinds[nrels] = entry->relkind;
+		nrels++;
+	}
+
+	if (nrels > 0)
+	{
+		int i;
+
+		smgrdounlinkall(srels, nrels, false);
+
+		LWLockAcquire(&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.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (gtt_storage_local_hash == NULL)
+		return false;
+
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_FIND, &found);
+
+	if (!found)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	if (relpages)
+		*relpages = entry->relpages;
+
+	if (reltuples)
+		*reltuples = entry->reltuples;
+
+	if (relallvisible)
+		*relallvisible = entry->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = entry->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = entry->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	MemoryContext			oldcontext;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+						(void *) &reloid, HASH_FIND, &found);
+
+	if (!found)
+		return;
+
+	/* todo */
+	if (entry->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	Assert(entry->relid == reloid);
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == 0)
+		{
+			entry->attnum[i] = attnum;
+			break;
+		}
+		else if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < entry->natts);
+	Assert(entry->att_stat_tups[i] == NULL);
+	entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+						(void *) &reloid, HASH_FIND, &found);
+
+	if (!found)
+		return NULL;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			return entry->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int			i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	HeapTuple	tuple;
+	Relation	rel = NULL;
+	int		attnum = PG_GETARG_INT32(1);
+	Oid		reloid = PG_GETARG_OID(0);
+	char		rel_persistence;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	  tupdesc;
+	MemoryContext oldcontext;
+	Tuplestorestate *tupstore;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = table_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+
+	table_close(rel, NoLock);
+	table_close(pg_tatistic, AccessShareLock);
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate	*tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Relation	rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	if (get_gtt_relstats(reloid,
+						&relpages, &reltuples, &relallvisible,
+						&relfrozenxid, &relminmxid))
+	{
+		Datum	values[5];
+		bool	isnull[5];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = Int32GetDatum(relpages);
+		values[1] = Float4GetDatum((float4)reltuples);
+		values[2] = Int32GetDatum(relallvisible);
+		values[3] = UInt32GetDatum(relfrozenxid);
+		values[4] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+
+	tuplestore_donestoring(tupstore);
+	table_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(rel->rd_node);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	table_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(indexOid));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid relOid = index->rd_index->indrelid;
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	if (gtt_storage_attached(relOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int			i;
+	Oid			toastrelid;
+	List		*seqlist = NIL;
+	ListCell	*seqcell = NULL;
+
+	if (operation != CMD_INSERT)
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_attached(relation->rd_node.relNode))
+		return;
+
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+
+		index_build(relation, index, info, true, false);
+	}
+
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid 		indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo 	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	seqlist = getOwnedSequences(RelationGetRelid(relation));
+	foreach(seqcell, seqlist)
+	{
+		Oid 		seq_relid = lfirst_oid(seqcell);
+		Relation	seq_rel;
+
+		seq_rel = relation_open(seq_relid, RowExclusiveLock);
+	
+		/* This check must match AlterSequence! */
+		if (!pg_class_ownercheck(seq_relid, GetUserId()))
+			aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SEQUENCE,
+						   RelationGetRelationName(seq_rel));
+
+		RelationCreateStorage(seq_rel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seq_rel);
+		relation_close(seq_rel, NoLock);
+	}
+
+	return;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index f681aaf..e1fa3ec 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM 
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c4420dd..977a984 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -586,14 +587,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 		 * pg_statistic for columns we didn't process, we leave them alone.)
 		 */
 		update_attstats(RelationGetRelid(onerel), inh,
-						attr_cnt, vacattrstats);
+						attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
 
 		for (ind = 0; ind < nindexes; ind++)
 		{
 			AnlIndexData *thisdata = &indexdata[ind];
 
 			update_attstats(RelationGetRelid(Irel[ind]), false,
-							thisdata->attr_cnt, thisdata->vacattrstats);
+							thisdata->attr_cnt, thisdata->vacattrstats,
+							RelationGetRelPersistence(Irel[ind]));
 		}
 
 		/*
@@ -1456,7 +1458,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
  *		by taking a self-exclusive lock on the relation in analyze_rel().
  */
 static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
 {
 	Relation	sd;
 	int			attno;
@@ -1558,31 +1560,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/* Update column statistic to localhash, not catalog */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 11ce1bb..552fd87 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -391,6 +391,12 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/* not support cluster global temp table yet */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("not support cluster global temporary tables yet")));
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 40a8ec1..a3255a1 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -27,6 +27,7 @@
 #include "catalog/dependency.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -1062,7 +1063,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, stmt->filename, stmt->is_program,
@@ -2840,6 +2841,8 @@ CopyFrom(CopyState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	estate->es_result_relations = resultRelInfo;
 	estate->es_num_result_relations = 1;
 	estate->es_result_relation_info = resultRelInfo;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index ec20ba3..a29cc00 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2614,6 +2614,16 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 			!isTempNamespace(classtuple->relnamespace))
 			continue;
 
+		/* not support reindex on global temp table, so skip it */
+		if (classtuple->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			ereport(WARNING,
+				(errmsg("global temp table \"%s.%s\" skip reindexed",
+					get_namespace_name(get_rel_namespace(relid)),
+					get_rel_name(relid))));
+			continue;
+		}
+
 		/* Check user/system classification, and optionally skip */
 		if (objectKind == REINDEX_OBJECT_SYSTEM &&
 			!IsSystemClass(relid, classtuple))
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 329ab84..dfa257c 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -96,7 +96,8 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 	 * transaction.
 	 */
 	relpersistence = get_rel_persistence(relid);
-	if (relpersistence == RELPERSISTENCE_TEMP)
+	if (relpersistence == RELPERSISTENCE_TEMP ||
+		relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
 
 	/* Check permissions. */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..c55dcf7 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -94,7 +94,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +108,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -222,7 +223,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* now initialize the sequence's data */
 	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -327,7 +328,7 @@ ResetSequence(Oid seq_relid)
 	/*
 	 * Insert the modified tuple into the new storage file.
 	 */
-	fill_seq_with_data(seq_rel, tuple);
+	fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -340,17 +341,21 @@ ResetSequence(Oid seq_relid)
  * Initialize a sequence's relation with the specified tuple as content
  */
 static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 	sequence_magic *sm;
 	OffsetNumber offnum;
+	bool lockBuffer = false;
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ReadBuffer(rel, P_NEW);
-	Assert(BufferGetBlockNumber(buf) == 0);
+	if (buf == InvalidBuffer)
+	{
+		buf = ReadBuffer(rel, P_NEW);
+		Assert(BufferGetBlockNumber(buf) == 0);
+		lockBuffer = true;
+	}
 
 	page = BufferGetPage(buf);
 
@@ -360,7 +365,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* Now insert sequence tuple */
 
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+	if (lockBuffer)
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +416,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	END_CRIT_SECTION();
 
-	UnlockReleaseBuffer(buf);
+	if (lockBuffer)
+		UnlockReleaseBuffer(buf);
 }
 
 /*
@@ -502,7 +509,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * Insert the modified tuple into the new storage file.
 		 */
-		fill_seq_with_data(seqrel, newdatatuple);
+		fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
 	}
 
 	/* process OWNED BY if given */
@@ -611,7 +618,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +943,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1178,6 +1185,25 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
 	LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
 
 	page = BufferGetPage(*buf);
+	if (GlobalTempRelationPageIsNotInitialized(rel, page))
+	{
+		/* Initialize sequence for global temporary tables */
+		Datum		value[SEQ_COL_LASTCOL] = {0};
+		bool		null[SEQ_COL_LASTCOL] = {false};
+		HeapTuple	tuple;
+		int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+		/*
+		 * last_value from pg_sequence.seqstart
+		 * log_cnt = 0
+		 * is_called = false
+		 */
+		value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+		tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+		fill_seq_with_data(rel, tuple, *buf);
+		heap_freetuple(tuple);
+	}
 	sm = (sequence_magic *) PageGetSpecialPointer(page);
 
 	if (sm->magic != SEQ_MAGIC)
@@ -1954,3 +1980,23 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index b7c8d66..6d1e054 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -553,6 +554,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 								  Relation partitionTbl);
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static bool has_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -598,6 +600,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
@@ -608,8 +611,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Check consistency of arguments
 	 */
+	/* global temp table same as local temp table */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !(stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+			 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -639,7 +644,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	/* global temp table same as local temp table */
+	if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+		 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -740,6 +747,57 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* global temp table */
+	has_oncommit_clause = has_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* inherit table or parition table inherit on commit property from parent table*/
+		if (inheritOids && stmt->oncommit == ONCOMMIT_NOOP && !has_oncommit_clause)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			if (RELATION_GTT_ON_COMMIT_DELETE(relation))
+				stmt->oncommit = ONCOMMIT_DELETE_ROWS;
+			else
+				stmt->oncommit = ONCOMMIT_PRESERVE_ROWS;
+			table_close(relation, NoLock);
+		}
+
+		if (has_oncommit_clause)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "can not defeine global temp table with on commit and with clause at same time");
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temp table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (has_oncommit_clause)
+		elog(ERROR, "regular table cannot specifie on_commit_delete_rows");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1824,7 +1882,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);
@@ -3392,6 +3451,13 @@ RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal, bo
 	 * specially.
 	 */
 	targetrelation = relation_open(myrelid, is_index ? ShareUpdateExclusiveLock : AccessExclusiveLock);
+
+	if (RELATION_IS_GLOBAL_TEMP(targetrelation))
+	{
+		if (is_other_backend_use_gtt(targetrelation->rd_node))
+			elog(ERROR, "can not rename relation when other backend attached this global temp table");
+	}
+
 	namespaceId = RelationGetNamespace(targetrelation);
 
 	/*
@@ -3567,6 +3633,13 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(rel->rd_node))
+			elog(ERROR, "can not alter relation when other backend attached this global temp table");
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -8140,6 +8213,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),
@@ -8179,6 +8259,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		/* 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;
 	}
 
 	/*
@@ -13221,7 +13307,7 @@ index_copy_data(Relation rel, RelFileNode newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
@@ -14628,7 +14714,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",
@@ -17269,3 +17357,20 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
 		table_close(rel, NoLock);
 	}
 }
+
+static bool
+has_oncommit_option(List *options)
+{
+	ListCell   *listptr;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (pg_strcasecmp(def->defname, "on_commit_delete_rows") == 0)
+			return true;
+	}
+
+	return false;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index d625d17..69ad24f 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/vacuum.h"
@@ -1217,6 +1218,17 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	 /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		is_gtt = true;
+		up_gtt_relstats(relation,
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1230,17 +1242,26 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relpages = (int32) num_pages;
+	else if (pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (is_gtt)
+		relation->rd_rel->reltuples = (float4) num_tuples;
+	else if (pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	else if (pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1285,7 +1306,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1296,7 +1318,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1394,6 +1417,10 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/* global temp table relstats not in pg_class */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1451,6 +1478,25 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/*
+	 * Global temp table get frozenxid from MyProc
+	 * to avoid the vacuum truncate clog that gtt need.
+	 */
+	if (max_active_gtt > 0)
+	{
+		TransactionId oldest_gtt_frozenxid =
+			list_all_session_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid) &&
+			TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+		{
+			ereport(WARNING,
+				(errmsg("global temp table oldest FrozenXid is far in the past"),
+				 errhint("please truncate them or kill those sessions that use them.")));
+			newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index ee5c3a6..0d7a2d4 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandTag((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index c13b1d3..253054d 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
@@ -543,6 +544,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	init_gtt_storage(mtstate->operation, leaf_part_rri);
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 59d1a31..fc48723 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,6 +42,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -2347,6 +2348,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			ExecOpenIndices(resultRelInfo,
 							node->onConflictAction != ONCONFLICT_NONE);
 
+		init_gtt_storage(operation, resultRelInfo);
+
 		/*
 		 * If this is an UPDATE and a BEFORE UPDATE trigger is present, the
 		 * trigger itself might modify the partition-key values. So arrange
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 905bbe7..170963d 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -591,6 +591,8 @@ static void
 set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 						  RangeTblEntry *rte)
 {
+	char relpersistence;
+
 	/*
 	 * The flag has previously been initialized to false, so we can just
 	 * return if it becomes clear that we can't safely set it.
@@ -618,7 +620,11 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			/* global temp table is same as local temp table */
+			relpersistence = get_rel_persistence(rte->relid);
+
+			if (relpersistence == RELPERSISTENCE_TEMP ||
+				relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b44efd6..14dbaaf 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6307,7 +6307,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
+	/* global temp table is same as local temp table */
 	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 748bebf..a5ddbcd 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2579,6 +2579,11 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("materialized views must not use temporary tables or views")));
 
+		if (is_query_using_gtt(query))
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("materialized views must not use global temporary tables or views")));
+
 		/*
 		 * A materialized view would either need to save parameters for use in
 		 * maintaining/loading the data or prohibit them entirely.  The latter
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 1b0edf5..492639f 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3290,17 +3290,11 @@ OptTemp:	TEMPORARY					{ $$ = RELPERSISTENCE_TEMP; }
 			| LOCAL TEMP				{ $$ = RELPERSISTENCE_TEMP; }
 			| GLOBAL TEMPORARY
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
-					$$ = RELPERSISTENCE_TEMP;
+					$$ = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
-					$$ = RELPERSISTENCE_TEMP;
+					$$ = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED					{ $$ = RELPERSISTENCE_UNLOGGED; }
 			| /*EMPTY*/					{ $$ = RELPERSISTENCE_PERMANENT; }
@@ -11591,19 +11585,13 @@ OptTempTableName:
 				}
 			| GLOBAL TEMPORARY opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED opt_table qualified_name
 				{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b875a50..123945b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index ee2d2b5..9c9abaa 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 6d1f28c..ed837d1 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2086,6 +2086,11 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* autovacuum skip vacuum global temp table */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2152,7 +2157,9 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		/* autovacuum skip vacuum global temp table */
+		if (classForm->relpersistence == RELPERSISTENCE_TEMP || 
+			classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 5880054..98f7b43 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -52,6 +53,7 @@
 #include "utils/rel.h"
 #include "utils/resowner_private.h"
 #include "utils/timestamp.h"
+#include "utils/guc.h"
 
 
 /* Note: these two macros only work on shared buffers, not local ones! */
@@ -2809,6 +2811,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(relation->rd_node.relNode))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d5..7adb969 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -21,6 +21,7 @@
 #include "access/nbtree.h"
 #include "access/subtrans.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, active_gtt_shared_hash_size());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* global temporary table */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 4a5b26c..9754168 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -62,6 +62,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -4087,3 +4088,77 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temp table.
+ */
+int
+list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct *arrayP = procArray;
+	TransactionId result = InvalidTransactionId;
+	int			index;
+	int			flags = 0;
+	int			i = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+		*n = 0;
+	}
+
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		volatile PGXACT *pgxact = &allPgXact[pgprocno];
+
+		if (pgxact->vacuumFlags & flags)
+			continue;
+
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->session_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->session_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->session_gtt_frozenxid, result))
+				result = proc->session_gtt_frozenxid;
+
+			if (max_size > 0)
+			{
+				pids[i] = proc->pid;
+				xids[i] = proc->session_gtt_frozenxid;
+				i++;
+			}
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (max_size > 0)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index eb321f7..3893cef 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 8406644..0d99321 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -1008,6 +1008,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 0be26fe..c169c99 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -112,6 +112,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -4570,12 +4571,25 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -4700,15 +4714,27 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6090,6 +6116,7 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	{
 		/* Simple variable --- look to stats for the underlying table */
 		RangeTblEntry *rte = planner_rt_fetch(index->rel->relid, root);
+		char	rel_persistence = get_rel_persistence(rte->relid);
 
 		Assert(rte->rtekind == RTE_RELATION);
 		relid = rte->relid;
@@ -6107,6 +6134,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6118,6 +6152,8 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	}
 	else
 	{
+		char	rel_persistence = get_rel_persistence(index->indexoid);
+
 		/* Expression --- maybe there are stats for the index itself */
 		relid = index->indexoid;
 		colnum = 1;
@@ -6133,6 +6169,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7046,6 +7089,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		/* attempt to lookup stats in relation for this index column */
 		if (attnum != 0)
 		{
+			char	rel_persistence = get_rel_persistence(rte->relid);
+
 			/* Simple variable -- look to stats for the underlying table */
 			if (get_relation_stats_hook &&
 				(*get_relation_stats_hook) (root, rte, attnum, &vardata))
@@ -7058,6 +7103,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7070,6 +7123,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		}
 		else
 		{
+			char	rel_persistence = get_rel_persistence(index->indexoid);
+
 			/*
 			 * Looks like we've found an expression column in the index. Let's
 			 * see if there's any stats for it.
@@ -7089,6 +7144,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index fb0599f..13f4d8a 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -2878,6 +2879,18 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index ff70326..1cf7063 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -63,6 +63,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1127,6 +1128,24 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1181,6 +1200,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			gtt_fix_index_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -2220,6 +2240,8 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		gtt_fix_index_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3316,6 +3338,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3430,6 +3456,9 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
 
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+		elog(ERROR, "global temp table does not allow setting new relfilenode");
+
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
@@ -3470,7 +3499,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 				/* handle these directly, at least for now */
 				SMgrRelation srel;
 
-				srel = RelationCreateStorage(newrnode, persistence);
+				srel = RelationCreateStorage(newrnode, persistence, relation);
 				smgrclose(srel);
 			}
 			break;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 8228e1f..c00ff94 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -140,6 +140,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temp table feature.
+ * global temp table define can still storage in catalog
+ * just can not use.
+ * num > 0 means database can management num active global temp table.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2047,6 +2059,15 @@ static struct config_bool ConfigureNamesBool[] =
 static struct config_int ConfigureNamesInt[] =
 {
 	{
+		{"max_active_global_temporary_table", PGC_POSTMASTER, UNGROUPED,
+			gettext_noop("max active global temporary table."),
+			NULL
+		},
+		&max_active_gtt,
+		DEFAULT_NUM_ACTIVE_GTT, MIN_NUM_ACTIVE_GTT, MAX_NUM_ACTIVE_GTT,
+		NULL, NULL, NULL
+	},
+	{
 		{"archive_timeout", PGC_SIGHUP, WAL_ARCHIVING,
 			gettext_noop("Forces a switch to the next WAL file if a "
 						 "new file has not been started within N seconds."),
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ec3e2c6..d3697d2 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15583,6 +15583,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	{
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
+		char		*table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15634,9 +15635,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 			binary_upgrade_set_pg_class_oids(fout, q,
 											 tbinfo->dobj.catId.oid, false);
 
+		if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED)
+			table_type = "UNLOGGED ";
+		else if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			table_type = "GLOBAL TEMPORARY ";
+		else
+			table_type = "";
+
 		appendPQExpBuffer(q, "CREATE %s%s %s",
-						  tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ?
-						  "UNLOGGED " : "",
+						  table_type,
 						  reltypename,
 						  qualrelname);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index f3c7eb9..28134e2 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3723,7 +3723,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index dc03fbd..8bd6d09 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1016,6 +1016,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2378,6 +2380,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2586,6 +2591,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 78b33b2..4be89f6 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -168,6 +168,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 0345118..a809c7d 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5504,6 +5504,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4191',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4192',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{relid,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4193',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4194',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 048003c..af48cdf 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -19,7 +19,7 @@
 #include "storage/smgr.h"
 #include "utils/relcache.h"
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationTruncate(Relation rel, BlockNumber nblocks);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..3afd71d
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,43 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid);
+extern bool is_other_backend_use_gtt(RelFileNode node);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(RelFileNode node);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Relation relation,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void gtt_force_enable_index(Relation index);
+extern void gtt_fix_index_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 3f88683..f870e9a 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 8fda8e4..172eae9 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_TBM,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_SXACT,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index d217801..8adde87 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -117,6 +117,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId session_gtt_frozenxid;	/* session level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ce93ace..0f7262e 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -281,6 +281,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 44ed04d..6b6d2da 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	struct SMgrRelationData *rd_smgr;	/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -277,6 +277,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -535,11 +536,13 @@ typedef struct ViewOptions
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -547,6 +550,7 @@ typedef struct ViewOptions
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -559,6 +563,14 @@ typedef struct ViewOptions
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is either local temp relation of this session
+ * 		or global temp relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -602,6 +614,17 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..50ca9ac
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,7 @@
+reset search_path;
+drop schema gtt cascade;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..28acdfb
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,276 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+ERROR:  cannot create indexes on global temporary tables using concurrent mode
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+ERROR:  not support cluster global temporary tables yet
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ok
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  regular table cannot specifie on_commit_delete_rows
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  can not defeine global temp table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test;
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  referenced relation "products" is not a global temp table
+-- ERROR
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  referenced relation "products" is not a global temp table
+-- ERROR
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 1)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 300)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 17 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to table gtt_function.gt1
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..30d8a7b
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,84 @@
+set search_path=gtt,sys;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..850ef3e
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,161 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          475136 |                 974848
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |           409600 |        409600 |                 409600
+(2 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..9fe5fd4
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,9 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..0e21b30
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,76 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+(1 row)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+(1 row)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 634f825..6e1297a 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1349,6 +1349,93 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d2b17dd..80e577f 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..f3cf710
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,6 @@
+
+
+reset search_path;
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..e4ce357
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,186 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ok
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test;
+
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ERROR
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ERROR
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d7d81de
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,42 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..5203c2b
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,76 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..2f4d883
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,16 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..f041892
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,42 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
#148曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Pavel Stehule (#146)
Re: [Proposal] Global temporary tables

2020年2月15日 下午6:06,Pavel Stehule <pavel.stehule@gmail.com> 写道:

postgres=# insert into foo select generate_series(1,10000);
INSERT 0 10000
postgres=# \dt+ foo
List of relations
┌────────┬──────┬───────┬───────┬─────────────┬────────┬─────────────┐
│ Schema │ Name │ Type │ Owner │ Persistence │ Size │ Description │
╞════════╪══════╪═══════╪═══════╪═════════════╪════════╪═════════════╡
│ public │ foo │ table │ pavel │ session │ 384 kB │ │
└────────┴──────┴───────┴───────┴─────────────┴────────┴─────────────┘
(1 row)

postgres=# truncate foo;
TRUNCATE TABLE
postgres=# \dt+ foo
List of relations
┌────────┬──────┬───────┬───────┬─────────────┬───────┬─────────────┐
│ Schema │ Name │ Type │ Owner │ Persistence │ Size │ Description │
╞════════╪══════╪═══════╪═══════╪═════════════╪═══════╪═════════════╡
│ public │ foo │ table │ pavel │ session │ 16 kB │ │
└────────┴──────┴───────┴───────┴─────────────┴───────┴─────────────┘
(1 row)

I expect zero size after truncate.

Thanks for review.

I can explain, I don't think it's a bug.
The current implementation of the truncated GTT retains two blocks of FSM pages.
The same is true for truncating regular tables in subtransactions.
This is an implementation that truncates the table without changing the relfilenode of the table.

This is not extra important feature - now this is little bit a surprise, because I was not under transaction.

Changing relfilenode, I think, is necessary, minimally for future VACUUM FULL support.

Not allowing relfilenode changes is the current limit.
I think can improve on it. But ,This is a bit complicated.
so I'd like to know the necessity of this improvement.
Could you give me more details?

Show quoted text

Regards

Pavel Stehule

Wenjing

Regards

Pavel

Wenjing

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;
The Enterprise PostgreSQL Company

#149Pavel Stehule
pavel.stehule@gmail.com
In reply to: 曾文旌(义从) (#148)
Re: [Proposal] Global temporary tables

ne 16. 2. 2020 v 16:15 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
napsal:

2020年2月15日 下午6:06,Pavel Stehule <pavel.stehule@gmail.com> 写道:

postgres=# insert into foo select generate_series(1,10000);

INSERT 0 10000
postgres=# \dt+ foo
List of relations
┌────────┬──────┬───────┬───────┬─────────────┬────────┬─────────────┐
│ Schema │ Name │ Type │ Owner │ Persistence │ Size │ Description │
╞════════╪══════╪═══════╪═══════╪═════════════╪════════╪═════════════╡
│ public │ foo │ table │ pavel │ session │ 384 kB │ │
└────────┴──────┴───────┴───────┴─────────────┴────────┴─────────────┘
(1 row)

postgres=# truncate foo;
TRUNCATE TABLE
postgres=# \dt+ foo
List of relations
┌────────┬──────┬───────┬───────┬─────────────┬───────┬─────────────┐
│ Schema │ Name │ Type │ Owner │ Persistence │ Size │ Description │
╞════════╪══════╪═══════╪═══════╪═════════════╪═══════╪═════════════╡
│ public │ foo │ table │ pavel │ session │ 16 kB │ │
└────────┴──────┴───────┴───────┴─────────────┴───────┴─────────────┘
(1 row)

I expect zero size after truncate.

Thanks for review.

I can explain, I don't think it's a bug.
The current implementation of the truncated GTT retains two blocks of FSM
pages.
The same is true for truncating regular tables in subtransactions.
This is an implementation that truncates the table without changing the
relfilenode of the table.

This is not extra important feature - now this is little bit a surprise,
because I was not under transaction.

Changing relfilenode, I think, is necessary, minimally for future VACUUM
FULL support.

Not allowing relfilenode changes is the current limit.
I think can improve on it. But ,This is a bit complicated.
so I'd like to know the necessity of this improvement.
Could you give me more details?

I don't think so GTT without support of VACUUM FULL can be accepted. Just
due consistency.

Regards

Pavel

Show quoted text

Regards

Pavel Stehule

Wenjing

Regards

Pavel

Wenjing

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#150Prabhat Sahu
prabhat.sahu@enterprisedb.com
In reply to: Pavel Stehule (#149)
Re: [Proposal] Global temporary tables

Hi,
I have started testing the "Global temporary table" feature,
from "gtt_v11-pg13.patch". Below is my findings:

-- session 1:
postgres=# create global temporary table gtt1(a int);
CREATE TABLE

-- seeeion 2:
postgres=# truncate gtt1 ;
ERROR: could not open file "base/13585/t3_16384": No such file or directory

is it expected?

On Sun, Feb 16, 2020 at 8:53 PM Pavel Stehule <pavel.stehule@gmail.com>
wrote:

ne 16. 2. 2020 v 16:15 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
napsal:

2020年2月15日 下午6:06,Pavel Stehule <pavel.stehule@gmail.com> 写道:

postgres=# insert into foo select generate_series(1,10000);

INSERT 0 10000
postgres=# \dt+ foo
List of relations
┌────────┬──────┬───────┬───────┬─────────────┬────────┬─────────────┐
│ Schema │ Name │ Type │ Owner │ Persistence │ Size │ Description │
╞════════╪══════╪═══════╪═══════╪═════════════╪════════╪═════════════╡
│ public │ foo │ table │ pavel │ session │ 384 kB │ │
└────────┴──────┴───────┴───────┴─────────────┴────────┴─────────────┘
(1 row)

postgres=# truncate foo;
TRUNCATE TABLE
postgres=# \dt+ foo
List of relations
┌────────┬──────┬───────┬───────┬─────────────┬───────┬─────────────┐
│ Schema │ Name │ Type │ Owner │ Persistence │ Size │ Description │
╞════════╪══════╪═══════╪═══════╪═════════════╪═══════╪═════════════╡
│ public │ foo │ table │ pavel │ session │ 16 kB │ │
└────────┴──────┴───────┴───────┴─────────────┴───────┴─────────────┘
(1 row)

I expect zero size after truncate.

Thanks for review.

I can explain, I don't think it's a bug.
The current implementation of the truncated GTT retains two blocks of
FSM pages.
The same is true for truncating regular tables in subtransactions.
This is an implementation that truncates the table without changing the
relfilenode of the table.

This is not extra important feature - now this is little bit a surprise,
because I was not under transaction.

Changing relfilenode, I think, is necessary, minimally for future VACUUM
FULL support.

Not allowing relfilenode changes is the current limit.
I think can improve on it. But ,This is a bit complicated.
so I'd like to know the necessity of this improvement.
Could you give me more details?

I don't think so GTT without support of VACUUM FULL can be accepted. Just
due consistency.

Regards

Pavel

Regards

Pavel Stehule

Wenjing

Regards

Pavel

Wenjing

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

#151曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Pavel Stehule (#149)
1 attachment(s)
Re: [Proposal] Global temporary tables

Hi,
I have started testing the "Global temporary table" feature,
That's great, I see hope.
from "gtt_v11-pg13.patch". Below is my findings:

-- session 1:
postgres=# create global temporary table gtt1(a int);
CREATE TABLE

-- seeeion 2:
postgres=# truncate gtt1 ;
ERROR: could not open file "base/13585/t3_16384": No such file or directory

is it expected?

Oh ,this is a bug, I fixed it.

Wenjing

On Sun, Feb 16, 2020 at 8:53 PM Pavel Stehule <pavel.stehule@gmail.com <mailto:pavel.stehule@gmail.com>> wrote:

ne 16. 2. 2020 v 16:15 odesílatel 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:

2020年2月15日 下午6:06,Pavel Stehule <pavel.stehule@gmail.com <mailto:pavel.stehule@gmail.com>> 写道:

postgres=# insert into foo select generate_series(1,10000);
INSERT 0 10000
postgres=# \dt+ foo
List of relations
┌────────┬──────┬───────┬───────┬─────────────┬────────┬─────────────┐
│ Schema │ Name │ Type │ Owner │ Persistence │ Size │ Description │
╞════════╪══════╪═══════╪═══════╪═════════════╪════════╪═════════════╡
│ public │ foo │ table │ pavel │ session │ 384 kB │ │
└────────┴──────┴───────┴───────┴─────────────┴────────┴─────────────┘
(1 row)

postgres=# truncate foo;
TRUNCATE TABLE
postgres=# \dt+ foo
List of relations
┌────────┬──────┬───────┬───────┬─────────────┬───────┬─────────────┐
│ Schema │ Name │ Type │ Owner │ Persistence │ Size │ Description │
╞════════╪══════╪═══════╪═══════╪═════════════╪═══════╪═════════════╡
│ public │ foo │ table │ pavel │ session │ 16 kB │ │
└────────┴──────┴───────┴───────┴─────────────┴───────┴─────────────┘
(1 row)

I expect zero size after truncate.

Thanks for review.

I can explain, I don't think it's a bug.
The current implementation of the truncated GTT retains two blocks of FSM pages.
The same is true for truncating regular tables in subtransactions.
This is an implementation that truncates the table without changing the relfilenode of the table.

This is not extra important feature - now this is little bit a surprise, because I was not under transaction.

Changing relfilenode, I think, is necessary, minimally for future VACUUM FULL support.

Not allowing relfilenode changes is the current limit.
I think can improve on it. But ,This is a bit complicated.
so I'd like to know the necessity of this improvement.
Could you give me more details?

I don't think so GTT without support of VACUUM FULL can be accepted. Just due consistency.

Regards

Pavel

Regards

Pavel Stehule

Wenjing

Regards

Pavel

Wenjing

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;
The Enterprise PostgreSQL Company

--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

Attachments:

global_temporary_table_v12-pg13.patchapplication/octet-stream; name=global_temporary_table_v12-pg13.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 79430d2..babb5f3 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -158,6 +158,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * For global temp table only
+	 * use ShareUpdateExclusiveLock for ensure safety
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temp table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},	
 	/* list terminator */
 	{{NULL}}
 };
@@ -1486,6 +1499,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1586,13 +1601,18 @@ build_reloptions(Datum reloptions, bool validate,
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	/*
 	 * There are no options for partitioned tables yet, but this is able to do
 	 * some validation.
 	 */
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index dd975b1..1610e7d 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1013,7 +1013,9 @@ gistGetFakeLSN(Relation rel)
 {
 	static XLogRecPtr counter = FirstNormalUnloggedLSN;
 
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	/* global temp is same as local temp table */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 4871b7f..16b00c9 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -149,7 +149,9 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	/* global temp table is same as local temp table */
+	if (!(index->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		index->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 3fa4b76..b54882d 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -598,7 +598,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -651,7 +651,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 03c43ef..2674132 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -62,6 +62,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -399,9 +400,9 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
 	Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
-	/* not every AM requires these to be valid, but heap does */
-	Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
-	Assert(MultiXactIdIsValid(onerel->rd_rel->relminmxid));
+	/* not every AM requires these to be valid, but regular heap does */
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ MultiXactIdIsValid(onerel->rd_rel->relminmxid));
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index f05cbe7..f2d3b83 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -27,6 +27,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -589,6 +590,10 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(rel->rd_node.relNode))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 3813ead..fd731d8 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6325,6 +6325,10 @@ StartupXLOG(void)
 	else
 		recoveryTargetTLI = ControlFile->checkPointCopy.ThisTimeLineID;
 
+	/* clean temp relation files */
+	if (max_active_gtt > 0)
+		RemovePgTempFiles();
+
 	/*
 	 * Check for signal files, and if so set up state for offline recovery
 	 */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index f8f0b48..dc8bbb1 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -42,6 +42,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 7d6acae..3be8d63 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -393,7 +393,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/* global temp table is same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index e31478b..1c382c6 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -427,7 +429,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -957,6 +959,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -995,8 +998,18 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/* global temp table not remember transaction info in catalog */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1358,6 +1371,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1938,6 +1952,13 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/* We allow to drop global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(rel->rd_node))
+			elog(ERROR, "can not drop relation when other backend attached this global temp table");
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3165,7 +3186,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3177,7 +3198,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3223,8 +3244,13 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		/* truncate global temp table only need RowExclusiveLock */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3257,6 +3283,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3265,23 +3292,37 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (!gtt_storage_attached(rel->rd_node.relNode))
+			return;
+
+		/*
+		 * Truncate global temp table only need RowExclusiveLock
+		 */
+		lockmode = RowExclusiveLock;
+	}
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(rel, 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 8880586..a23b499 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -52,6 +52,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -877,6 +878,19 @@ index_create(Relation heapRelation,
 						indexRelationName, RelationGetRelationName(heapRelation))));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temp table use concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot create indexes on global temporary tables using concurrent mode")));
+
+		/* if global temp table not init storage, then skip build index */
+		if (!gtt_storage_attached(heapRelation->rd_node.relNode))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -2054,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.
@@ -2660,6 +2681,11 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	/* update index stats into localhash and rel_rd_rel for global temp table */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		is_gtt = true;
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2745,21 +2771,35 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
+		if (is_gtt)
+			rel->rd_rel->relpages = (int32) relpages;
+		else if (rd_rel->relpages != (int32) relpages)
 		{
 			rd_rel->relpages = (int32) relpages;
 			dirty = true;
 		}
-		if (rd_rel->reltuples != (float4) reltuples)
+
+		if (is_gtt)
+			rel->rd_rel->reltuples = (float4) reltuples;
+		else if (rd_rel->reltuples != (float4) reltuples)
 		{
 			rd_rel->reltuples = (float4) reltuples;
 			dirty = true;
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+
+		if (is_gtt)
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+		else if (rd_rel->relallvisible != (int32) relallvisible)
 		{
 			rd_rel->relallvisible = (int32) relallvisible;
 			dirty = true;
 		}
+
+		if (is_gtt)
+		{
+			up_gtt_relstats(rel, relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
+		}
 	}
 
 	/*
@@ -2873,6 +2913,15 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, index, val);
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		if (!gtt_storage_attached(indexRelation->rd_node.relNode))
+		{
+			gtt_force_enable_index(indexRelation);
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3468,6 +3517,15 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 				 errmsg("cannot reindex temporary tables of other sessions")));
 
 	/*
+	 * Because global temp table cannot change relfilenode
+	 * no support reindex on global temp table yet.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(iRel))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot reindex global temporary tables")));
+
+	/*
 	 * Also check for active uses of the index in the current transaction; we
 	 * don't want to reindex underneath an open indexscan.
 	 */
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index e70243a..301da79 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -647,6 +647,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		/* global temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp relations in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index fddfbf1..671c614 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -26,6 +26,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -75,7 +76,7 @@ static PendingRelDelete *pendingDeletes = NULL; /* head of linked list */
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -85,6 +86,8 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		/* global temp table use same storage strategy as local temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -117,6 +120,10 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/* remember global temp table storage info to localhash */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel)
+		remember_gtt_storage_info(rnode, rel);
+
 	return srel;
 }
 
@@ -486,8 +493,15 @@ smgrDoPendingDeletes(bool isCommit)
 		smgrdounlinkall(srels, nrels, false);
 
 		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* clean global temp table flags when transaction commit or rollback */
+			if (SmgrIsTemp(srels[i]) && 
+				gtt_storage_attached(srels[i]->smgr_rnode.node.relNode))
+				forget_gtt_storage_info(srels[i]->smgr_rnode.node.relNode);
+		}
+
 		pfree(srels);
 	}
 }
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..e877b0c
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1260 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  code to create and destroy physical storage for global temparary table
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_relstats_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+typedef struct gtt_ctl_data
+{
+	LWLock			lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct
+{
+	RelFileNode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+	Oid			relid;
+
+	Oid			spcnode;
+	/* pg_class stat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+	char		relkind;
+	bool		on_commit_delete;
+
+	/* pg_statistic */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(RelFileNode rnode);
+static void gtt_storage_checkout(RelFileNode rnode, bool skiplock);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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;
+	int			natts = 0;
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_gtt to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temp table yet");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create stroage file", RelationGetRelationName(rel));
+
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		/* First time through: initialize the hash table */
+		HASHCTL		ctl;
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		gtt_storage_local_hash =
+			hash_create("global temp relation table",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_relstats_context =
+			AllocSetContextCreate(CacheMemoryContext,
+							"gtt relstats context",
+							ALLOCSET_DEFAULT_SIZES);
+	}
+
+	/* Look up or create an entry */
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_ENTER, &found);
+
+	if (found)
+	{
+		elog(ERROR, "backend %d relid %u already exists in global temp table local hash",
+					MyBackendId, relid);
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+	entry->spcnode = rnode.spcNode;
+	entry->relpages = 0;
+	entry->reltuples = 0;
+	entry->relallvisible = 0;
+	entry->relkind = rel->rd_rel->relkind;
+	natts = RelationGetNumberOfAttributes(rel);
+	entry->natts = natts;
+	entry->attnum = palloc0(sizeof(int) * natts);
+	entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+	/* only heap contain transaction information */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+		{
+			entry->on_commit_delete = true;
+			register_on_commit_action(rel->rd_node.relNode, ONCOMMIT_DELETE_ROWS);
+		}
+
+		entry->relfrozenxid = RecentXmin;
+		entry->relminmxid = GetOldestMultiXactId();
+		insert_gtt_relfrozenxid_to_ordered_list(entry->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+		gtt_storage_checkin(rnode);
+	}
+	else
+	{
+		entry->on_commit_delete = false;
+		entry->relfrozenxid = 0;
+		entry->relminmxid = 0;
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+void
+forget_gtt_storage_info(Oid relid)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry)
+	{
+		int		i;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			RelFileNode rnode;
+
+			rnode.spcNode = entry->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = entry->relid;
+
+			gtt_storage_checkout(rnode, false);
+
+			Assert(TransactionIdIsNormal(entry->relfrozenxid));
+			remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+
+		for (i = 0; i < entry->natts; i++)
+		{
+			if (entry->att_stat_tups[i])
+			{
+				heap_freetuple(entry->att_stat_tups[i]);
+				entry->att_stat_tups[i] = NULL;
+			}
+		}
+
+		pfree(entry->attnum);
+		pfree(entry->att_stat_tups);
+	}
+
+	hash_search(gtt_storage_local_hash,
+			(void *) &(relid), HASH_REMOVE, NULL);
+
+	return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool found;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (gtt_storage_local_hash == NULL)
+		return false;
+
+	hash_search(gtt_storage_local_hash,
+			(void *) &(relid), HASH_FIND, &found);
+
+	return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS				status;
+	gtt_local_hash_entry		*entry;
+	int			nrels = 0,
+				maxrels = 0;
+	SMgrRelation	*srels = NULL;
+	RelFileNode		*rnodes = NULL;
+	char			*relkinds = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		SMgrRelation srel;
+		RelFileNode rnode;
+
+		rnode.spcNode = entry->spcnode;
+		rnode.dbNode = MyDatabaseId;
+		rnode.relNode = entry->relid;
+
+		srel = smgropen(rnode, MyBackendId);
+
+		/* allocate the initial array, or extend it, if needed */
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			srels = palloc(sizeof(SMgrRelation) * maxrels);
+			rnodes = palloc(sizeof(RelFileNode) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+			rnodes = repalloc(rnodes, sizeof(RelFileNode) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		srels[nrels] = srel;
+		rnodes[nrels] = rnode;
+		relkinds[nrels] = entry->relkind;
+		nrels++;
+	}
+
+	if (nrels > 0)
+	{
+		int i;
+
+		smgrdounlinkall(srels, nrels, false);
+
+		LWLockAcquire(&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.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (gtt_storage_local_hash == NULL)
+		return false;
+
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_FIND, &found);
+
+	if (!found)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	if (relpages)
+		*relpages = entry->relpages;
+
+	if (reltuples)
+		*reltuples = entry->reltuples;
+
+	if (relallvisible)
+		*relallvisible = entry->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = entry->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = entry->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	MemoryContext			oldcontext;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+						(void *) &reloid, HASH_FIND, &found);
+
+	if (!found)
+		return;
+
+	/* todo */
+	if (entry->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	Assert(entry->relid == reloid);
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == 0)
+		{
+			entry->attnum[i] = attnum;
+			break;
+		}
+		else if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < entry->natts);
+	Assert(entry->att_stat_tups[i] == NULL);
+	entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+						(void *) &reloid, HASH_FIND, &found);
+
+	if (!found)
+		return NULL;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			return entry->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int			i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	HeapTuple	tuple;
+	Relation	rel = NULL;
+	int		attnum = PG_GETARG_INT32(1);
+	Oid		reloid = PG_GETARG_OID(0);
+	char		rel_persistence;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	  tupdesc;
+	MemoryContext oldcontext;
+	Tuplestorestate *tupstore;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = table_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+
+	table_close(rel, NoLock);
+	table_close(pg_tatistic, AccessShareLock);
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate	*tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Relation	rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	if (get_gtt_relstats(reloid,
+						&relpages, &reltuples, &relallvisible,
+						&relfrozenxid, &relminmxid))
+	{
+		Datum	values[5];
+		bool	isnull[5];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = Int32GetDatum(relpages);
+		values[1] = Float4GetDatum((float4)reltuples);
+		values[2] = Int32GetDatum(relallvisible);
+		values[3] = UInt32GetDatum(relfrozenxid);
+		values[4] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+
+	tuplestore_donestoring(tupstore);
+	table_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(rel->rd_node);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	table_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(indexOid));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid relOid = index->rd_index->indrelid;
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	if (gtt_storage_attached(relOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int			i;
+	Oid			toastrelid;
+	List		*seqlist = NIL;
+	ListCell	*seqcell = NULL;
+
+	if (operation != CMD_INSERT)
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_attached(relation->rd_node.relNode))
+		return;
+
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+
+		index_build(relation, index, info, true, false);
+	}
+
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid 		indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo 	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	seqlist = getOwnedSequences(RelationGetRelid(relation));
+	foreach(seqcell, seqlist)
+	{
+		Oid 		seq_relid = lfirst_oid(seqcell);
+		Relation	seq_rel;
+
+		seq_rel = relation_open(seq_relid, RowExclusiveLock);
+	
+		/* This check must match AlterSequence! */
+		if (!pg_class_ownercheck(seq_relid, GetUserId()))
+			aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SEQUENCE,
+						   RelationGetRelationName(seq_rel));
+
+		RelationCreateStorage(seq_rel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seq_rel);
+		relation_close(seq_rel, NoLock);
+	}
+
+	return;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index f681aaf..e1fa3ec 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM 
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c4420dd..977a984 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -586,14 +587,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 		 * pg_statistic for columns we didn't process, we leave them alone.)
 		 */
 		update_attstats(RelationGetRelid(onerel), inh,
-						attr_cnt, vacattrstats);
+						attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
 
 		for (ind = 0; ind < nindexes; ind++)
 		{
 			AnlIndexData *thisdata = &indexdata[ind];
 
 			update_attstats(RelationGetRelid(Irel[ind]), false,
-							thisdata->attr_cnt, thisdata->vacattrstats);
+							thisdata->attr_cnt, thisdata->vacattrstats,
+							RelationGetRelPersistence(Irel[ind]));
 		}
 
 		/*
@@ -1456,7 +1458,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
  *		by taking a self-exclusive lock on the relation in analyze_rel().
  */
 static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
 {
 	Relation	sd;
 	int			attno;
@@ -1558,31 +1560,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/* Update column statistic to localhash, not catalog */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 11ce1bb..552fd87 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -391,6 +391,12 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/* not support cluster global temp table yet */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("not support cluster global temporary tables yet")));
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index e79ede4..078694d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -27,6 +27,7 @@
 #include "catalog/dependency.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -1062,7 +1063,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, stmt->filename, stmt->is_program,
@@ -2840,6 +2841,8 @@ CopyFrom(CopyState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	estate->es_result_relations = resultRelInfo;
 	estate->es_num_result_relations = 1;
 	estate->es_result_relation_info = resultRelInfo;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index ec20ba3..a29cc00 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2614,6 +2614,16 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 			!isTempNamespace(classtuple->relnamespace))
 			continue;
 
+		/* not support reindex on global temp table, so skip it */
+		if (classtuple->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			ereport(WARNING,
+				(errmsg("global temp table \"%s.%s\" skip reindexed",
+					get_namespace_name(get_rel_namespace(relid)),
+					get_rel_name(relid))));
+			continue;
+		}
+
 		/* Check user/system classification, and optionally skip */
 		if (objectKind == REINDEX_OBJECT_SYSTEM &&
 			!IsSystemClass(relid, classtuple))
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index d8cafc4..deecde8 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -96,7 +96,8 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 	 * transaction.
 	 */
 	relpersistence = get_rel_persistence(relid);
-	if (relpersistence == RELPERSISTENCE_TEMP)
+	if (relpersistence == RELPERSISTENCE_TEMP ||
+		relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
 
 	/* Check permissions. */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..c55dcf7 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -94,7 +94,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +108,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -222,7 +223,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* now initialize the sequence's data */
 	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -327,7 +328,7 @@ ResetSequence(Oid seq_relid)
 	/*
 	 * Insert the modified tuple into the new storage file.
 	 */
-	fill_seq_with_data(seq_rel, tuple);
+	fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -340,17 +341,21 @@ ResetSequence(Oid seq_relid)
  * Initialize a sequence's relation with the specified tuple as content
  */
 static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 	sequence_magic *sm;
 	OffsetNumber offnum;
+	bool lockBuffer = false;
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ReadBuffer(rel, P_NEW);
-	Assert(BufferGetBlockNumber(buf) == 0);
+	if (buf == InvalidBuffer)
+	{
+		buf = ReadBuffer(rel, P_NEW);
+		Assert(BufferGetBlockNumber(buf) == 0);
+		lockBuffer = true;
+	}
 
 	page = BufferGetPage(buf);
 
@@ -360,7 +365,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* Now insert sequence tuple */
 
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+	if (lockBuffer)
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +416,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	END_CRIT_SECTION();
 
-	UnlockReleaseBuffer(buf);
+	if (lockBuffer)
+		UnlockReleaseBuffer(buf);
 }
 
 /*
@@ -502,7 +509,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * Insert the modified tuple into the new storage file.
 		 */
-		fill_seq_with_data(seqrel, newdatatuple);
+		fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
 	}
 
 	/* process OWNED BY if given */
@@ -611,7 +618,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +943,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1178,6 +1185,25 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
 	LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
 
 	page = BufferGetPage(*buf);
+	if (GlobalTempRelationPageIsNotInitialized(rel, page))
+	{
+		/* Initialize sequence for global temporary tables */
+		Datum		value[SEQ_COL_LASTCOL] = {0};
+		bool		null[SEQ_COL_LASTCOL] = {false};
+		HeapTuple	tuple;
+		int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+		/*
+		 * last_value from pg_sequence.seqstart
+		 * log_cnt = 0
+		 * is_called = false
+		 */
+		value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+		tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+		fill_seq_with_data(rel, tuple, *buf);
+		heap_freetuple(tuple);
+	}
 	sm = (sequence_magic *) PageGetSpecialPointer(page);
 
 	if (sm->magic != SEQ_MAGIC)
@@ -1954,3 +1980,23 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index b7c8d66..6d1e054 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -553,6 +554,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 								  Relation partitionTbl);
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static bool has_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -598,6 +600,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
@@ -608,8 +611,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Check consistency of arguments
 	 */
+	/* global temp table same as local temp table */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !(stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+			 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -639,7 +644,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	/* global temp table same as local temp table */
+	if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+		 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -740,6 +747,57 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* global temp table */
+	has_oncommit_clause = has_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* inherit table or parition table inherit on commit property from parent table*/
+		if (inheritOids && stmt->oncommit == ONCOMMIT_NOOP && !has_oncommit_clause)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			if (RELATION_GTT_ON_COMMIT_DELETE(relation))
+				stmt->oncommit = ONCOMMIT_DELETE_ROWS;
+			else
+				stmt->oncommit = ONCOMMIT_PRESERVE_ROWS;
+			table_close(relation, NoLock);
+		}
+
+		if (has_oncommit_clause)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "can not defeine global temp table with on commit and with clause at same time");
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temp table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (has_oncommit_clause)
+		elog(ERROR, "regular table cannot specifie on_commit_delete_rows");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1824,7 +1882,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);
@@ -3392,6 +3451,13 @@ RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal, bo
 	 * specially.
 	 */
 	targetrelation = relation_open(myrelid, is_index ? ShareUpdateExclusiveLock : AccessExclusiveLock);
+
+	if (RELATION_IS_GLOBAL_TEMP(targetrelation))
+	{
+		if (is_other_backend_use_gtt(targetrelation->rd_node))
+			elog(ERROR, "can not rename relation when other backend attached this global temp table");
+	}
+
 	namespaceId = RelationGetNamespace(targetrelation);
 
 	/*
@@ -3567,6 +3633,13 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(rel->rd_node))
+			elog(ERROR, "can not alter relation when other backend attached this global temp table");
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -8140,6 +8213,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),
@@ -8179,6 +8259,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		/* 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;
 	}
 
 	/*
@@ -13221,7 +13307,7 @@ index_copy_data(Relation rel, RelFileNode newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
@@ -14628,7 +14714,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",
@@ -17269,3 +17357,20 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
 		table_close(rel, NoLock);
 	}
 }
+
+static bool
+has_oncommit_option(List *options)
+{
+	ListCell   *listptr;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (pg_strcasecmp(def->defname, "on_commit_delete_rows") == 0)
+			return true;
+	}
+
+	return false;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index d625d17..69ad24f 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/vacuum.h"
@@ -1217,6 +1218,17 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	 /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		is_gtt = true;
+		up_gtt_relstats(relation,
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1230,17 +1242,26 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relpages = (int32) num_pages;
+	else if (pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (is_gtt)
+		relation->rd_rel->reltuples = (float4) num_tuples;
+	else if (pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	else if (pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1285,7 +1306,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1296,7 +1318,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1394,6 +1417,10 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/* global temp table relstats not in pg_class */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1451,6 +1478,25 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/*
+	 * Global temp table get frozenxid from MyProc
+	 * to avoid the vacuum truncate clog that gtt need.
+	 */
+	if (max_active_gtt > 0)
+	{
+		TransactionId oldest_gtt_frozenxid =
+			list_all_session_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid) &&
+			TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+		{
+			ereport(WARNING,
+				(errmsg("global temp table oldest FrozenXid is far in the past"),
+				 errhint("please truncate them or kill those sessions that use them.")));
+			newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index ee5c3a6..0d7a2d4 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandTag((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index c13b1d3..253054d 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
@@ -543,6 +544,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	init_gtt_storage(mtstate->operation, leaf_part_rri);
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d71c0a4..0900907 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,6 +42,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -2372,6 +2373,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			ExecOpenIndices(resultRelInfo,
 							node->onConflictAction != ONCONFLICT_NONE);
 
+		init_gtt_storage(operation, resultRelInfo);
+
 		/*
 		 * If this is an UPDATE and a BEFORE UPDATE trigger is present, the
 		 * trigger itself might modify the partition-key values. So arrange
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 905bbe7..170963d 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -591,6 +591,8 @@ static void
 set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 						  RangeTblEntry *rte)
 {
+	char relpersistence;
+
 	/*
 	 * The flag has previously been initialized to false, so we can just
 	 * return if it becomes clear that we can't safely set it.
@@ -618,7 +620,11 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			/* global temp table is same as local temp table */
+			relpersistence = get_rel_persistence(rte->relid);
+
+			if (relpersistence == RELPERSISTENCE_TEMP ||
+				relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b44efd6..14dbaaf 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6307,7 +6307,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
+	/* global temp table is same as local temp table */
 	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6676412..0685c1c 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2585,6 +2585,11 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("materialized views must not use temporary tables or views")));
 
+		if (is_query_using_gtt(query))
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("materialized views must not use global temporary tables or views")));
+
 		/*
 		 * A materialized view would either need to save parameters for use in
 		 * maintaining/loading the data or prohibit them entirely.  The latter
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 96e7fdb..30f585c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3290,17 +3290,11 @@ OptTemp:	TEMPORARY					{ $$ = RELPERSISTENCE_TEMP; }
 			| LOCAL TEMP				{ $$ = RELPERSISTENCE_TEMP; }
 			| GLOBAL TEMPORARY
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
-					$$ = RELPERSISTENCE_TEMP;
+					$$ = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
-					$$ = RELPERSISTENCE_TEMP;
+					$$ = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED					{ $$ = RELPERSISTENCE_UNLOGGED; }
 			| /*EMPTY*/					{ $$ = RELPERSISTENCE_PERMANENT; }
@@ -11594,19 +11588,13 @@ OptTempTableName:
 				}
 			| GLOBAL TEMPORARY opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED opt_table qualified_name
 				{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b875a50..123945b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index ee2d2b5..9c9abaa 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 6d1f28c..ed837d1 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2086,6 +2086,11 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* autovacuum skip vacuum global temp table */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2152,7 +2157,9 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		/* autovacuum skip vacuum global temp table */
+		if (classForm->relpersistence == RELPERSISTENCE_TEMP || 
+			classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 5880054..98f7b43 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -52,6 +53,7 @@
 #include "utils/rel.h"
 #include "utils/resowner_private.h"
 #include "utils/timestamp.h"
+#include "utils/guc.h"
 
 
 /* Note: these two macros only work on shared buffers, not local ones! */
@@ -2809,6 +2811,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(relation->rd_node.relNode))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d5..7adb969 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -21,6 +21,7 @@
 #include "access/nbtree.h"
 #include "access/subtrans.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, active_gtt_shared_hash_size());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* global temporary table */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 4a5b26c..9754168 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -62,6 +62,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -4087,3 +4088,77 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temp table.
+ */
+int
+list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct *arrayP = procArray;
+	TransactionId result = InvalidTransactionId;
+	int			index;
+	int			flags = 0;
+	int			i = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+		*n = 0;
+	}
+
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		volatile PGXACT *pgxact = &allPgXact[pgprocno];
+
+		if (pgxact->vacuumFlags & flags)
+			continue;
+
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->session_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->session_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->session_gtt_frozenxid, result))
+				result = proc->session_gtt_frozenxid;
+
+			if (max_size > 0)
+			{
+				pids[i] = proc->pid;
+				xids[i] = proc->session_gtt_frozenxid;
+				i++;
+			}
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (max_size > 0)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index eb321f7..3893cef 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 8406644..0d99321 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -1008,6 +1008,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 0be26fe..c169c99 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -112,6 +112,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -4570,12 +4571,25 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -4700,15 +4714,27 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6090,6 +6116,7 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	{
 		/* Simple variable --- look to stats for the underlying table */
 		RangeTblEntry *rte = planner_rt_fetch(index->rel->relid, root);
+		char	rel_persistence = get_rel_persistence(rte->relid);
 
 		Assert(rte->rtekind == RTE_RELATION);
 		relid = rte->relid;
@@ -6107,6 +6134,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6118,6 +6152,8 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	}
 	else
 	{
+		char	rel_persistence = get_rel_persistence(index->indexoid);
+
 		/* Expression --- maybe there are stats for the index itself */
 		relid = index->indexoid;
 		colnum = 1;
@@ -6133,6 +6169,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7046,6 +7089,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		/* attempt to lookup stats in relation for this index column */
 		if (attnum != 0)
 		{
+			char	rel_persistence = get_rel_persistence(rte->relid);
+
 			/* Simple variable -- look to stats for the underlying table */
 			if (get_relation_stats_hook &&
 				(*get_relation_stats_hook) (root, rte, attnum, &vardata))
@@ -7058,6 +7103,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7070,6 +7123,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		}
 		else
 		{
+			char	rel_persistence = get_rel_persistence(index->indexoid);
+
 			/*
 			 * Looks like we've found an expression column in the index. Let's
 			 * see if there's any stats for it.
@@ -7089,6 +7144,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index fb0599f..13f4d8a 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -2878,6 +2879,18 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index ff70326..1cf7063 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -63,6 +63,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1127,6 +1128,24 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1181,6 +1200,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			gtt_fix_index_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -2220,6 +2240,8 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		gtt_fix_index_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3316,6 +3338,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3430,6 +3456,9 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
 
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+		elog(ERROR, "global temp table does not allow setting new relfilenode");
+
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
@@ -3470,7 +3499,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 				/* handle these directly, at least for now */
 				SMgrRelation srel;
 
-				srel = RelationCreateStorage(newrnode, persistence);
+				srel = RelationCreateStorage(newrnode, persistence, relation);
 				smgrclose(srel);
 			}
 			break;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 8228e1f..c00ff94 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -140,6 +140,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temp table feature.
+ * global temp table define can still storage in catalog
+ * just can not use.
+ * num > 0 means database can management num active global temp table.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2047,6 +2059,15 @@ static struct config_bool ConfigureNamesBool[] =
 static struct config_int ConfigureNamesInt[] =
 {
 	{
+		{"max_active_global_temporary_table", PGC_POSTMASTER, UNGROUPED,
+			gettext_noop("max active global temporary table."),
+			NULL
+		},
+		&max_active_gtt,
+		DEFAULT_NUM_ACTIVE_GTT, MIN_NUM_ACTIVE_GTT, MAX_NUM_ACTIVE_GTT,
+		NULL, NULL, NULL
+	},
+	{
 		{"archive_timeout", PGC_SIGHUP, WAL_ARCHIVING,
 			gettext_noop("Forces a switch to the next WAL file if a "
 						 "new file has not been started within N seconds."),
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index d92a662..07c99c5 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15598,6 +15598,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	{
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
+		char		*table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15649,9 +15650,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 			binary_upgrade_set_pg_class_oids(fout, q,
 											 tbinfo->dobj.catId.oid, false);
 
+		if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED)
+			table_type = "UNLOGGED ";
+		else if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			table_type = "GLOBAL TEMPORARY ";
+		else
+			table_type = "";
+
 		appendPQExpBuffer(q, "CREATE %s%s %s",
-						  tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ?
-						  "UNLOGGED " : "",
+						  table_type,
 						  reltypename,
 						  qualrelname);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index f3c7eb9..28134e2 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3723,7 +3723,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index dc03fbd..8bd6d09 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1016,6 +1016,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2378,6 +2380,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2586,6 +2591,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 78b33b2..4be89f6 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -168,6 +168,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index eb3c1a8..5206815 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5504,6 +5504,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4191',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4192',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{relid,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4193',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4194',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 048003c..af48cdf 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -19,7 +19,7 @@
 #include "storage/smgr.h"
 #include "utils/relcache.h"
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationTruncate(Relation rel, BlockNumber nblocks);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..3afd71d
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,43 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid);
+extern bool is_other_backend_use_gtt(RelFileNode node);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(RelFileNode node);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Relation relation,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void gtt_force_enable_index(Relation index);
+extern void gtt_fix_index_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 3f88683..f870e9a 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 8fda8e4..172eae9 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_TBM,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_SXACT,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index d217801..8adde87 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -117,6 +117,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId session_gtt_frozenxid;	/* session level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ce93ace..0f7262e 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -281,6 +281,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 44ed04d..6b6d2da 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	struct SMgrRelationData *rd_smgr;	/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -277,6 +277,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -535,11 +536,13 @@ typedef struct ViewOptions
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -547,6 +550,7 @@ typedef struct ViewOptions
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -559,6 +563,14 @@ typedef struct ViewOptions
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is either local temp relation of this session
+ * 		or global temp relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -602,6 +614,17 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..50ca9ac
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,7 @@
+reset search_path;
+drop schema gtt cascade;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..28acdfb
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,276 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+ERROR:  cannot create indexes on global temporary tables using concurrent mode
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+ERROR:  not support cluster global temporary tables yet
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ok
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  regular table cannot specifie on_commit_delete_rows
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  can not defeine global temp table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test;
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  referenced relation "products" is not a global temp table
+-- ERROR
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  referenced relation "products" is not a global temp table
+-- ERROR
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 1)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 300)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 17 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to table gtt_function.gt1
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..30d8a7b
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,84 @@
+set search_path=gtt,sys;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..850ef3e
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,161 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          475136 |                 974848
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |           409600 |        409600 |                 409600
+(2 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..9fe5fd4
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,9 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..0e21b30
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,76 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+(1 row)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+(1 row)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 634f825..6e1297a 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1349,6 +1349,93 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d2b17dd..80e577f 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..f3cf710
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,6 @@
+
+
+reset search_path;
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..e4ce357
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,186 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ok
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test;
+
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ERROR
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ERROR
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d7d81de
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,42 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..5203c2b
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,76 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..2f4d883
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,16 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..f041892
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,42 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
#152Prabhat Sahu
prabhat.sahu@enterprisedb.com
In reply to: 曾文旌(义从) (#151)
Re: [Proposal] Global temporary tables

On Fri, Feb 21, 2020 at 9:10 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com> wrote:

Hi,
I have started testing the "Global temporary table" feature,
That's great, I see hope.
from "gtt_v11-pg13.patch". Below is my findings:

-- session 1:
postgres=# create global temporary table gtt1(a int);
CREATE TABLE

-- seeeion 2:
postgres=# truncate gtt1 ;
ERROR: could not open file "base/13585/t3_16384": No such file or
directory

is it expected?

Oh ,this is a bug, I fixed it.

Thanks for the patch.
I have verified the same, Now the issue is resolved with v12 patch.

Kindly confirm the below scenario:

postgres=# create global temporary table gtt1 (c1 int unique);
CREATE TABLE

postgres=# create global temporary table gtt2 (c1 int references gtt1(c1) );
ERROR: referenced relation "gtt1" is not a global temp table

postgres=# create table tab2 (c1 int references gtt1(c1) );
ERROR: referenced relation "gtt1" is not a global temp table

Thanks,
Prabhat Sahu

#153Prabhat Sahu
prabhat.sahu@enterprisedb.com
In reply to: Prabhat Sahu (#152)
Re: [Proposal] Global temporary tables

Hi All,

I observe a different behavior in "temporary table" and "global temporary
table".
Not sure if it is expected?

postgres=# create global temporary table parent1(a int) on commit delete
rows;
CREATE TABLE
postgres=# create global temporary table child1() inherits (parent1);
CREATE TABLE
postgres=# insert into parent1 values(1);
INSERT 0 1
postgres=# insert into child1 values(2);
INSERT 0 1
postgres=# select * from parent1;
a
---
(0 rows)

postgres=# select * from child1;
a
---
(0 rows)

postgres=# create temporary table parent2(a int) on commit delete rows;
CREATE TABLE
postgres=# create temporary table child2() inherits (parent2);
CREATE TABLE
postgres=# insert into parent2 values(1);
INSERT 0 1
postgres=# insert into child2 values(2);
INSERT 0 1
postgres=# select * from parent2;
a
---
2
(1 row)

postgres=# select * from child2;
a
---
2
(1 row)

Thanks,
Prabhat Sahu

#154Pavel Stehule
pavel.stehule@gmail.com
In reply to: Prabhat Sahu (#153)
Re: [Proposal] Global temporary tables

po 24. 2. 2020 v 14:34 odesílatel Prabhat Sahu <
prabhat.sahu@enterprisedb.com> napsal:

Hi All,

I observe a different behavior in "temporary table" and "global temporary
table".
Not sure if it is expected?

postgres=# create global temporary table parent1(a int) on commit delete
rows;
CREATE TABLE
postgres=# create global temporary table child1() inherits (parent1);
CREATE TABLE
postgres=# insert into parent1 values(1);
INSERT 0 1
postgres=# insert into child1 values(2);
INSERT 0 1
postgres=# select * from parent1;
a
---
(0 rows)

postgres=# select * from child1;
a
---
(0 rows)

It is bug. Probably INHERITS clause is not well implemented for GTT

Show quoted text

postgres=# create temporary table parent2(a int) on commit delete rows;
CREATE TABLE
postgres=# create temporary table child2() inherits (parent2);
CREATE TABLE
postgres=# insert into parent2 values(1);
INSERT 0 1
postgres=# insert into child2 values(2);
INSERT 0 1
postgres=# select * from parent2;
a
---
2
(1 row)

postgres=# select * from child2;
a
---
2
(1 row)

Thanks,
Prabhat Sahu

#155曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Prabhat Sahu (#153)
Re: [Proposal] Global temporary tables

2020年2月24日 下午9:34,Prabhat Sahu <prabhat.sahu@enterprisedb.com> 写道:

Hi All,

I observe a different behavior in "temporary table" and "global temporary table".
Not sure if it is expected?

postgres=# create global temporary table parent1(a int) on commit delete rows;
CREATE TABLE
postgres=# create global temporary table child1() inherits (parent1);
CREATE TABLE
postgres=# insert into parent1 values(1);
INSERT 0 1
postgres=# insert into child1 values(2);
INSERT 0 1
postgres=# select * from parent1;
a
---
(0 rows)

postgres=# select * from child1;
a
---
(0 rows)

Because child1 inherits its father's on commit property.
I can make GTT behave like local temp table.

Show quoted text

postgres=# create temporary table parent2(a int) on commit delete rows;
CREATE TABLE
postgres=# create temporary table child2() inherits (parent2);
CREATE TABLE
postgres=# insert into parent2 values(1);
INSERT 0 1
postgres=# insert into child2 values(2);
INSERT 0 1
postgres=# select * from parent2;
a
---
2
(1 row)

postgres=# select * from child2;
a
---
2
(1 row)

Thanks,
Prabhat Sahu

#156曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Pavel Stehule (#154)
1 attachment(s)
Re: [Proposal] Global temporary tables

2020年2月24日 下午9:41,Pavel Stehule <pavel.stehule@gmail.com> 写道:

po 24. 2. 2020 v 14:34 odesílatel Prabhat Sahu <prabhat.sahu@enterprisedb.com <mailto:prabhat.sahu@enterprisedb.com>> napsal:
Hi All,

I observe a different behavior in "temporary table" and "global temporary table".
Not sure if it is expected?

postgres=# create global temporary table parent1(a int) on commit delete rows;
CREATE TABLE
postgres=# create global temporary table child1() inherits (parent1);
CREATE TABLE
postgres=# insert into parent1 values(1);
INSERT 0 1
postgres=# insert into child1 values(2);
INSERT 0 1
postgres=# select * from parent1;
a
---
(0 rows)

postgres=# select * from child1;
a
---
(0 rows)

It is bug. Probably INHERITS clause is not well implemented for GTT

I fixed the GTT's behavior, like local temp table.

Wenjing

Show quoted text

postgres=# create temporary table parent2(a int) on commit delete rows;
CREATE TABLE
postgres=# create temporary table child2() inherits (parent2);
CREATE TABLE
postgres=# insert into parent2 values(1);
INSERT 0 1
postgres=# insert into child2 values(2);
INSERT 0 1
postgres=# select * from parent2;
a
---
2
(1 row)

postgres=# select * from child2;
a
---
2
(1 row)

Thanks,
Prabhat Sahu

Attachments:

global_temporary_table_v13-pg13.patchapplication/octet-stream; name=global_temporary_table_v13-pg13.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 79430d2..babb5f3 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -158,6 +158,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * For global temp table only
+	 * use ShareUpdateExclusiveLock for ensure safety
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temp table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},	
 	/* list terminator */
 	{{NULL}}
 };
@@ -1486,6 +1499,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1586,13 +1601,18 @@ build_reloptions(Datum reloptions, bool validate,
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	/*
 	 * There are no options for partitioned tables yet, but this is able to do
 	 * some validation.
 	 */
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index dd975b1..1610e7d 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1013,7 +1013,9 @@ gistGetFakeLSN(Relation rel)
 {
 	static XLogRecPtr counter = FirstNormalUnloggedLSN;
 
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	/* global temp is same as local temp table */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 4871b7f..16b00c9 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -149,7 +149,9 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	/* global temp table is same as local temp table */
+	if (!(index->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		index->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 3fa4b76..b54882d 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -598,7 +598,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -651,7 +651,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 03c43ef..2674132 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -62,6 +62,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -399,9 +400,9 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
 	Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
-	/* not every AM requires these to be valid, but heap does */
-	Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
-	Assert(MultiXactIdIsValid(onerel->rd_rel->relminmxid));
+	/* not every AM requires these to be valid, but regular heap does */
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ MultiXactIdIsValid(onerel->rd_rel->relminmxid));
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index f05cbe7..f2d3b83 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -27,6 +27,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -589,6 +590,10 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(rel->rd_node.relNode))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index d19408b..5014268 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6347,6 +6347,10 @@ StartupXLOG(void)
 	else
 		recoveryTargetTLI = ControlFile->checkPointCopy.ThisTimeLineID;
 
+	/* clean temp relation files */
+	if (max_active_gtt > 0)
+		RemovePgTempFiles();
+
 	/*
 	 * Check for signal files, and if so set up state for offline recovery
 	 */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index f8f0b48..dc8bbb1 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -42,6 +42,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 7d6acae..3be8d63 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -393,7 +393,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/* global temp table is same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index e31478b..1c382c6 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -427,7 +429,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -957,6 +959,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -995,8 +998,18 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/* global temp table not remember transaction info in catalog */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1358,6 +1371,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1938,6 +1952,13 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/* We allow to drop global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(rel->rd_node))
+			elog(ERROR, "can not drop relation when other backend attached this global temp table");
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3165,7 +3186,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3177,7 +3198,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3223,8 +3244,13 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		/* truncate global temp table only need RowExclusiveLock */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3257,6 +3283,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3265,23 +3292,37 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (!gtt_storage_attached(rel->rd_node.relNode))
+			return;
+
+		/*
+		 * Truncate global temp table only need RowExclusiveLock
+		 */
+		lockmode = RowExclusiveLock;
+	}
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(rel, 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 8880586..a23b499 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -52,6 +52,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -877,6 +878,19 @@ index_create(Relation heapRelation,
 						indexRelationName, RelationGetRelationName(heapRelation))));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temp table use concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot create indexes on global temporary tables using concurrent mode")));
+
+		/* if global temp table not init storage, then skip build index */
+		if (!gtt_storage_attached(heapRelation->rd_node.relNode))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -2054,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.
@@ -2660,6 +2681,11 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	/* update index stats into localhash and rel_rd_rel for global temp table */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		is_gtt = true;
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2745,21 +2771,35 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
+		if (is_gtt)
+			rel->rd_rel->relpages = (int32) relpages;
+		else if (rd_rel->relpages != (int32) relpages)
 		{
 			rd_rel->relpages = (int32) relpages;
 			dirty = true;
 		}
-		if (rd_rel->reltuples != (float4) reltuples)
+
+		if (is_gtt)
+			rel->rd_rel->reltuples = (float4) reltuples;
+		else if (rd_rel->reltuples != (float4) reltuples)
 		{
 			rd_rel->reltuples = (float4) reltuples;
 			dirty = true;
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+
+		if (is_gtt)
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+		else if (rd_rel->relallvisible != (int32) relallvisible)
 		{
 			rd_rel->relallvisible = (int32) relallvisible;
 			dirty = true;
 		}
+
+		if (is_gtt)
+		{
+			up_gtt_relstats(rel, relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
+		}
 	}
 
 	/*
@@ -2873,6 +2913,15 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, index, val);
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		if (!gtt_storage_attached(indexRelation->rd_node.relNode))
+		{
+			gtt_force_enable_index(indexRelation);
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3468,6 +3517,15 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 				 errmsg("cannot reindex temporary tables of other sessions")));
 
 	/*
+	 * Because global temp table cannot change relfilenode
+	 * no support reindex on global temp table yet.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(iRel))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot reindex global temporary tables")));
+
+	/*
 	 * Also check for active uses of the index in the current transaction; we
 	 * don't want to reindex underneath an open indexscan.
 	 */
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index e70243a..301da79 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -647,6 +647,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		/* global temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp relations in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index fddfbf1..671c614 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -26,6 +26,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -75,7 +76,7 @@ static PendingRelDelete *pendingDeletes = NULL; /* head of linked list */
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -85,6 +86,8 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		/* global temp table use same storage strategy as local temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -117,6 +120,10 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/* remember global temp table storage info to localhash */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel)
+		remember_gtt_storage_info(rnode, rel);
+
 	return srel;
 }
 
@@ -486,8 +493,15 @@ smgrDoPendingDeletes(bool isCommit)
 		smgrdounlinkall(srels, nrels, false);
 
 		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* clean global temp table flags when transaction commit or rollback */
+			if (SmgrIsTemp(srels[i]) && 
+				gtt_storage_attached(srels[i]->smgr_rnode.node.relNode))
+				forget_gtt_storage_info(srels[i]->smgr_rnode.node.relNode);
+		}
+
 		pfree(srels);
 	}
 }
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..e877b0c
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1260 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  code to create and destroy physical storage for global temparary table
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_relstats_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+typedef struct gtt_ctl_data
+{
+	LWLock			lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct
+{
+	RelFileNode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+	Oid			relid;
+
+	Oid			spcnode;
+	/* pg_class stat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+	char		relkind;
+	bool		on_commit_delete;
+
+	/* pg_statistic */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(RelFileNode rnode);
+static void gtt_storage_checkout(RelFileNode rnode, bool skiplock);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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;
+	int			natts = 0;
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_gtt to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temp table yet");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create stroage file", RelationGetRelationName(rel));
+
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		/* First time through: initialize the hash table */
+		HASHCTL		ctl;
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		gtt_storage_local_hash =
+			hash_create("global temp relation table",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_relstats_context =
+			AllocSetContextCreate(CacheMemoryContext,
+							"gtt relstats context",
+							ALLOCSET_DEFAULT_SIZES);
+	}
+
+	/* Look up or create an entry */
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_ENTER, &found);
+
+	if (found)
+	{
+		elog(ERROR, "backend %d relid %u already exists in global temp table local hash",
+					MyBackendId, relid);
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+	entry->spcnode = rnode.spcNode;
+	entry->relpages = 0;
+	entry->reltuples = 0;
+	entry->relallvisible = 0;
+	entry->relkind = rel->rd_rel->relkind;
+	natts = RelationGetNumberOfAttributes(rel);
+	entry->natts = natts;
+	entry->attnum = palloc0(sizeof(int) * natts);
+	entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+	/* only heap contain transaction information */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+		{
+			entry->on_commit_delete = true;
+			register_on_commit_action(rel->rd_node.relNode, ONCOMMIT_DELETE_ROWS);
+		}
+
+		entry->relfrozenxid = RecentXmin;
+		entry->relminmxid = GetOldestMultiXactId();
+		insert_gtt_relfrozenxid_to_ordered_list(entry->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+		gtt_storage_checkin(rnode);
+	}
+	else
+	{
+		entry->on_commit_delete = false;
+		entry->relfrozenxid = 0;
+		entry->relminmxid = 0;
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+void
+forget_gtt_storage_info(Oid relid)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry)
+	{
+		int		i;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			RelFileNode rnode;
+
+			rnode.spcNode = entry->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = entry->relid;
+
+			gtt_storage_checkout(rnode, false);
+
+			Assert(TransactionIdIsNormal(entry->relfrozenxid));
+			remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+
+		for (i = 0; i < entry->natts; i++)
+		{
+			if (entry->att_stat_tups[i])
+			{
+				heap_freetuple(entry->att_stat_tups[i]);
+				entry->att_stat_tups[i] = NULL;
+			}
+		}
+
+		pfree(entry->attnum);
+		pfree(entry->att_stat_tups);
+	}
+
+	hash_search(gtt_storage_local_hash,
+			(void *) &(relid), HASH_REMOVE, NULL);
+
+	return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool found;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (gtt_storage_local_hash == NULL)
+		return false;
+
+	hash_search(gtt_storage_local_hash,
+			(void *) &(relid), HASH_FIND, &found);
+
+	return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS				status;
+	gtt_local_hash_entry		*entry;
+	int			nrels = 0,
+				maxrels = 0;
+	SMgrRelation	*srels = NULL;
+	RelFileNode		*rnodes = NULL;
+	char			*relkinds = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		SMgrRelation srel;
+		RelFileNode rnode;
+
+		rnode.spcNode = entry->spcnode;
+		rnode.dbNode = MyDatabaseId;
+		rnode.relNode = entry->relid;
+
+		srel = smgropen(rnode, MyBackendId);
+
+		/* allocate the initial array, or extend it, if needed */
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			srels = palloc(sizeof(SMgrRelation) * maxrels);
+			rnodes = palloc(sizeof(RelFileNode) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+			rnodes = repalloc(rnodes, sizeof(RelFileNode) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		srels[nrels] = srel;
+		rnodes[nrels] = rnode;
+		relkinds[nrels] = entry->relkind;
+		nrels++;
+	}
+
+	if (nrels > 0)
+	{
+		int i;
+
+		smgrdounlinkall(srels, nrels, false);
+
+		LWLockAcquire(&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.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (gtt_storage_local_hash == NULL)
+		return false;
+
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_FIND, &found);
+
+	if (!found)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	if (relpages)
+		*relpages = entry->relpages;
+
+	if (reltuples)
+		*reltuples = entry->reltuples;
+
+	if (relallvisible)
+		*relallvisible = entry->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = entry->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = entry->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	MemoryContext			oldcontext;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+						(void *) &reloid, HASH_FIND, &found);
+
+	if (!found)
+		return;
+
+	/* todo */
+	if (entry->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	Assert(entry->relid == reloid);
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == 0)
+		{
+			entry->attnum[i] = attnum;
+			break;
+		}
+		else if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < entry->natts);
+	Assert(entry->att_stat_tups[i] == NULL);
+	entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+						(void *) &reloid, HASH_FIND, &found);
+
+	if (!found)
+		return NULL;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			return entry->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int			i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	HeapTuple	tuple;
+	Relation	rel = NULL;
+	int		attnum = PG_GETARG_INT32(1);
+	Oid		reloid = PG_GETARG_OID(0);
+	char		rel_persistence;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	  tupdesc;
+	MemoryContext oldcontext;
+	Tuplestorestate *tupstore;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = table_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+
+	table_close(rel, NoLock);
+	table_close(pg_tatistic, AccessShareLock);
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate	*tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Relation	rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	if (get_gtt_relstats(reloid,
+						&relpages, &reltuples, &relallvisible,
+						&relfrozenxid, &relminmxid))
+	{
+		Datum	values[5];
+		bool	isnull[5];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = Int32GetDatum(relpages);
+		values[1] = Float4GetDatum((float4)reltuples);
+		values[2] = Int32GetDatum(relallvisible);
+		values[3] = UInt32GetDatum(relfrozenxid);
+		values[4] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+
+	tuplestore_donestoring(tupstore);
+	table_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(rel->rd_node);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	table_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(indexOid));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid relOid = index->rd_index->indrelid;
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	if (gtt_storage_attached(relOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int			i;
+	Oid			toastrelid;
+	List		*seqlist = NIL;
+	ListCell	*seqcell = NULL;
+
+	if (operation != CMD_INSERT)
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_attached(relation->rd_node.relNode))
+		return;
+
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+
+		index_build(relation, index, info, true, false);
+	}
+
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid 		indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo 	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	seqlist = getOwnedSequences(RelationGetRelid(relation));
+	foreach(seqcell, seqlist)
+	{
+		Oid 		seq_relid = lfirst_oid(seqcell);
+		Relation	seq_rel;
+
+		seq_rel = relation_open(seq_relid, RowExclusiveLock);
+	
+		/* This check must match AlterSequence! */
+		if (!pg_class_ownercheck(seq_relid, GetUserId()))
+			aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SEQUENCE,
+						   RelationGetRelationName(seq_rel));
+
+		RelationCreateStorage(seq_rel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seq_rel);
+		relation_close(seq_rel, NoLock);
+	}
+
+	return;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index f681aaf..e1fa3ec 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM 
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c4420dd..977a984 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -586,14 +587,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 		 * pg_statistic for columns we didn't process, we leave them alone.)
 		 */
 		update_attstats(RelationGetRelid(onerel), inh,
-						attr_cnt, vacattrstats);
+						attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
 
 		for (ind = 0; ind < nindexes; ind++)
 		{
 			AnlIndexData *thisdata = &indexdata[ind];
 
 			update_attstats(RelationGetRelid(Irel[ind]), false,
-							thisdata->attr_cnt, thisdata->vacattrstats);
+							thisdata->attr_cnt, thisdata->vacattrstats,
+							RelationGetRelPersistence(Irel[ind]));
 		}
 
 		/*
@@ -1456,7 +1458,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
  *		by taking a self-exclusive lock on the relation in analyze_rel().
  */
 static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
 {
 	Relation	sd;
 	int			attno;
@@ -1558,31 +1560,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/* Update column statistic to localhash, not catalog */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 11ce1bb..552fd87 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -391,6 +391,12 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/* not support cluster global temp table yet */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("not support cluster global temporary tables yet")));
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index e79ede4..078694d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -27,6 +27,7 @@
 #include "catalog/dependency.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -1062,7 +1063,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, stmt->filename, stmt->is_program,
@@ -2840,6 +2841,8 @@ CopyFrom(CopyState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	estate->es_result_relations = resultRelInfo;
 	estate->es_num_result_relations = 1;
 	estate->es_result_relation_info = resultRelInfo;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index ec20ba3..a29cc00 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2614,6 +2614,16 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 			!isTempNamespace(classtuple->relnamespace))
 			continue;
 
+		/* not support reindex on global temp table, so skip it */
+		if (classtuple->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			ereport(WARNING,
+				(errmsg("global temp table \"%s.%s\" skip reindexed",
+					get_namespace_name(get_rel_namespace(relid)),
+					get_rel_name(relid))));
+			continue;
+		}
+
 		/* Check user/system classification, and optionally skip */
 		if (objectKind == REINDEX_OBJECT_SYSTEM &&
 			!IsSystemClass(relid, classtuple))
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index d8cafc4..deecde8 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -96,7 +96,8 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 	 * transaction.
 	 */
 	relpersistence = get_rel_persistence(relid);
-	if (relpersistence == RELPERSISTENCE_TEMP)
+	if (relpersistence == RELPERSISTENCE_TEMP ||
+		relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
 
 	/* Check permissions. */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..c55dcf7 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -94,7 +94,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +108,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -222,7 +223,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* now initialize the sequence's data */
 	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -327,7 +328,7 @@ ResetSequence(Oid seq_relid)
 	/*
 	 * Insert the modified tuple into the new storage file.
 	 */
-	fill_seq_with_data(seq_rel, tuple);
+	fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -340,17 +341,21 @@ ResetSequence(Oid seq_relid)
  * Initialize a sequence's relation with the specified tuple as content
  */
 static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 	sequence_magic *sm;
 	OffsetNumber offnum;
+	bool lockBuffer = false;
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ReadBuffer(rel, P_NEW);
-	Assert(BufferGetBlockNumber(buf) == 0);
+	if (buf == InvalidBuffer)
+	{
+		buf = ReadBuffer(rel, P_NEW);
+		Assert(BufferGetBlockNumber(buf) == 0);
+		lockBuffer = true;
+	}
 
 	page = BufferGetPage(buf);
 
@@ -360,7 +365,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* Now insert sequence tuple */
 
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+	if (lockBuffer)
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +416,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	END_CRIT_SECTION();
 
-	UnlockReleaseBuffer(buf);
+	if (lockBuffer)
+		UnlockReleaseBuffer(buf);
 }
 
 /*
@@ -502,7 +509,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * Insert the modified tuple into the new storage file.
 		 */
-		fill_seq_with_data(seqrel, newdatatuple);
+		fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
 	}
 
 	/* process OWNED BY if given */
@@ -611,7 +618,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +943,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1178,6 +1185,25 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
 	LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
 
 	page = BufferGetPage(*buf);
+	if (GlobalTempRelationPageIsNotInitialized(rel, page))
+	{
+		/* Initialize sequence for global temporary tables */
+		Datum		value[SEQ_COL_LASTCOL] = {0};
+		bool		null[SEQ_COL_LASTCOL] = {false};
+		HeapTuple	tuple;
+		int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+		/*
+		 * last_value from pg_sequence.seqstart
+		 * log_cnt = 0
+		 * is_called = false
+		 */
+		value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+		tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+		fill_seq_with_data(rel, tuple, *buf);
+		heap_freetuple(tuple);
+	}
 	sm = (sequence_magic *) PageGetSpecialPointer(page);
 
 	if (sm->magic != SEQ_MAGIC)
@@ -1954,3 +1980,23 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index b7c8d66..ff34fea 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -553,6 +554,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 								  Relation partitionTbl);
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static bool has_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -598,6 +600,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
@@ -608,8 +611,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Check consistency of arguments
 	 */
+	/* global temp table same as local temp table */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !(stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+			 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -639,7 +644,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	/* global temp table same as local temp table */
+	if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+		 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -740,6 +747,53 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* global temp table */
+	has_oncommit_clause = has_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* check parent table*/
+		if (inheritOids && stmt->oncommit == ONCOMMIT_NOOP && !has_oncommit_clause)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			table_close(relation, NoLock);
+		}
+
+		if (has_oncommit_clause)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "can not defeine global temp table with on commit and with clause at same time");
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temp table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (has_oncommit_clause)
+		elog(ERROR, "regular table cannot specifie on_commit_delete_rows");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1824,7 +1878,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);
@@ -3392,6 +3447,13 @@ RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal, bo
 	 * specially.
 	 */
 	targetrelation = relation_open(myrelid, is_index ? ShareUpdateExclusiveLock : AccessExclusiveLock);
+
+	if (RELATION_IS_GLOBAL_TEMP(targetrelation))
+	{
+		if (is_other_backend_use_gtt(targetrelation->rd_node))
+			elog(ERROR, "can not rename relation when other backend attached this global temp table");
+	}
+
 	namespaceId = RelationGetNamespace(targetrelation);
 
 	/*
@@ -3567,6 +3629,13 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(rel->rd_node))
+			elog(ERROR, "can not alter relation when other backend attached this global temp table");
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -8179,6 +8248,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on temporary tables may reference only temporary tables")));
+			break;
 	}
 
 	/*
@@ -13221,7 +13296,7 @@ index_copy_data(Relation rel, RelFileNode newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
@@ -14628,7 +14703,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",
@@ -17269,3 +17346,20 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
 		table_close(rel, NoLock);
 	}
 }
+
+static bool
+has_oncommit_option(List *options)
+{
+	ListCell   *listptr;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (pg_strcasecmp(def->defname, "on_commit_delete_rows") == 0)
+			return true;
+	}
+
+	return false;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index d625d17..69ad24f 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/vacuum.h"
@@ -1217,6 +1218,17 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	 /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		is_gtt = true;
+		up_gtt_relstats(relation,
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1230,17 +1242,26 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relpages = (int32) num_pages;
+	else if (pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (is_gtt)
+		relation->rd_rel->reltuples = (float4) num_tuples;
+	else if (pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	else if (pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1285,7 +1306,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1296,7 +1318,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1394,6 +1417,10 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/* global temp table relstats not in pg_class */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1451,6 +1478,25 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/*
+	 * Global temp table get frozenxid from MyProc
+	 * to avoid the vacuum truncate clog that gtt need.
+	 */
+	if (max_active_gtt > 0)
+	{
+		TransactionId oldest_gtt_frozenxid =
+			list_all_session_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid) &&
+			TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+		{
+			ereport(WARNING,
+				(errmsg("global temp table oldest FrozenXid is far in the past"),
+				 errhint("please truncate them or kill those sessions that use them.")));
+			newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index ee5c3a6..0d7a2d4 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandTag((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index c13b1d3..253054d 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
@@ -543,6 +544,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	init_gtt_storage(mtstate->operation, leaf_part_rri);
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d71c0a4..0900907 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,6 +42,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -2372,6 +2373,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			ExecOpenIndices(resultRelInfo,
 							node->onConflictAction != ONCONFLICT_NONE);
 
+		init_gtt_storage(operation, resultRelInfo);
+
 		/*
 		 * If this is an UPDATE and a BEFORE UPDATE trigger is present, the
 		 * trigger itself might modify the partition-key values. So arrange
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 905bbe7..170963d 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -591,6 +591,8 @@ static void
 set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 						  RangeTblEntry *rte)
 {
+	char relpersistence;
+
 	/*
 	 * The flag has previously been initialized to false, so we can just
 	 * return if it becomes clear that we can't safely set it.
@@ -618,7 +620,11 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			/* global temp table is same as local temp table */
+			relpersistence = get_rel_persistence(rte->relid);
+
+			if (relpersistence == RELPERSISTENCE_TEMP ||
+				relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b44efd6..14dbaaf 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6307,7 +6307,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
+	/* global temp table is same as local temp table */
 	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index d82fc5a..93c6d97 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -28,6 +28,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temp table */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(indexRelation->rd_node.relNode))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6676412..0685c1c 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2585,6 +2585,11 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("materialized views must not use temporary tables or views")));
 
+		if (is_query_using_gtt(query))
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("materialized views must not use global temporary tables or views")));
+
 		/*
 		 * A materialized view would either need to save parameters for use in
 		 * maintaining/loading the data or prohibit them entirely.  The latter
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 96e7fdb..30f585c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3290,17 +3290,11 @@ OptTemp:	TEMPORARY					{ $$ = RELPERSISTENCE_TEMP; }
 			| LOCAL TEMP				{ $$ = RELPERSISTENCE_TEMP; }
 			| GLOBAL TEMPORARY
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
-					$$ = RELPERSISTENCE_TEMP;
+					$$ = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
-					$$ = RELPERSISTENCE_TEMP;
+					$$ = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED					{ $$ = RELPERSISTENCE_UNLOGGED; }
 			| /*EMPTY*/					{ $$ = RELPERSISTENCE_PERMANENT; }
@@ -11594,19 +11588,13 @@ OptTempTableName:
 				}
 			| GLOBAL TEMPORARY opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED opt_table qualified_name
 				{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b875a50..123945b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index ee2d2b5..9c9abaa 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 6d1f28c..ed837d1 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2086,6 +2086,11 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* autovacuum skip vacuum global temp table */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2152,7 +2157,9 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		/* autovacuum skip vacuum global temp table */
+		if (classForm->relpersistence == RELPERSISTENCE_TEMP || 
+			classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 5880054..98f7b43 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -52,6 +53,7 @@
 #include "utils/rel.h"
 #include "utils/resowner_private.h"
 #include "utils/timestamp.h"
+#include "utils/guc.h"
 
 
 /* Note: these two macros only work on shared buffers, not local ones! */
@@ -2809,6 +2811,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(relation->rd_node.relNode))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d5..7adb969 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -21,6 +21,7 @@
 #include "access/nbtree.h"
 #include "access/subtrans.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, active_gtt_shared_hash_size());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* global temporary table */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 4a5b26c..9754168 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -62,6 +62,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -4087,3 +4088,77 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temp table.
+ */
+int
+list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct *arrayP = procArray;
+	TransactionId result = InvalidTransactionId;
+	int			index;
+	int			flags = 0;
+	int			i = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+		*n = 0;
+	}
+
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		volatile PGXACT *pgxact = &allPgXact[pgprocno];
+
+		if (pgxact->vacuumFlags & flags)
+			continue;
+
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->session_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->session_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->session_gtt_frozenxid, result))
+				result = proc->session_gtt_frozenxid;
+
+			if (max_size > 0)
+			{
+				pids[i] = proc->pid;
+				xids[i] = proc->session_gtt_frozenxid;
+				i++;
+			}
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (max_size > 0)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index eb321f7..3893cef 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 8406644..0d99321 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -1008,6 +1008,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 0be26fe..c169c99 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -112,6 +112,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -4570,12 +4571,25 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -4700,15 +4714,27 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6090,6 +6116,7 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	{
 		/* Simple variable --- look to stats for the underlying table */
 		RangeTblEntry *rte = planner_rt_fetch(index->rel->relid, root);
+		char	rel_persistence = get_rel_persistence(rte->relid);
 
 		Assert(rte->rtekind == RTE_RELATION);
 		relid = rte->relid;
@@ -6107,6 +6134,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6118,6 +6152,8 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	}
 	else
 	{
+		char	rel_persistence = get_rel_persistence(index->indexoid);
+
 		/* Expression --- maybe there are stats for the index itself */
 		relid = index->indexoid;
 		colnum = 1;
@@ -6133,6 +6169,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7046,6 +7089,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		/* attempt to lookup stats in relation for this index column */
 		if (attnum != 0)
 		{
+			char	rel_persistence = get_rel_persistence(rte->relid);
+
 			/* Simple variable -- look to stats for the underlying table */
 			if (get_relation_stats_hook &&
 				(*get_relation_stats_hook) (root, rte, attnum, &vardata))
@@ -7058,6 +7103,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7070,6 +7123,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		}
 		else
 		{
+			char	rel_persistence = get_rel_persistence(index->indexoid);
+
 			/*
 			 * Looks like we've found an expression column in the index. Let's
 			 * see if there's any stats for it.
@@ -7089,6 +7144,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index fb0599f..13f4d8a 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -2878,6 +2879,18 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index ff70326..1cf7063 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -63,6 +63,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1127,6 +1128,24 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1181,6 +1200,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			gtt_fix_index_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -2220,6 +2240,8 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		gtt_fix_index_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3316,6 +3338,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3430,6 +3456,9 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
 
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+		elog(ERROR, "global temp table does not allow setting new relfilenode");
+
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
@@ -3470,7 +3499,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 				/* handle these directly, at least for now */
 				SMgrRelation srel;
 
-				srel = RelationCreateStorage(newrnode, persistence);
+				srel = RelationCreateStorage(newrnode, persistence, relation);
 				smgrclose(srel);
 			}
 			break;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 464f264..0173156 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -140,6 +140,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temp table feature.
+ * global temp table define can still storage in catalog
+ * just can not use.
+ * num > 0 means database can management num active global temp table.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2047,6 +2059,15 @@ static struct config_bool ConfigureNamesBool[] =
 static struct config_int ConfigureNamesInt[] =
 {
 	{
+		{"max_active_global_temporary_table", PGC_POSTMASTER, UNGROUPED,
+			gettext_noop("max active global temporary table."),
+			NULL
+		},
+		&max_active_gtt,
+		DEFAULT_NUM_ACTIVE_GTT, MIN_NUM_ACTIVE_GTT, MAX_NUM_ACTIVE_GTT,
+		NULL, NULL, NULL
+	},
+	{
 		{"archive_timeout", PGC_SIGHUP, WAL_ARCHIVING,
 			gettext_noop("Forces a switch to the next WAL file if a "
 						 "new file has not been started within N seconds."),
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index d92a662..07c99c5 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15598,6 +15598,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	{
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
+		char		*table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15649,9 +15650,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 			binary_upgrade_set_pg_class_oids(fout, q,
 											 tbinfo->dobj.catId.oid, false);
 
+		if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED)
+			table_type = "UNLOGGED ";
+		else if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			table_type = "GLOBAL TEMPORARY ";
+		else
+			table_type = "";
+
 		appendPQExpBuffer(q, "CREATE %s%s %s",
-						  tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ?
-						  "UNLOGGED " : "",
+						  table_type,
 						  reltypename,
 						  qualrelname);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index f3c7eb9..28134e2 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3723,7 +3723,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index dc03fbd..8bd6d09 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1016,6 +1016,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2378,6 +2380,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2586,6 +2591,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 78b33b2..4be89f6 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -168,6 +168,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index eb3c1a8..5206815 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5504,6 +5504,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4191',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4192',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{relid,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4193',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4194',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 048003c..af48cdf 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -19,7 +19,7 @@
 #include "storage/smgr.h"
 #include "utils/relcache.h"
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationTruncate(Relation rel, BlockNumber nblocks);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..3afd71d
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,43 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid);
+extern bool is_other_backend_use_gtt(RelFileNode node);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(RelFileNode node);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Relation relation,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void gtt_force_enable_index(Relation index);
+extern void gtt_fix_index_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 3f88683..f870e9a 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 8fda8e4..172eae9 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_TBM,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_SXACT,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index d217801..8adde87 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -117,6 +117,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId session_gtt_frozenxid;	/* session level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ce93ace..0f7262e 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -281,6 +281,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 44ed04d..6b6d2da 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	struct SMgrRelationData *rd_smgr;	/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -277,6 +277,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -535,11 +536,13 @@ typedef struct ViewOptions
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -547,6 +550,7 @@ typedef struct ViewOptions
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -559,6 +563,14 @@ typedef struct ViewOptions
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is either local temp relation of this session
+ * 		or global temp relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -602,6 +614,17 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..50ca9ac
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,7 @@
+reset search_path;
+drop schema gtt cascade;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..35bbc20
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,297 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+ERROR:  cannot create indexes on global temporary tables using concurrent mode
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+ERROR:  not support cluster global temporary tables yet
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ok
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  regular table cannot specifie on_commit_delete_rows
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  can not defeine global temp table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test;
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR:  insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count 
+-------
+     1
+(1 row)
+
+select count(*) from orders;
+ count 
+-------
+     0
+(1 row)
+
+-- ERROR
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 1)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 300)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 18 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to table gtt_function.gt1
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..30d8a7b
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,84 @@
+set search_path=gtt,sys;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..850ef3e
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,161 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          475136 |                 974848
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |           409600 |        409600 |                 409600
+(2 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..9fe5fd4
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,9 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..0e21b30
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,76 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+(1 row)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+(1 row)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 634f825..6e1297a 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1349,6 +1349,93 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d2b17dd..80e577f 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..f3cf710
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,6 @@
+
+
+reset search_path;
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..0274d99
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,199 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ok
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test;
+
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ERROR
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d7d81de
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,42 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..5203c2b
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,76 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..2f4d883
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,16 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..f041892
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,42 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
#157曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Prabhat Sahu (#152)
1 attachment(s)
Re: [Proposal] Global temporary tables

2020年2月24日 下午5:44,Prabhat Sahu <prabhat.sahu@enterprisedb.com> 写道:

On Fri, Feb 21, 2020 at 9:10 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> wrote:
Hi,
I have started testing the "Global temporary table" feature,
That's great, I see hope.
from "gtt_v11-pg13.patch". Below is my findings:

-- session 1:
postgres=# create global temporary table gtt1(a int);
CREATE TABLE

-- seeeion 2:
postgres=# truncate gtt1 ;
ERROR: could not open file "base/13585/t3_16384": No such file or directory

is it expected?

Oh ,this is a bug, I fixed it.
Thanks for the patch.
I have verified the same, Now the issue is resolved with v12 patch.

Kindly confirm the below scenario:

postgres=# create global temporary table gtt1 (c1 int unique);
CREATE TABLE

postgres=# create global temporary table gtt2 (c1 int references gtt1(c1) );
ERROR: referenced relation "gtt1" is not a global temp table

postgres=# create table tab2 (c1 int references gtt1(c1) );
ERROR: referenced relation "gtt1" is not a global temp table

Thanks,
Prabhat Sahu

GTT supports foreign key constraints in global_temporary_table_v13-pg13.patch

Wenjing

Attachments:

global_temporary_table_v13-pg13.patchapplication/octet-stream; name=global_temporary_table_v13-pg13.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 79430d2..babb5f3 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -158,6 +158,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * For global temp table only
+	 * use ShareUpdateExclusiveLock for ensure safety
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temp table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},	
 	/* list terminator */
 	{{NULL}}
 };
@@ -1486,6 +1499,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1586,13 +1601,18 @@ build_reloptions(Datum reloptions, bool validate,
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	/*
 	 * There are no options for partitioned tables yet, but this is able to do
 	 * some validation.
 	 */
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index dd975b1..1610e7d 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1013,7 +1013,9 @@ gistGetFakeLSN(Relation rel)
 {
 	static XLogRecPtr counter = FirstNormalUnloggedLSN;
 
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	/* global temp is same as local temp table */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 4871b7f..16b00c9 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -149,7 +149,9 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	/* global temp table is same as local temp table */
+	if (!(index->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		index->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 3fa4b76..b54882d 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -598,7 +598,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -651,7 +651,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 03c43ef..2674132 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -62,6 +62,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -399,9 +400,9 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
 	Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
-	/* not every AM requires these to be valid, but heap does */
-	Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
-	Assert(MultiXactIdIsValid(onerel->rd_rel->relminmxid));
+	/* not every AM requires these to be valid, but regular heap does */
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ MultiXactIdIsValid(onerel->rd_rel->relminmxid));
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index f05cbe7..f2d3b83 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -27,6 +27,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -589,6 +590,10 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(rel->rd_node.relNode))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index d19408b..5014268 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6347,6 +6347,10 @@ StartupXLOG(void)
 	else
 		recoveryTargetTLI = ControlFile->checkPointCopy.ThisTimeLineID;
 
+	/* clean temp relation files */
+	if (max_active_gtt > 0)
+		RemovePgTempFiles();
+
 	/*
 	 * Check for signal files, and if so set up state for offline recovery
 	 */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index f8f0b48..dc8bbb1 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -42,6 +42,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 7d6acae..3be8d63 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -393,7 +393,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/* global temp table is same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index e31478b..1c382c6 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -427,7 +429,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -957,6 +959,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -995,8 +998,18 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/* global temp table not remember transaction info in catalog */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1358,6 +1371,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1938,6 +1952,13 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/* We allow to drop global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(rel->rd_node))
+			elog(ERROR, "can not drop relation when other backend attached this global temp table");
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3165,7 +3186,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3177,7 +3198,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3223,8 +3244,13 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		/* truncate global temp table only need RowExclusiveLock */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3257,6 +3283,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3265,23 +3292,37 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (!gtt_storage_attached(rel->rd_node.relNode))
+			return;
+
+		/*
+		 * Truncate global temp table only need RowExclusiveLock
+		 */
+		lockmode = RowExclusiveLock;
+	}
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(rel, 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 8880586..a23b499 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -52,6 +52,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -877,6 +878,19 @@ index_create(Relation heapRelation,
 						indexRelationName, RelationGetRelationName(heapRelation))));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temp table use concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot create indexes on global temporary tables using concurrent mode")));
+
+		/* if global temp table not init storage, then skip build index */
+		if (!gtt_storage_attached(heapRelation->rd_node.relNode))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -2054,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.
@@ -2660,6 +2681,11 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	/* update index stats into localhash and rel_rd_rel for global temp table */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		is_gtt = true;
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2745,21 +2771,35 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
+		if (is_gtt)
+			rel->rd_rel->relpages = (int32) relpages;
+		else if (rd_rel->relpages != (int32) relpages)
 		{
 			rd_rel->relpages = (int32) relpages;
 			dirty = true;
 		}
-		if (rd_rel->reltuples != (float4) reltuples)
+
+		if (is_gtt)
+			rel->rd_rel->reltuples = (float4) reltuples;
+		else if (rd_rel->reltuples != (float4) reltuples)
 		{
 			rd_rel->reltuples = (float4) reltuples;
 			dirty = true;
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+
+		if (is_gtt)
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+		else if (rd_rel->relallvisible != (int32) relallvisible)
 		{
 			rd_rel->relallvisible = (int32) relallvisible;
 			dirty = true;
 		}
+
+		if (is_gtt)
+		{
+			up_gtt_relstats(rel, relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
+		}
 	}
 
 	/*
@@ -2873,6 +2913,15 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, index, val);
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		if (!gtt_storage_attached(indexRelation->rd_node.relNode))
+		{
+			gtt_force_enable_index(indexRelation);
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3468,6 +3517,15 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 				 errmsg("cannot reindex temporary tables of other sessions")));
 
 	/*
+	 * Because global temp table cannot change relfilenode
+	 * no support reindex on global temp table yet.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(iRel))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot reindex global temporary tables")));
+
+	/*
 	 * Also check for active uses of the index in the current transaction; we
 	 * don't want to reindex underneath an open indexscan.
 	 */
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index e70243a..301da79 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -647,6 +647,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		/* global temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp relations in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index fddfbf1..671c614 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -26,6 +26,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -75,7 +76,7 @@ static PendingRelDelete *pendingDeletes = NULL; /* head of linked list */
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -85,6 +86,8 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		/* global temp table use same storage strategy as local temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -117,6 +120,10 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/* remember global temp table storage info to localhash */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel)
+		remember_gtt_storage_info(rnode, rel);
+
 	return srel;
 }
 
@@ -486,8 +493,15 @@ smgrDoPendingDeletes(bool isCommit)
 		smgrdounlinkall(srels, nrels, false);
 
 		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* clean global temp table flags when transaction commit or rollback */
+			if (SmgrIsTemp(srels[i]) && 
+				gtt_storage_attached(srels[i]->smgr_rnode.node.relNode))
+				forget_gtt_storage_info(srels[i]->smgr_rnode.node.relNode);
+		}
+
 		pfree(srels);
 	}
 }
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..e877b0c
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1260 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  code to create and destroy physical storage for global temparary table
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_relstats_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+typedef struct gtt_ctl_data
+{
+	LWLock			lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct
+{
+	RelFileNode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+	Oid			relid;
+
+	Oid			spcnode;
+	/* pg_class stat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+	char		relkind;
+	bool		on_commit_delete;
+
+	/* pg_statistic */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(RelFileNode rnode);
+static void gtt_storage_checkout(RelFileNode rnode, bool skiplock);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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;
+	int			natts = 0;
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_gtt to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temp table yet");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create stroage file", RelationGetRelationName(rel));
+
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		/* First time through: initialize the hash table */
+		HASHCTL		ctl;
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		gtt_storage_local_hash =
+			hash_create("global temp relation table",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_relstats_context =
+			AllocSetContextCreate(CacheMemoryContext,
+							"gtt relstats context",
+							ALLOCSET_DEFAULT_SIZES);
+	}
+
+	/* Look up or create an entry */
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_ENTER, &found);
+
+	if (found)
+	{
+		elog(ERROR, "backend %d relid %u already exists in global temp table local hash",
+					MyBackendId, relid);
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+	entry->spcnode = rnode.spcNode;
+	entry->relpages = 0;
+	entry->reltuples = 0;
+	entry->relallvisible = 0;
+	entry->relkind = rel->rd_rel->relkind;
+	natts = RelationGetNumberOfAttributes(rel);
+	entry->natts = natts;
+	entry->attnum = palloc0(sizeof(int) * natts);
+	entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+	/* only heap contain transaction information */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+		{
+			entry->on_commit_delete = true;
+			register_on_commit_action(rel->rd_node.relNode, ONCOMMIT_DELETE_ROWS);
+		}
+
+		entry->relfrozenxid = RecentXmin;
+		entry->relminmxid = GetOldestMultiXactId();
+		insert_gtt_relfrozenxid_to_ordered_list(entry->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+		gtt_storage_checkin(rnode);
+	}
+	else
+	{
+		entry->on_commit_delete = false;
+		entry->relfrozenxid = 0;
+		entry->relminmxid = 0;
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+void
+forget_gtt_storage_info(Oid relid)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry)
+	{
+		int		i;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			RelFileNode rnode;
+
+			rnode.spcNode = entry->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = entry->relid;
+
+			gtt_storage_checkout(rnode, false);
+
+			Assert(TransactionIdIsNormal(entry->relfrozenxid));
+			remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+
+		for (i = 0; i < entry->natts; i++)
+		{
+			if (entry->att_stat_tups[i])
+			{
+				heap_freetuple(entry->att_stat_tups[i]);
+				entry->att_stat_tups[i] = NULL;
+			}
+		}
+
+		pfree(entry->attnum);
+		pfree(entry->att_stat_tups);
+	}
+
+	hash_search(gtt_storage_local_hash,
+			(void *) &(relid), HASH_REMOVE, NULL);
+
+	return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool found;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (gtt_storage_local_hash == NULL)
+		return false;
+
+	hash_search(gtt_storage_local_hash,
+			(void *) &(relid), HASH_FIND, &found);
+
+	return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS				status;
+	gtt_local_hash_entry		*entry;
+	int			nrels = 0,
+				maxrels = 0;
+	SMgrRelation	*srels = NULL;
+	RelFileNode		*rnodes = NULL;
+	char			*relkinds = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		SMgrRelation srel;
+		RelFileNode rnode;
+
+		rnode.spcNode = entry->spcnode;
+		rnode.dbNode = MyDatabaseId;
+		rnode.relNode = entry->relid;
+
+		srel = smgropen(rnode, MyBackendId);
+
+		/* allocate the initial array, or extend it, if needed */
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			srels = palloc(sizeof(SMgrRelation) * maxrels);
+			rnodes = palloc(sizeof(RelFileNode) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+			rnodes = repalloc(rnodes, sizeof(RelFileNode) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		srels[nrels] = srel;
+		rnodes[nrels] = rnode;
+		relkinds[nrels] = entry->relkind;
+		nrels++;
+	}
+
+	if (nrels > 0)
+	{
+		int i;
+
+		smgrdounlinkall(srels, nrels, false);
+
+		LWLockAcquire(&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.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (gtt_storage_local_hash == NULL)
+		return false;
+
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_FIND, &found);
+
+	if (!found)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	if (relpages)
+		*relpages = entry->relpages;
+
+	if (reltuples)
+		*reltuples = entry->reltuples;
+
+	if (relallvisible)
+		*relallvisible = entry->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = entry->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = entry->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	MemoryContext			oldcontext;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+						(void *) &reloid, HASH_FIND, &found);
+
+	if (!found)
+		return;
+
+	/* todo */
+	if (entry->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	Assert(entry->relid == reloid);
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == 0)
+		{
+			entry->attnum[i] = attnum;
+			break;
+		}
+		else if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < entry->natts);
+	Assert(entry->att_stat_tups[i] == NULL);
+	entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+						(void *) &reloid, HASH_FIND, &found);
+
+	if (!found)
+		return NULL;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			return entry->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int			i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	HeapTuple	tuple;
+	Relation	rel = NULL;
+	int		attnum = PG_GETARG_INT32(1);
+	Oid		reloid = PG_GETARG_OID(0);
+	char		rel_persistence;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	  tupdesc;
+	MemoryContext oldcontext;
+	Tuplestorestate *tupstore;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = table_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+
+	table_close(rel, NoLock);
+	table_close(pg_tatistic, AccessShareLock);
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate	*tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Relation	rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	if (get_gtt_relstats(reloid,
+						&relpages, &reltuples, &relallvisible,
+						&relfrozenxid, &relminmxid))
+	{
+		Datum	values[5];
+		bool	isnull[5];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = Int32GetDatum(relpages);
+		values[1] = Float4GetDatum((float4)reltuples);
+		values[2] = Int32GetDatum(relallvisible);
+		values[3] = UInt32GetDatum(relfrozenxid);
+		values[4] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+
+	tuplestore_donestoring(tupstore);
+	table_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(rel->rd_node);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	table_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(indexOid));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid relOid = index->rd_index->indrelid;
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	if (gtt_storage_attached(relOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int			i;
+	Oid			toastrelid;
+	List		*seqlist = NIL;
+	ListCell	*seqcell = NULL;
+
+	if (operation != CMD_INSERT)
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_attached(relation->rd_node.relNode))
+		return;
+
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+
+		index_build(relation, index, info, true, false);
+	}
+
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid 		indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo 	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	seqlist = getOwnedSequences(RelationGetRelid(relation));
+	foreach(seqcell, seqlist)
+	{
+		Oid 		seq_relid = lfirst_oid(seqcell);
+		Relation	seq_rel;
+
+		seq_rel = relation_open(seq_relid, RowExclusiveLock);
+	
+		/* This check must match AlterSequence! */
+		if (!pg_class_ownercheck(seq_relid, GetUserId()))
+			aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SEQUENCE,
+						   RelationGetRelationName(seq_rel));
+
+		RelationCreateStorage(seq_rel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seq_rel);
+		relation_close(seq_rel, NoLock);
+	}
+
+	return;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index f681aaf..e1fa3ec 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM 
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c4420dd..977a984 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -586,14 +587,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 		 * pg_statistic for columns we didn't process, we leave them alone.)
 		 */
 		update_attstats(RelationGetRelid(onerel), inh,
-						attr_cnt, vacattrstats);
+						attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
 
 		for (ind = 0; ind < nindexes; ind++)
 		{
 			AnlIndexData *thisdata = &indexdata[ind];
 
 			update_attstats(RelationGetRelid(Irel[ind]), false,
-							thisdata->attr_cnt, thisdata->vacattrstats);
+							thisdata->attr_cnt, thisdata->vacattrstats,
+							RelationGetRelPersistence(Irel[ind]));
 		}
 
 		/*
@@ -1456,7 +1458,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
  *		by taking a self-exclusive lock on the relation in analyze_rel().
  */
 static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
 {
 	Relation	sd;
 	int			attno;
@@ -1558,31 +1560,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/* Update column statistic to localhash, not catalog */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 11ce1bb..552fd87 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -391,6 +391,12 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/* not support cluster global temp table yet */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("not support cluster global temporary tables yet")));
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index e79ede4..078694d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -27,6 +27,7 @@
 #include "catalog/dependency.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -1062,7 +1063,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, stmt->filename, stmt->is_program,
@@ -2840,6 +2841,8 @@ CopyFrom(CopyState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	estate->es_result_relations = resultRelInfo;
 	estate->es_num_result_relations = 1;
 	estate->es_result_relation_info = resultRelInfo;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index ec20ba3..a29cc00 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2614,6 +2614,16 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 			!isTempNamespace(classtuple->relnamespace))
 			continue;
 
+		/* not support reindex on global temp table, so skip it */
+		if (classtuple->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			ereport(WARNING,
+				(errmsg("global temp table \"%s.%s\" skip reindexed",
+					get_namespace_name(get_rel_namespace(relid)),
+					get_rel_name(relid))));
+			continue;
+		}
+
 		/* Check user/system classification, and optionally skip */
 		if (objectKind == REINDEX_OBJECT_SYSTEM &&
 			!IsSystemClass(relid, classtuple))
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index d8cafc4..deecde8 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -96,7 +96,8 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 	 * transaction.
 	 */
 	relpersistence = get_rel_persistence(relid);
-	if (relpersistence == RELPERSISTENCE_TEMP)
+	if (relpersistence == RELPERSISTENCE_TEMP ||
+		relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
 
 	/* Check permissions. */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..c55dcf7 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -94,7 +94,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +108,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -222,7 +223,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* now initialize the sequence's data */
 	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -327,7 +328,7 @@ ResetSequence(Oid seq_relid)
 	/*
 	 * Insert the modified tuple into the new storage file.
 	 */
-	fill_seq_with_data(seq_rel, tuple);
+	fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -340,17 +341,21 @@ ResetSequence(Oid seq_relid)
  * Initialize a sequence's relation with the specified tuple as content
  */
 static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 	sequence_magic *sm;
 	OffsetNumber offnum;
+	bool lockBuffer = false;
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ReadBuffer(rel, P_NEW);
-	Assert(BufferGetBlockNumber(buf) == 0);
+	if (buf == InvalidBuffer)
+	{
+		buf = ReadBuffer(rel, P_NEW);
+		Assert(BufferGetBlockNumber(buf) == 0);
+		lockBuffer = true;
+	}
 
 	page = BufferGetPage(buf);
 
@@ -360,7 +365,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* Now insert sequence tuple */
 
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+	if (lockBuffer)
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +416,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	END_CRIT_SECTION();
 
-	UnlockReleaseBuffer(buf);
+	if (lockBuffer)
+		UnlockReleaseBuffer(buf);
 }
 
 /*
@@ -502,7 +509,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * Insert the modified tuple into the new storage file.
 		 */
-		fill_seq_with_data(seqrel, newdatatuple);
+		fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
 	}
 
 	/* process OWNED BY if given */
@@ -611,7 +618,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +943,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1178,6 +1185,25 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
 	LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
 
 	page = BufferGetPage(*buf);
+	if (GlobalTempRelationPageIsNotInitialized(rel, page))
+	{
+		/* Initialize sequence for global temporary tables */
+		Datum		value[SEQ_COL_LASTCOL] = {0};
+		bool		null[SEQ_COL_LASTCOL] = {false};
+		HeapTuple	tuple;
+		int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+		/*
+		 * last_value from pg_sequence.seqstart
+		 * log_cnt = 0
+		 * is_called = false
+		 */
+		value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+		tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+		fill_seq_with_data(rel, tuple, *buf);
+		heap_freetuple(tuple);
+	}
 	sm = (sequence_magic *) PageGetSpecialPointer(page);
 
 	if (sm->magic != SEQ_MAGIC)
@@ -1954,3 +1980,23 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index b7c8d66..ff34fea 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -553,6 +554,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 								  Relation partitionTbl);
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static bool has_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -598,6 +600,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
@@ -608,8 +611,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Check consistency of arguments
 	 */
+	/* global temp table same as local temp table */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !(stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+			 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -639,7 +644,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	/* global temp table same as local temp table */
+	if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+		 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -740,6 +747,53 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* global temp table */
+	has_oncommit_clause = has_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* check parent table*/
+		if (inheritOids && stmt->oncommit == ONCOMMIT_NOOP && !has_oncommit_clause)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			table_close(relation, NoLock);
+		}
+
+		if (has_oncommit_clause)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "can not defeine global temp table with on commit and with clause at same time");
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temp table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (has_oncommit_clause)
+		elog(ERROR, "regular table cannot specifie on_commit_delete_rows");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1824,7 +1878,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);
@@ -3392,6 +3447,13 @@ RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal, bo
 	 * specially.
 	 */
 	targetrelation = relation_open(myrelid, is_index ? ShareUpdateExclusiveLock : AccessExclusiveLock);
+
+	if (RELATION_IS_GLOBAL_TEMP(targetrelation))
+	{
+		if (is_other_backend_use_gtt(targetrelation->rd_node))
+			elog(ERROR, "can not rename relation when other backend attached this global temp table");
+	}
+
 	namespaceId = RelationGetNamespace(targetrelation);
 
 	/*
@@ -3567,6 +3629,13 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(rel->rd_node))
+			elog(ERROR, "can not alter relation when other backend attached this global temp table");
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -8179,6 +8248,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on temporary tables may reference only temporary tables")));
+			break;
 	}
 
 	/*
@@ -13221,7 +13296,7 @@ index_copy_data(Relation rel, RelFileNode newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
@@ -14628,7 +14703,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",
@@ -17269,3 +17346,20 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
 		table_close(rel, NoLock);
 	}
 }
+
+static bool
+has_oncommit_option(List *options)
+{
+	ListCell   *listptr;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (pg_strcasecmp(def->defname, "on_commit_delete_rows") == 0)
+			return true;
+	}
+
+	return false;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index d625d17..69ad24f 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/vacuum.h"
@@ -1217,6 +1218,17 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	 /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		is_gtt = true;
+		up_gtt_relstats(relation,
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1230,17 +1242,26 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relpages = (int32) num_pages;
+	else if (pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (is_gtt)
+		relation->rd_rel->reltuples = (float4) num_tuples;
+	else if (pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	else if (pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1285,7 +1306,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1296,7 +1318,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1394,6 +1417,10 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/* global temp table relstats not in pg_class */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1451,6 +1478,25 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/*
+	 * Global temp table get frozenxid from MyProc
+	 * to avoid the vacuum truncate clog that gtt need.
+	 */
+	if (max_active_gtt > 0)
+	{
+		TransactionId oldest_gtt_frozenxid =
+			list_all_session_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid) &&
+			TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+		{
+			ereport(WARNING,
+				(errmsg("global temp table oldest FrozenXid is far in the past"),
+				 errhint("please truncate them or kill those sessions that use them.")));
+			newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index ee5c3a6..0d7a2d4 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandTag((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index c13b1d3..253054d 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
@@ -543,6 +544,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	init_gtt_storage(mtstate->operation, leaf_part_rri);
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d71c0a4..0900907 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,6 +42,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -2372,6 +2373,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			ExecOpenIndices(resultRelInfo,
 							node->onConflictAction != ONCONFLICT_NONE);
 
+		init_gtt_storage(operation, resultRelInfo);
+
 		/*
 		 * If this is an UPDATE and a BEFORE UPDATE trigger is present, the
 		 * trigger itself might modify the partition-key values. So arrange
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 905bbe7..170963d 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -591,6 +591,8 @@ static void
 set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 						  RangeTblEntry *rte)
 {
+	char relpersistence;
+
 	/*
 	 * The flag has previously been initialized to false, so we can just
 	 * return if it becomes clear that we can't safely set it.
@@ -618,7 +620,11 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			/* global temp table is same as local temp table */
+			relpersistence = get_rel_persistence(rte->relid);
+
+			if (relpersistence == RELPERSISTENCE_TEMP ||
+				relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b44efd6..14dbaaf 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6307,7 +6307,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
+	/* global temp table is same as local temp table */
 	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index d82fc5a..93c6d97 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -28,6 +28,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temp table */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(indexRelation->rd_node.relNode))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6676412..0685c1c 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2585,6 +2585,11 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("materialized views must not use temporary tables or views")));
 
+		if (is_query_using_gtt(query))
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("materialized views must not use global temporary tables or views")));
+
 		/*
 		 * A materialized view would either need to save parameters for use in
 		 * maintaining/loading the data or prohibit them entirely.  The latter
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 96e7fdb..30f585c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3290,17 +3290,11 @@ OptTemp:	TEMPORARY					{ $$ = RELPERSISTENCE_TEMP; }
 			| LOCAL TEMP				{ $$ = RELPERSISTENCE_TEMP; }
 			| GLOBAL TEMPORARY
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
-					$$ = RELPERSISTENCE_TEMP;
+					$$ = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
-					$$ = RELPERSISTENCE_TEMP;
+					$$ = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED					{ $$ = RELPERSISTENCE_UNLOGGED; }
 			| /*EMPTY*/					{ $$ = RELPERSISTENCE_PERMANENT; }
@@ -11594,19 +11588,13 @@ OptTempTableName:
 				}
 			| GLOBAL TEMPORARY opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED opt_table qualified_name
 				{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b875a50..123945b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index ee2d2b5..9c9abaa 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 6d1f28c..ed837d1 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2086,6 +2086,11 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* autovacuum skip vacuum global temp table */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2152,7 +2157,9 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		/* autovacuum skip vacuum global temp table */
+		if (classForm->relpersistence == RELPERSISTENCE_TEMP || 
+			classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 5880054..98f7b43 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -52,6 +53,7 @@
 #include "utils/rel.h"
 #include "utils/resowner_private.h"
 #include "utils/timestamp.h"
+#include "utils/guc.h"
 
 
 /* Note: these two macros only work on shared buffers, not local ones! */
@@ -2809,6 +2811,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(relation->rd_node.relNode))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d5..7adb969 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -21,6 +21,7 @@
 #include "access/nbtree.h"
 #include "access/subtrans.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, active_gtt_shared_hash_size());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* global temporary table */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 4a5b26c..9754168 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -62,6 +62,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -4087,3 +4088,77 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temp table.
+ */
+int
+list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct *arrayP = procArray;
+	TransactionId result = InvalidTransactionId;
+	int			index;
+	int			flags = 0;
+	int			i = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+		*n = 0;
+	}
+
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		volatile PGXACT *pgxact = &allPgXact[pgprocno];
+
+		if (pgxact->vacuumFlags & flags)
+			continue;
+
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->session_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->session_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->session_gtt_frozenxid, result))
+				result = proc->session_gtt_frozenxid;
+
+			if (max_size > 0)
+			{
+				pids[i] = proc->pid;
+				xids[i] = proc->session_gtt_frozenxid;
+				i++;
+			}
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (max_size > 0)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index eb321f7..3893cef 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 8406644..0d99321 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -1008,6 +1008,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 0be26fe..c169c99 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -112,6 +112,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -4570,12 +4571,25 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -4700,15 +4714,27 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6090,6 +6116,7 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	{
 		/* Simple variable --- look to stats for the underlying table */
 		RangeTblEntry *rte = planner_rt_fetch(index->rel->relid, root);
+		char	rel_persistence = get_rel_persistence(rte->relid);
 
 		Assert(rte->rtekind == RTE_RELATION);
 		relid = rte->relid;
@@ -6107,6 +6134,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6118,6 +6152,8 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	}
 	else
 	{
+		char	rel_persistence = get_rel_persistence(index->indexoid);
+
 		/* Expression --- maybe there are stats for the index itself */
 		relid = index->indexoid;
 		colnum = 1;
@@ -6133,6 +6169,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7046,6 +7089,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		/* attempt to lookup stats in relation for this index column */
 		if (attnum != 0)
 		{
+			char	rel_persistence = get_rel_persistence(rte->relid);
+
 			/* Simple variable -- look to stats for the underlying table */
 			if (get_relation_stats_hook &&
 				(*get_relation_stats_hook) (root, rte, attnum, &vardata))
@@ -7058,6 +7103,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7070,6 +7123,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		}
 		else
 		{
+			char	rel_persistence = get_rel_persistence(index->indexoid);
+
 			/*
 			 * Looks like we've found an expression column in the index. Let's
 			 * see if there's any stats for it.
@@ -7089,6 +7144,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index fb0599f..13f4d8a 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -2878,6 +2879,18 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index ff70326..1cf7063 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -63,6 +63,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1127,6 +1128,24 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1181,6 +1200,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			gtt_fix_index_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -2220,6 +2240,8 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		gtt_fix_index_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3316,6 +3338,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3430,6 +3456,9 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
 
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+		elog(ERROR, "global temp table does not allow setting new relfilenode");
+
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
@@ -3470,7 +3499,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 				/* handle these directly, at least for now */
 				SMgrRelation srel;
 
-				srel = RelationCreateStorage(newrnode, persistence);
+				srel = RelationCreateStorage(newrnode, persistence, relation);
 				smgrclose(srel);
 			}
 			break;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 464f264..0173156 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -140,6 +140,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temp table feature.
+ * global temp table define can still storage in catalog
+ * just can not use.
+ * num > 0 means database can management num active global temp table.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2047,6 +2059,15 @@ static struct config_bool ConfigureNamesBool[] =
 static struct config_int ConfigureNamesInt[] =
 {
 	{
+		{"max_active_global_temporary_table", PGC_POSTMASTER, UNGROUPED,
+			gettext_noop("max active global temporary table."),
+			NULL
+		},
+		&max_active_gtt,
+		DEFAULT_NUM_ACTIVE_GTT, MIN_NUM_ACTIVE_GTT, MAX_NUM_ACTIVE_GTT,
+		NULL, NULL, NULL
+	},
+	{
 		{"archive_timeout", PGC_SIGHUP, WAL_ARCHIVING,
 			gettext_noop("Forces a switch to the next WAL file if a "
 						 "new file has not been started within N seconds."),
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index d92a662..07c99c5 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15598,6 +15598,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	{
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
+		char		*table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15649,9 +15650,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 			binary_upgrade_set_pg_class_oids(fout, q,
 											 tbinfo->dobj.catId.oid, false);
 
+		if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED)
+			table_type = "UNLOGGED ";
+		else if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			table_type = "GLOBAL TEMPORARY ";
+		else
+			table_type = "";
+
 		appendPQExpBuffer(q, "CREATE %s%s %s",
-						  tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ?
-						  "UNLOGGED " : "",
+						  table_type,
 						  reltypename,
 						  qualrelname);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index f3c7eb9..28134e2 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3723,7 +3723,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index dc03fbd..8bd6d09 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1016,6 +1016,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2378,6 +2380,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2586,6 +2591,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 78b33b2..4be89f6 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -168,6 +168,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index eb3c1a8..5206815 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5504,6 +5504,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4191',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4192',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{relid,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4193',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4194',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 048003c..af48cdf 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -19,7 +19,7 @@
 #include "storage/smgr.h"
 #include "utils/relcache.h"
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationTruncate(Relation rel, BlockNumber nblocks);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..3afd71d
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,43 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid);
+extern bool is_other_backend_use_gtt(RelFileNode node);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(RelFileNode node);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Relation relation,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void gtt_force_enable_index(Relation index);
+extern void gtt_fix_index_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 3f88683..f870e9a 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 8fda8e4..172eae9 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_TBM,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_SXACT,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index d217801..8adde87 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -117,6 +117,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId session_gtt_frozenxid;	/* session level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ce93ace..0f7262e 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -281,6 +281,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 44ed04d..6b6d2da 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	struct SMgrRelationData *rd_smgr;	/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -277,6 +277,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -535,11 +536,13 @@ typedef struct ViewOptions
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -547,6 +550,7 @@ typedef struct ViewOptions
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -559,6 +563,14 @@ typedef struct ViewOptions
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is either local temp relation of this session
+ * 		or global temp relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -602,6 +614,17 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..50ca9ac
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,7 @@
+reset search_path;
+drop schema gtt cascade;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..35bbc20
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,297 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+ERROR:  cannot create indexes on global temporary tables using concurrent mode
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+ERROR:  not support cluster global temporary tables yet
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ok
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  regular table cannot specifie on_commit_delete_rows
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  can not defeine global temp table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test;
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR:  insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count 
+-------
+     1
+(1 row)
+
+select count(*) from orders;
+ count 
+-------
+     0
+(1 row)
+
+-- ERROR
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 1)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 300)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 18 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to table gtt_function.gt1
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..30d8a7b
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,84 @@
+set search_path=gtt,sys;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..850ef3e
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,161 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          475136 |                 974848
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |           409600 |        409600 |                 409600
+(2 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..9fe5fd4
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,9 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..0e21b30
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,76 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+(1 row)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+(1 row)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 634f825..6e1297a 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1349,6 +1349,93 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d2b17dd..80e577f 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..f3cf710
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,6 @@
+
+
+reset search_path;
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..0274d99
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,199 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ok
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test;
+
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ERROR
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d7d81de
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,42 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..5203c2b
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,76 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..2f4d883
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,16 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..f041892
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,42 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
#158Prabhat Sahu
prabhat.sahu@enterprisedb.com
In reply to: 曾文旌(义从) (#157)
Re: [Proposal] Global temporary tables

Hi All,

Please check the below findings on GTT.
*-- Scenario 1:*
Under "information_schema", We are not allowed to create "temporary table",
whereas we can CREATE/DROP "Global Temporary Table", is it expected ?

postgres=# create temporary table information_schema.temp1(c1 int);
ERROR: cannot create temporary relation in non-temporary schema
LINE 1: create temporary table information_schema.temp1(c1 int);
^

postgres=# create global temporary table information_schema.temp1(c1 int);
CREATE TABLE

postgres=# drop table information_schema.temp1 ;
DROP TABLE

*-- Scenario 2:*
Here I am getting the same error message in both the below cases.
We may add a "global" keyword with GTT related error message.

postgres=# create global temporary table gtt1 (c1 int unique);
CREATE TABLE
postgres=# create temporary table tmp1 (c1 int unique);
CREATE TABLE

postgres=# create temporary table tmp2 (c1 int references gtt1(c1) );
ERROR: constraints on temporary tables may reference only temporary tables

postgres=# create global temporary table gtt2 (c1 int references tmp1(c1) );
ERROR: constraints on temporary tables may reference only temporary tables

Thanks,
Prabhat Sahu

On Tue, Feb 25, 2020 at 2:25 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com> wrote:

2020年2月24日 下午5:44,Prabhat Sahu <prabhat.sahu@enterprisedb.com> 写道:

On Fri, Feb 21, 2020 at 9:10 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
wrote:

Hi,
I have started testing the "Global temporary table" feature,
That's great, I see hope.
from "gtt_v11-pg13.patch". Below is my findings:

-- session 1:
postgres=# create global temporary table gtt1(a int);
CREATE TABLE

-- seeeion 2:
postgres=# truncate gtt1 ;
ERROR: could not open file "base/13585/t3_16384": No such file or
directory

is it expected?

Oh ,this is a bug, I fixed it.

Thanks for the patch.
I have verified the same, Now the issue is resolved with v12 patch.

Kindly confirm the below scenario:

postgres=# create global temporary table gtt1 (c1 int unique);
CREATE TABLE

postgres=# create global temporary table gtt2 (c1 int references gtt1(c1)
);
ERROR: referenced relation "gtt1" is not a global temp table

postgres=# create table tab2 (c1 int references gtt1(c1) );
ERROR: referenced relation "gtt1" is not a global temp table

Thanks,
Prabhat Sahu

GTT supports foreign key constraints
in global_temporary_table_v13-pg13.patch

Wenjing

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

#159Pavel Stehule
pavel.stehule@gmail.com
In reply to: Prabhat Sahu (#158)
Re: [Proposal] Global temporary tables

út 25. 2. 2020 v 14:36 odesílatel Prabhat Sahu <
prabhat.sahu@enterprisedb.com> napsal:

Hi All,

Please check the below findings on GTT.
*-- Scenario 1:*
Under "information_schema", We are not allowed to create "temporary
table", whereas we can CREATE/DROP "Global Temporary Table", is it expected
?

It is ok for me. temporary tables should be created only in proprietary
schema. For GTT there is not risk of collision, so it can be created in any
schema where are necessary access rights.

Pavel

Show quoted text

postgres=# create temporary table information_schema.temp1(c1 int);
ERROR: cannot create temporary relation in non-temporary schema
LINE 1: create temporary table information_schema.temp1(c1 int);
^

postgres=# create global temporary table information_schema.temp1(c1 int);
CREATE TABLE

postgres=# drop table information_schema.temp1 ;
DROP TABLE

*-- Scenario 2:*
Here I am getting the same error message in both the below cases.
We may add a "global" keyword with GTT related error message.

postgres=# create global temporary table gtt1 (c1 int unique);
CREATE TABLE
postgres=# create temporary table tmp1 (c1 int unique);
CREATE TABLE

postgres=# create temporary table tmp2 (c1 int references gtt1(c1) );
ERROR: constraints on temporary tables may reference only temporary tables

postgres=# create global temporary table gtt2 (c1 int references tmp1(c1)
);
ERROR: constraints on temporary tables may reference only temporary tables

Thanks,
Prabhat Sahu

On Tue, Feb 25, 2020 at 2:25 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
wrote:

2020年2月24日 下午5:44,Prabhat Sahu <prabhat.sahu@enterprisedb.com> 写道:

On Fri, Feb 21, 2020 at 9:10 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
wrote:

Hi,
I have started testing the "Global temporary table" feature,
That's great, I see hope.
from "gtt_v11-pg13.patch". Below is my findings:

-- session 1:
postgres=# create global temporary table gtt1(a int);
CREATE TABLE

-- seeeion 2:
postgres=# truncate gtt1 ;
ERROR: could not open file "base/13585/t3_16384": No such file or
directory

is it expected?

Oh ,this is a bug, I fixed it.

Thanks for the patch.
I have verified the same, Now the issue is resolved with v12 patch.

Kindly confirm the below scenario:

postgres=# create global temporary table gtt1 (c1 int unique);
CREATE TABLE

postgres=# create global temporary table gtt2 (c1 int references gtt1(c1)
);
ERROR: referenced relation "gtt1" is not a global temp table

postgres=# create table tab2 (c1 int references gtt1(c1) );
ERROR: referenced relation "gtt1" is not a global temp table

Thanks,
Prabhat Sahu

GTT supports foreign key constraints
in global_temporary_table_v13-pg13.patch

Wenjing

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

#160tushar
tushar.ahuja@enterprisedb.com
In reply to: Prabhat Sahu (#158)
Re: [Proposal] Global temporary tables

Hi ,

pg_upgrade  scenario is failing if database is containing  global
temporary table

=============================
centos@tushar-ldap-docker bin]$ ./psql postgres
psql (13devel)
Type "help" for help.

postgres=# create global temporary table  t(n int);
CREATE TABLE
postgres=# \q
===============================

run pg_upgrade -

[centos@tushar-ldap-docker bin]$ ./pg_upgrade -d /tmp/t1/ -D /tmp/t2 -b
. -B .
Performing Consistency Checks
-----------------------------
Checking cluster versions    ok
Checking database user is the install user                   ok
Checking database connection settings                       ok
Checking for prepared transactions                             ok
Checking for reg* data types in user tables                 ok
--
--
If pg_upgrade fails after this point, you must re-initdb the
new cluster before continuing.

Performing Upgrade
------------------
Analyzing all rows in the new cluster                        ok
Freezing all rows in the new cluster                          ok
Deleting files from new pg_xact                                ok
--
--
Restoring database schemas in the new cluster
ok
Copying user relation files
  /tmp/t1/base/13585/16384
error while copying relation "public.t": could not open file
"/tmp/t1/base/13585/16384": No such file or directory
Failure, exiting

regards,

On 2/25/20 7:06 PM, Prabhat Sahu wrote:

Hi All,

Please check the below findings on GTT.
_-- Scenario 1:_
Under "information_schema", We are not allowed to create "temporary
table", whereas we can CREATE/DROP "Global Temporary Table", is it
expected ?

postgres=# create temporary table information_schema.temp1(c1 int);
ERROR:  cannot create temporary relation in non-temporary schema
LINE 1: create temporary table information_schema.temp1(c1 int);
                               ^

postgres=# create global temporary table information_schema.temp1(c1 int);
CREATE TABLE

postgres=# drop table information_schema.temp1 ;
DROP TABLE

_-- Scenario 2:_
Here I am getting the same error message in both the below cases.
We may add a "global" keyword with GTT related error message.

postgres=# create global temporary table gtt1 (c1 int unique);
CREATE TABLE
postgres=# create temporary table tmp1 (c1 int unique);
CREATE TABLE

postgres=# create temporary table tmp2 (c1 int references gtt1(c1) );
ERROR:  constraints on temporary tables may reference only temporary
tables

postgres=# create global temporary table gtt2 (c1 int references
tmp1(c1) );
ERROR:  constraints on temporary tables may reference only temporary
tables

Thanks,
Prabhat Sahu

On Tue, Feb 25, 2020 at 2:25 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com
<mailto:wenjing.zwj@alibaba-inc.com>> wrote:

2020年2月24日 下午5:44,Prabhat Sahu <prabhat.sahu@enterprisedb.com
<mailto:prabhat.sahu@enterprisedb.com>> 写道:

On Fri, Feb 21, 2020 at 9:10 PM 曾文旌(义从)
<wenjing.zwj@alibaba-inc.com
<mailto:wenjing.zwj@alibaba-inc.com>> wrote:

Hi,
I have started testing the "Global temporary table" feature,
That's great, I see hope.
from "gtt_v11-pg13.patch". Below is my findings:

-- session 1:
postgres=# create global temporary table gtt1(a int);
CREATE TABLE

-- seeeion 2:
postgres=# truncate gtt1 ;
ERROR:  could not open file "base/13585/t3_16384": No such
file or directory

is it expected?

Oh ,this is a bug, I fixed it.

Thanks for the patch.
I have verified the same, Now the issue is resolved with v12 patch.

Kindly confirm the below scenario:

postgres=# create global temporary table gtt1 (c1 int unique);
CREATE TABLE

postgres=# create global temporary table gtt2 (c1 int references
gtt1(c1) );
ERROR:  referenced relation "gtt1" is not a global temp table

postgres=# create table tab2 (c1 int references gtt1(c1) );
ERROR:  referenced relation "gtt1" is not a global temp table

Thanks,
Prabhat Sahu

GTT supports foreign key constraints
in global_temporary_table_v13-pg13.patch

Wenjing

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company

#161tushar
tushar.ahuja@enterprisedb.com
In reply to: Pavel Stehule (#159)
Re: [Proposal] Global temporary tables

Hi,

I have created two  global temporary tables like this -

Case 1-
postgres=# create global  temp table foo(n int) *with
(on_c*ommit_delete_rows='true');
CREATE TABLE

Case 2-
postgres=# create global  temp table bar1(n int) *on c*ommit delete rows;
CREATE TABLE

but   if i try to do the same having only 'temp' keyword , Case 2 is
working fine but getting this error  for case 1 -

postgres=# create   temp table foo1(n int) with
(on_commit_delete_rows='true');
ERROR:  regular table cannot specifie on_commit_delete_rows
postgres=#

postgres=#  create   temp table bar1(n int) on commit delete rows;
CREATE TABLE

i think this error message need to be more clear .

regards,
tushar

On 2/25/20 7:19 PM, Pavel Stehule wrote/:

út 25. 2. 2020 v 14:36 odesílatel Prabhat Sahu
<prabhat.sahu@enterprisedb.com <mailto:prabhat.sahu@enterprisedb.com>>
napsal:

Hi All,

Please check the below findings on GTT.
_-- Scenario 1:_
Under "information_schema", We are not allowed to create
"temporary table", whereas we can CREATE/DROP "Global Temporary
Table", is it expected ?

It is ok for me. temporary tables should be created only in
proprietary schema. For GTT there is not risk of collision, so it can
be created in any schema where are necessary access rights.

Pavel

postgres=# create temporary table information_schema.temp1(c1 int);
ERROR:  cannot create temporary relation in non-temporary schema
LINE 1: create temporary table information_schema.temp1(c1 int);
                               ^

postgres=# create global temporary table
information_schema.temp1(c1 int);
CREATE TABLE

postgres=# drop table information_schema.temp1 ;
DROP TABLE

_-- Scenario 2:_
Here I am getting the same error message in both the below cases.
We may add a "global" keyword with GTT related error message.

postgres=# create global temporary table gtt1 (c1 int unique);
CREATE TABLE
postgres=# create temporary table tmp1 (c1 int unique);
CREATE TABLE

postgres=# create temporary table tmp2 (c1 int references gtt1(c1) );
ERROR:  constraints on temporary tables may reference only
temporary tables

postgres=# create global temporary table gtt2 (c1 int references
tmp1(c1) );
ERROR:  constraints on temporary tables may reference only
temporary tables

Thanks,
Prabhat Sahu

On Tue, Feb 25, 2020 at 2:25 PM 曾文旌(义从)
<wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>>
wrote:

2020年2月24日 下午5:44,Prabhat Sahu
<prabhat.sahu@enterprisedb.com
<mailto:prabhat.sahu@enterprisedb.com>> 写道:

On Fri, Feb 21, 2020 at 9:10 PM 曾文旌(义从)
<wenjing.zwj@alibaba-inc.com
<mailto:wenjing.zwj@alibaba-inc.com>> wrote:

Hi,
I have started testing the "Global temporary table" feature,
That's great, I see hope.
from "gtt_v11-pg13.patch". Below is my findings:

-- session 1:
postgres=# create global temporary table gtt1(a int);
CREATE TABLE

-- seeeion 2:
postgres=# truncate gtt1 ;
ERROR:  could not open file "base/13585/t3_16384": No
such file or directory

is it expected?

Oh ,this is a bug, I fixed it.

Thanks for the patch.
I have verified the same, Now the issue is resolved with v12
patch.

Kindly confirm the below scenario:

postgres=# create global temporary table gtt1 (c1 int unique);
CREATE TABLE

postgres=# create global temporary table gtt2 (c1 int
references gtt1(c1) );
ERROR:  referenced relation "gtt1" is not a global temp table

postgres=# create table tab2 (c1 int references gtt1(c1) );
ERROR:  referenced relation "gtt1" is not a global temp table

Thanks,
Prabhat Sahu

GTT supports foreign key constraints
in global_temporary_table_v13-pg13.patch

Wenjing

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com
<http://www.enterprisedb.com/&gt;

--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company

#162曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: tushar (#160)
1 attachment(s)
Re: [Proposal] Global temporary tables

Thanks for review.

2020年2月25日 下午9:56,tushar <tushar.ahuja@enterprisedb.com> 写道:

Hi ,

pg_upgrade scenario is failing if database is containing global temporary table

=============================
centos@tushar-ldap-docker bin]$ ./psql postgres
psql (13devel)
Type "help" for help.

postgres=# create global temporary table t(n int);
CREATE TABLE
postgres=# \q
===============================

run pg_upgrade -

[centos@tushar-ldap-docker bin]$ ./pg_upgrade -d /tmp/t1/ -D /tmp/t2 -b . -B .
Performing Consistency Checks
-----------------------------
Checking cluster versions ok
Checking database user is the install user ok
Checking database connection settings ok
Checking for prepared transactions ok
Checking for reg* data types in user tables ok
--
--
If pg_upgrade fails after this point, you must re-initdb the
new cluster before continuing.

Performing Upgrade
------------------
Analyzing all rows in the new cluster ok
Freezing all rows in the new cluster ok
Deleting files from new pg_xact ok
--
--
Restoring database schemas in the new cluster
ok
Copying user relation files
/tmp/t1/base/13585/16384
error while copying relation "public.t": could not open file "/tmp/t1/base/13585/16384": No such file or directory
Failure, exiting

This is a bug.
I fixed in global_temporary_table_v14-pg13.patch

Wenjing

Show quoted text

regards,

On 2/25/20 7:06 PM, Prabhat Sahu wrote:

Hi All,

Please check the below findings on GTT.
-- Scenario 1:
Under "information_schema", We are not allowed to create "temporary table", whereas we can CREATE/DROP "Global Temporary Table", is it expected ?

postgres=# create temporary table information_schema.temp1(c1 int);
ERROR: cannot create temporary relation in non-temporary schema
LINE 1: create temporary table information_schema.temp1(c1 int);
^

postgres=# create global temporary table information_schema.temp1(c1 int);
CREATE TABLE

postgres=# drop table information_schema.temp1 ;
DROP TABLE

-- Scenario 2:
Here I am getting the same error message in both the below cases.
We may add a "global" keyword with GTT related error message.

postgres=# create global temporary table gtt1 (c1 int unique);
CREATE TABLE
postgres=# create temporary table tmp1 (c1 int unique);
CREATE TABLE

postgres=# create temporary table tmp2 (c1 int references gtt1(c1) );
ERROR: constraints on temporary tables may reference only temporary tables

postgres=# create global temporary table gtt2 (c1 int references tmp1(c1) );
ERROR: constraints on temporary tables may reference only temporary tables

Thanks,
Prabhat Sahu

On Tue, Feb 25, 2020 at 2:25 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> wrote:

2020年2月24日 下午5:44,Prabhat Sahu <prabhat.sahu@enterprisedb.com <mailto:prabhat.sahu@enterprisedb.com>> 写道:

On Fri, Feb 21, 2020 at 9:10 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> wrote:
Hi,
I have started testing the "Global temporary table" feature,
That's great, I see hope.
from "gtt_v11-pg13.patch". Below is my findings:

-- session 1:
postgres=# create global temporary table gtt1(a int);
CREATE TABLE

-- seeeion 2:
postgres=# truncate gtt1 ;
ERROR: could not open file "base/13585/t3_16384": No such file or directory

is it expected?

Oh ,this is a bug, I fixed it.
Thanks for the patch.
I have verified the same, Now the issue is resolved with v12 patch.

Kindly confirm the below scenario:

postgres=# create global temporary table gtt1 (c1 int unique);
CREATE TABLE

postgres=# create global temporary table gtt2 (c1 int references gtt1(c1) );
ERROR: referenced relation "gtt1" is not a global temp table

postgres=# create table tab2 (c1 int references gtt1(c1) );
ERROR: referenced relation "gtt1" is not a global temp table

Thanks,
Prabhat Sahu

GTT supports foreign key constraints in global_temporary_table_v13-pg13.patch

Wenjing

--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/ <https://www.enterprisedb.com/&gt;
The Enterprise PostgreSQL Company

Attachments:

global_temporary_table_v14-pg13.patchapplication/octet-stream; name=global_temporary_table_v14-pg13.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 79430d2..babb5f3 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -158,6 +158,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * For global temp table only
+	 * use ShareUpdateExclusiveLock for ensure safety
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temp table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},	
 	/* list terminator */
 	{{NULL}}
 };
@@ -1486,6 +1499,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1586,13 +1601,18 @@ build_reloptions(Datum reloptions, bool validate,
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	/*
 	 * There are no options for partitioned tables yet, but this is able to do
 	 * some validation.
 	 */
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index dd975b1..1610e7d 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1013,7 +1013,9 @@ gistGetFakeLSN(Relation rel)
 {
 	static XLogRecPtr counter = FirstNormalUnloggedLSN;
 
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	/* global temp is same as local temp table */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 4871b7f..16b00c9 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -149,7 +149,9 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	/* global temp table is same as local temp table */
+	if (!(index->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		index->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 3fa4b76..b54882d 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -598,7 +598,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -651,7 +651,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 03c43ef..2674132 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -62,6 +62,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -399,9 +400,9 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
 	Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
-	/* not every AM requires these to be valid, but heap does */
-	Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
-	Assert(MultiXactIdIsValid(onerel->rd_rel->relminmxid));
+	/* not every AM requires these to be valid, but regular heap does */
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ MultiXactIdIsValid(onerel->rd_rel->relminmxid));
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index f05cbe7..f2d3b83 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -27,6 +27,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -589,6 +590,10 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(rel->rd_node.relNode))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index d19408b..5014268 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6347,6 +6347,10 @@ StartupXLOG(void)
 	else
 		recoveryTargetTLI = ControlFile->checkPointCopy.ThisTimeLineID;
 
+	/* clean temp relation files */
+	if (max_active_gtt > 0)
+		RemovePgTempFiles();
+
 	/*
 	 * Check for signal files, and if so set up state for offline recovery
 	 */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index f8f0b48..dc8bbb1 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -42,6 +42,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 7d6acae..3be8d63 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -393,7 +393,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/* global temp table is same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index e31478b..1c382c6 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -427,7 +429,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -957,6 +959,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -995,8 +998,18 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/* global temp table not remember transaction info in catalog */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1358,6 +1371,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1938,6 +1952,13 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/* We allow to drop global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(rel->rd_node))
+			elog(ERROR, "can not drop relation when other backend attached this global temp table");
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3165,7 +3186,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3177,7 +3198,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3223,8 +3244,13 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		/* truncate global temp table only need RowExclusiveLock */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3257,6 +3283,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3265,23 +3292,37 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (!gtt_storage_attached(rel->rd_node.relNode))
+			return;
+
+		/*
+		 * Truncate global temp table only need RowExclusiveLock
+		 */
+		lockmode = RowExclusiveLock;
+	}
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(rel, 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 8880586..a23b499 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -52,6 +52,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -877,6 +878,19 @@ index_create(Relation heapRelation,
 						indexRelationName, RelationGetRelationName(heapRelation))));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temp table use concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot create indexes on global temporary tables using concurrent mode")));
+
+		/* if global temp table not init storage, then skip build index */
+		if (!gtt_storage_attached(heapRelation->rd_node.relNode))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -2054,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.
@@ -2660,6 +2681,11 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	/* update index stats into localhash and rel_rd_rel for global temp table */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		is_gtt = true;
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2745,21 +2771,35 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
+		if (is_gtt)
+			rel->rd_rel->relpages = (int32) relpages;
+		else if (rd_rel->relpages != (int32) relpages)
 		{
 			rd_rel->relpages = (int32) relpages;
 			dirty = true;
 		}
-		if (rd_rel->reltuples != (float4) reltuples)
+
+		if (is_gtt)
+			rel->rd_rel->reltuples = (float4) reltuples;
+		else if (rd_rel->reltuples != (float4) reltuples)
 		{
 			rd_rel->reltuples = (float4) reltuples;
 			dirty = true;
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+
+		if (is_gtt)
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+		else if (rd_rel->relallvisible != (int32) relallvisible)
 		{
 			rd_rel->relallvisible = (int32) relallvisible;
 			dirty = true;
 		}
+
+		if (is_gtt)
+		{
+			up_gtt_relstats(rel, relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
+		}
 	}
 
 	/*
@@ -2873,6 +2913,15 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, index, val);
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		if (!gtt_storage_attached(indexRelation->rd_node.relNode))
+		{
+			gtt_force_enable_index(indexRelation);
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3468,6 +3517,15 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 				 errmsg("cannot reindex temporary tables of other sessions")));
 
 	/*
+	 * Because global temp table cannot change relfilenode
+	 * no support reindex on global temp table yet.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(iRel))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot reindex global temporary tables")));
+
+	/*
 	 * Also check for active uses of the index in the current transaction; we
 	 * don't want to reindex underneath an open indexscan.
 	 */
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index e70243a..301da79 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -647,6 +647,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		/* global temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp relations in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index fddfbf1..671c614 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -26,6 +26,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -75,7 +76,7 @@ static PendingRelDelete *pendingDeletes = NULL; /* head of linked list */
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -85,6 +86,8 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		/* global temp table use same storage strategy as local temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -117,6 +120,10 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/* remember global temp table storage info to localhash */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel)
+		remember_gtt_storage_info(rnode, rel);
+
 	return srel;
 }
 
@@ -486,8 +493,15 @@ smgrDoPendingDeletes(bool isCommit)
 		smgrdounlinkall(srels, nrels, false);
 
 		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* clean global temp table flags when transaction commit or rollback */
+			if (SmgrIsTemp(srels[i]) && 
+				gtt_storage_attached(srels[i]->smgr_rnode.node.relNode))
+				forget_gtt_storage_info(srels[i]->smgr_rnode.node.relNode);
+		}
+
 		pfree(srels);
 	}
 }
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..e877b0c
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1260 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  code to create and destroy physical storage for global temparary table
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_relstats_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+typedef struct gtt_ctl_data
+{
+	LWLock			lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct
+{
+	RelFileNode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+	Oid			relid;
+
+	Oid			spcnode;
+	/* pg_class stat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+	char		relkind;
+	bool		on_commit_delete;
+
+	/* pg_statistic */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(RelFileNode rnode);
+static void gtt_storage_checkout(RelFileNode rnode, bool skiplock);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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;
+	int			natts = 0;
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_gtt to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temp table yet");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create stroage file", RelationGetRelationName(rel));
+
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		/* First time through: initialize the hash table */
+		HASHCTL		ctl;
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		gtt_storage_local_hash =
+			hash_create("global temp relation table",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_relstats_context =
+			AllocSetContextCreate(CacheMemoryContext,
+							"gtt relstats context",
+							ALLOCSET_DEFAULT_SIZES);
+	}
+
+	/* Look up or create an entry */
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_ENTER, &found);
+
+	if (found)
+	{
+		elog(ERROR, "backend %d relid %u already exists in global temp table local hash",
+					MyBackendId, relid);
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+	entry->spcnode = rnode.spcNode;
+	entry->relpages = 0;
+	entry->reltuples = 0;
+	entry->relallvisible = 0;
+	entry->relkind = rel->rd_rel->relkind;
+	natts = RelationGetNumberOfAttributes(rel);
+	entry->natts = natts;
+	entry->attnum = palloc0(sizeof(int) * natts);
+	entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+	/* only heap contain transaction information */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+		{
+			entry->on_commit_delete = true;
+			register_on_commit_action(rel->rd_node.relNode, ONCOMMIT_DELETE_ROWS);
+		}
+
+		entry->relfrozenxid = RecentXmin;
+		entry->relminmxid = GetOldestMultiXactId();
+		insert_gtt_relfrozenxid_to_ordered_list(entry->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+		gtt_storage_checkin(rnode);
+	}
+	else
+	{
+		entry->on_commit_delete = false;
+		entry->relfrozenxid = 0;
+		entry->relminmxid = 0;
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+void
+forget_gtt_storage_info(Oid relid)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry)
+	{
+		int		i;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			RelFileNode rnode;
+
+			rnode.spcNode = entry->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = entry->relid;
+
+			gtt_storage_checkout(rnode, false);
+
+			Assert(TransactionIdIsNormal(entry->relfrozenxid));
+			remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+
+		for (i = 0; i < entry->natts; i++)
+		{
+			if (entry->att_stat_tups[i])
+			{
+				heap_freetuple(entry->att_stat_tups[i]);
+				entry->att_stat_tups[i] = NULL;
+			}
+		}
+
+		pfree(entry->attnum);
+		pfree(entry->att_stat_tups);
+	}
+
+	hash_search(gtt_storage_local_hash,
+			(void *) &(relid), HASH_REMOVE, NULL);
+
+	return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool found;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (gtt_storage_local_hash == NULL)
+		return false;
+
+	hash_search(gtt_storage_local_hash,
+			(void *) &(relid), HASH_FIND, &found);
+
+	return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS				status;
+	gtt_local_hash_entry		*entry;
+	int			nrels = 0,
+				maxrels = 0;
+	SMgrRelation	*srels = NULL;
+	RelFileNode		*rnodes = NULL;
+	char			*relkinds = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		SMgrRelation srel;
+		RelFileNode rnode;
+
+		rnode.spcNode = entry->spcnode;
+		rnode.dbNode = MyDatabaseId;
+		rnode.relNode = entry->relid;
+
+		srel = smgropen(rnode, MyBackendId);
+
+		/* allocate the initial array, or extend it, if needed */
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			srels = palloc(sizeof(SMgrRelation) * maxrels);
+			rnodes = palloc(sizeof(RelFileNode) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+			rnodes = repalloc(rnodes, sizeof(RelFileNode) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		srels[nrels] = srel;
+		rnodes[nrels] = rnode;
+		relkinds[nrels] = entry->relkind;
+		nrels++;
+	}
+
+	if (nrels > 0)
+	{
+		int i;
+
+		smgrdounlinkall(srels, nrels, false);
+
+		LWLockAcquire(&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.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (gtt_storage_local_hash == NULL)
+		return false;
+
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_FIND, &found);
+
+	if (!found)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	if (relpages)
+		*relpages = entry->relpages;
+
+	if (reltuples)
+		*reltuples = entry->reltuples;
+
+	if (relallvisible)
+		*relallvisible = entry->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = entry->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = entry->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	MemoryContext			oldcontext;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+						(void *) &reloid, HASH_FIND, &found);
+
+	if (!found)
+		return;
+
+	/* todo */
+	if (entry->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	Assert(entry->relid == reloid);
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == 0)
+		{
+			entry->attnum[i] = attnum;
+			break;
+		}
+		else if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < entry->natts);
+	Assert(entry->att_stat_tups[i] == NULL);
+	entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+						(void *) &reloid, HASH_FIND, &found);
+
+	if (!found)
+		return NULL;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			return entry->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int			i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	HeapTuple	tuple;
+	Relation	rel = NULL;
+	int		attnum = PG_GETARG_INT32(1);
+	Oid		reloid = PG_GETARG_OID(0);
+	char		rel_persistence;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	  tupdesc;
+	MemoryContext oldcontext;
+	Tuplestorestate *tupstore;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = table_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+
+	table_close(rel, NoLock);
+	table_close(pg_tatistic, AccessShareLock);
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate	*tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Relation	rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	if (get_gtt_relstats(reloid,
+						&relpages, &reltuples, &relallvisible,
+						&relfrozenxid, &relminmxid))
+	{
+		Datum	values[5];
+		bool	isnull[5];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = Int32GetDatum(relpages);
+		values[1] = Float4GetDatum((float4)reltuples);
+		values[2] = Int32GetDatum(relallvisible);
+		values[3] = UInt32GetDatum(relfrozenxid);
+		values[4] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+
+	tuplestore_donestoring(tupstore);
+	table_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(rel->rd_node);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	table_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(indexOid));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid relOid = index->rd_index->indrelid;
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	if (gtt_storage_attached(relOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int			i;
+	Oid			toastrelid;
+	List		*seqlist = NIL;
+	ListCell	*seqcell = NULL;
+
+	if (operation != CMD_INSERT)
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_attached(relation->rd_node.relNode))
+		return;
+
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+
+		index_build(relation, index, info, true, false);
+	}
+
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid 		indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo 	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	seqlist = getOwnedSequences(RelationGetRelid(relation));
+	foreach(seqcell, seqlist)
+	{
+		Oid 		seq_relid = lfirst_oid(seqcell);
+		Relation	seq_rel;
+
+		seq_rel = relation_open(seq_relid, RowExclusiveLock);
+	
+		/* This check must match AlterSequence! */
+		if (!pg_class_ownercheck(seq_relid, GetUserId()))
+			aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SEQUENCE,
+						   RelationGetRelationName(seq_rel));
+
+		RelationCreateStorage(seq_rel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seq_rel);
+		relation_close(seq_rel, NoLock);
+	}
+
+	return;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index f681aaf..e1fa3ec 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM 
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c4420dd..977a984 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -586,14 +587,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 		 * pg_statistic for columns we didn't process, we leave them alone.)
 		 */
 		update_attstats(RelationGetRelid(onerel), inh,
-						attr_cnt, vacattrstats);
+						attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
 
 		for (ind = 0; ind < nindexes; ind++)
 		{
 			AnlIndexData *thisdata = &indexdata[ind];
 
 			update_attstats(RelationGetRelid(Irel[ind]), false,
-							thisdata->attr_cnt, thisdata->vacattrstats);
+							thisdata->attr_cnt, thisdata->vacattrstats,
+							RelationGetRelPersistence(Irel[ind]));
 		}
 
 		/*
@@ -1456,7 +1458,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
  *		by taking a self-exclusive lock on the relation in analyze_rel().
  */
 static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
 {
 	Relation	sd;
 	int			attno;
@@ -1558,31 +1560,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/* Update column statistic to localhash, not catalog */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 11ce1bb..552fd87 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -391,6 +391,12 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/* not support cluster global temp table yet */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("not support cluster global temporary tables yet")));
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index e79ede4..078694d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -27,6 +27,7 @@
 #include "catalog/dependency.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -1062,7 +1063,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, stmt->filename, stmt->is_program,
@@ -2840,6 +2841,8 @@ CopyFrom(CopyState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	estate->es_result_relations = resultRelInfo;
 	estate->es_num_result_relations = 1;
 	estate->es_result_relation_info = resultRelInfo;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index ec20ba3..a29cc00 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2614,6 +2614,16 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 			!isTempNamespace(classtuple->relnamespace))
 			continue;
 
+		/* not support reindex on global temp table, so skip it */
+		if (classtuple->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			ereport(WARNING,
+				(errmsg("global temp table \"%s.%s\" skip reindexed",
+					get_namespace_name(get_rel_namespace(relid)),
+					get_rel_name(relid))));
+			continue;
+		}
+
 		/* Check user/system classification, and optionally skip */
 		if (objectKind == REINDEX_OBJECT_SYSTEM &&
 			!IsSystemClass(relid, classtuple))
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index d8cafc4..deecde8 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -96,7 +96,8 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 	 * transaction.
 	 */
 	relpersistence = get_rel_persistence(relid);
-	if (relpersistence == RELPERSISTENCE_TEMP)
+	if (relpersistence == RELPERSISTENCE_TEMP ||
+		relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
 
 	/* Check permissions. */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..c55dcf7 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -94,7 +94,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +108,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -222,7 +223,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* now initialize the sequence's data */
 	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -327,7 +328,7 @@ ResetSequence(Oid seq_relid)
 	/*
 	 * Insert the modified tuple into the new storage file.
 	 */
-	fill_seq_with_data(seq_rel, tuple);
+	fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -340,17 +341,21 @@ ResetSequence(Oid seq_relid)
  * Initialize a sequence's relation with the specified tuple as content
  */
 static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 	sequence_magic *sm;
 	OffsetNumber offnum;
+	bool lockBuffer = false;
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ReadBuffer(rel, P_NEW);
-	Assert(BufferGetBlockNumber(buf) == 0);
+	if (buf == InvalidBuffer)
+	{
+		buf = ReadBuffer(rel, P_NEW);
+		Assert(BufferGetBlockNumber(buf) == 0);
+		lockBuffer = true;
+	}
 
 	page = BufferGetPage(buf);
 
@@ -360,7 +365,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* Now insert sequence tuple */
 
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+	if (lockBuffer)
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +416,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	END_CRIT_SECTION();
 
-	UnlockReleaseBuffer(buf);
+	if (lockBuffer)
+		UnlockReleaseBuffer(buf);
 }
 
 /*
@@ -502,7 +509,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * Insert the modified tuple into the new storage file.
 		 */
-		fill_seq_with_data(seqrel, newdatatuple);
+		fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
 	}
 
 	/* process OWNED BY if given */
@@ -611,7 +618,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +943,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1178,6 +1185,25 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
 	LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
 
 	page = BufferGetPage(*buf);
+	if (GlobalTempRelationPageIsNotInitialized(rel, page))
+	{
+		/* Initialize sequence for global temporary tables */
+		Datum		value[SEQ_COL_LASTCOL] = {0};
+		bool		null[SEQ_COL_LASTCOL] = {false};
+		HeapTuple	tuple;
+		int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+		/*
+		 * last_value from pg_sequence.seqstart
+		 * log_cnt = 0
+		 * is_called = false
+		 */
+		value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+		tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+		fill_seq_with_data(rel, tuple, *buf);
+		heap_freetuple(tuple);
+	}
 	sm = (sequence_magic *) PageGetSpecialPointer(page);
 
 	if (sm->magic != SEQ_MAGIC)
@@ -1954,3 +1980,23 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index b7c8d66..f710fb8 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -553,6 +554,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 								  Relation partitionTbl);
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static bool has_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -598,6 +600,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
@@ -608,8 +611,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Check consistency of arguments
 	 */
+	/* global temp table same as local temp table */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !(stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+			 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -639,7 +644,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	/* global temp table same as local temp table */
+	if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+		 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -740,6 +747,53 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* global temp table */
+	has_oncommit_clause = has_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* check parent table*/
+		if (inheritOids && stmt->oncommit == ONCOMMIT_NOOP && !has_oncommit_clause)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			table_close(relation, NoLock);
+		}
+
+		if (has_oncommit_clause)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "can not defeine global temp table with on commit and with clause at same time");
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temp table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (has_oncommit_clause)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1824,7 +1878,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);
@@ -3392,6 +3447,13 @@ RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal, bo
 	 * specially.
 	 */
 	targetrelation = relation_open(myrelid, is_index ? ShareUpdateExclusiveLock : AccessExclusiveLock);
+
+	if (RELATION_IS_GLOBAL_TEMP(targetrelation))
+	{
+		if (is_other_backend_use_gtt(targetrelation->rd_node))
+			elog(ERROR, "can not rename relation when other backend attached this global temp table");
+	}
+
 	namespaceId = RelationGetNamespace(targetrelation);
 
 	/*
@@ -3567,6 +3629,13 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(rel->rd_node))
+			elog(ERROR, "can not alter relation when other backend attached this global temp table");
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -8179,6 +8248,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on temporary tables may reference only temporary tables")));
+			break;
 	}
 
 	/*
@@ -13221,7 +13296,7 @@ index_copy_data(Relation rel, RelFileNode newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
@@ -14628,7 +14703,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",
@@ -17269,3 +17346,20 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
 		table_close(rel, NoLock);
 	}
 }
+
+static bool
+has_oncommit_option(List *options)
+{
+	ListCell   *listptr;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (pg_strcasecmp(def->defname, "on_commit_delete_rows") == 0)
+			return true;
+	}
+
+	return false;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index d625d17..69ad24f 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/vacuum.h"
@@ -1217,6 +1218,17 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	 /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		is_gtt = true;
+		up_gtt_relstats(relation,
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1230,17 +1242,26 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relpages = (int32) num_pages;
+	else if (pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (is_gtt)
+		relation->rd_rel->reltuples = (float4) num_tuples;
+	else if (pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	else if (pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1285,7 +1306,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1296,7 +1318,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1394,6 +1417,10 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/* global temp table relstats not in pg_class */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1451,6 +1478,25 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/*
+	 * Global temp table get frozenxid from MyProc
+	 * to avoid the vacuum truncate clog that gtt need.
+	 */
+	if (max_active_gtt > 0)
+	{
+		TransactionId oldest_gtt_frozenxid =
+			list_all_session_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid) &&
+			TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+		{
+			ereport(WARNING,
+				(errmsg("global temp table oldest FrozenXid is far in the past"),
+				 errhint("please truncate them or kill those sessions that use them.")));
+			newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index ee5c3a6..0d7a2d4 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandTag((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index c13b1d3..253054d 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
@@ -543,6 +544,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	init_gtt_storage(mtstate->operation, leaf_part_rri);
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d71c0a4..0900907 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,6 +42,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -2372,6 +2373,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			ExecOpenIndices(resultRelInfo,
 							node->onConflictAction != ONCONFLICT_NONE);
 
+		init_gtt_storage(operation, resultRelInfo);
+
 		/*
 		 * If this is an UPDATE and a BEFORE UPDATE trigger is present, the
 		 * trigger itself might modify the partition-key values. So arrange
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 905bbe7..170963d 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -591,6 +591,8 @@ static void
 set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 						  RangeTblEntry *rte)
 {
+	char relpersistence;
+
 	/*
 	 * The flag has previously been initialized to false, so we can just
 	 * return if it becomes clear that we can't safely set it.
@@ -618,7 +620,11 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			/* global temp table is same as local temp table */
+			relpersistence = get_rel_persistence(rte->relid);
+
+			if (relpersistence == RELPERSISTENCE_TEMP ||
+				relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b44efd6..14dbaaf 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6307,7 +6307,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
+	/* global temp table is same as local temp table */
 	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index d82fc5a..93c6d97 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -28,6 +28,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temp table */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(indexRelation->rd_node.relNode))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6676412..0685c1c 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2585,6 +2585,11 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("materialized views must not use temporary tables or views")));
 
+		if (is_query_using_gtt(query))
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("materialized views must not use global temporary tables or views")));
+
 		/*
 		 * A materialized view would either need to save parameters for use in
 		 * maintaining/loading the data or prohibit them entirely.  The latter
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 96e7fdb..30f585c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3290,17 +3290,11 @@ OptTemp:	TEMPORARY					{ $$ = RELPERSISTENCE_TEMP; }
 			| LOCAL TEMP				{ $$ = RELPERSISTENCE_TEMP; }
 			| GLOBAL TEMPORARY
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
-					$$ = RELPERSISTENCE_TEMP;
+					$$ = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
-					$$ = RELPERSISTENCE_TEMP;
+					$$ = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED					{ $$ = RELPERSISTENCE_UNLOGGED; }
 			| /*EMPTY*/					{ $$ = RELPERSISTENCE_PERMANENT; }
@@ -11594,19 +11588,13 @@ OptTempTableName:
 				}
 			| GLOBAL TEMPORARY opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED opt_table qualified_name
 				{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b875a50..123945b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index ee2d2b5..9c9abaa 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 6d1f28c..ed837d1 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2086,6 +2086,11 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* autovacuum skip vacuum global temp table */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2152,7 +2157,9 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		/* autovacuum skip vacuum global temp table */
+		if (classForm->relpersistence == RELPERSISTENCE_TEMP || 
+			classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 5880054..98f7b43 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -52,6 +53,7 @@
 #include "utils/rel.h"
 #include "utils/resowner_private.h"
 #include "utils/timestamp.h"
+#include "utils/guc.h"
 
 
 /* Note: these two macros only work on shared buffers, not local ones! */
@@ -2809,6 +2811,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(relation->rd_node.relNode))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d5..7adb969 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -21,6 +21,7 @@
 #include "access/nbtree.h"
 #include "access/subtrans.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, active_gtt_shared_hash_size());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* global temporary table */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 4a5b26c..9754168 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -62,6 +62,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -4087,3 +4088,77 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temp table.
+ */
+int
+list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct *arrayP = procArray;
+	TransactionId result = InvalidTransactionId;
+	int			index;
+	int			flags = 0;
+	int			i = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+		*n = 0;
+	}
+
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		volatile PGXACT *pgxact = &allPgXact[pgprocno];
+
+		if (pgxact->vacuumFlags & flags)
+			continue;
+
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->session_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->session_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->session_gtt_frozenxid, result))
+				result = proc->session_gtt_frozenxid;
+
+			if (max_size > 0)
+			{
+				pids[i] = proc->pid;
+				xids[i] = proc->session_gtt_frozenxid;
+				i++;
+			}
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (max_size > 0)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index eb321f7..3893cef 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 8406644..0d99321 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -1008,6 +1008,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 0be26fe..c169c99 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -112,6 +112,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -4570,12 +4571,25 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -4700,15 +4714,27 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6090,6 +6116,7 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	{
 		/* Simple variable --- look to stats for the underlying table */
 		RangeTblEntry *rte = planner_rt_fetch(index->rel->relid, root);
+		char	rel_persistence = get_rel_persistence(rte->relid);
 
 		Assert(rte->rtekind == RTE_RELATION);
 		relid = rte->relid;
@@ -6107,6 +6134,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6118,6 +6152,8 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	}
 	else
 	{
+		char	rel_persistence = get_rel_persistence(index->indexoid);
+
 		/* Expression --- maybe there are stats for the index itself */
 		relid = index->indexoid;
 		colnum = 1;
@@ -6133,6 +6169,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7046,6 +7089,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		/* attempt to lookup stats in relation for this index column */
 		if (attnum != 0)
 		{
+			char	rel_persistence = get_rel_persistence(rte->relid);
+
 			/* Simple variable -- look to stats for the underlying table */
 			if (get_relation_stats_hook &&
 				(*get_relation_stats_hook) (root, rte, attnum, &vardata))
@@ -7058,6 +7103,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7070,6 +7123,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		}
 		else
 		{
+			char	rel_persistence = get_rel_persistence(index->indexoid);
+
 			/*
 			 * Looks like we've found an expression column in the index. Let's
 			 * see if there's any stats for it.
@@ -7089,6 +7144,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index fb0599f..13f4d8a 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -2878,6 +2879,18 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index ff70326..1cf7063 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -63,6 +63,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1127,6 +1128,24 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1181,6 +1200,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			gtt_fix_index_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -2220,6 +2240,8 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		gtt_fix_index_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3316,6 +3338,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3430,6 +3456,9 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
 
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+		elog(ERROR, "global temp table does not allow setting new relfilenode");
+
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
@@ -3470,7 +3499,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 				/* handle these directly, at least for now */
 				SMgrRelation srel;
 
-				srel = RelationCreateStorage(newrnode, persistence);
+				srel = RelationCreateStorage(newrnode, persistence, relation);
 				smgrclose(srel);
 			}
 			break;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 464f264..0173156 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -140,6 +140,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temp table feature.
+ * global temp table define can still storage in catalog
+ * just can not use.
+ * num > 0 means database can management num active global temp table.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2047,6 +2059,15 @@ static struct config_bool ConfigureNamesBool[] =
 static struct config_int ConfigureNamesInt[] =
 {
 	{
+		{"max_active_global_temporary_table", PGC_POSTMASTER, UNGROUPED,
+			gettext_noop("max active global temporary table."),
+			NULL
+		},
+		&max_active_gtt,
+		DEFAULT_NUM_ACTIVE_GTT, MIN_NUM_ACTIVE_GTT, MAX_NUM_ACTIVE_GTT,
+		NULL, NULL, NULL
+	},
+	{
 		{"archive_timeout", PGC_SIGHUP, WAL_ARCHIVING,
 			gettext_noop("Forces a switch to the next WAL file if a "
 						 "new file has not been started within N seconds."),
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index d92a662..07c99c5 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15598,6 +15598,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	{
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
+		char		*table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15649,9 +15650,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 			binary_upgrade_set_pg_class_oids(fout, q,
 											 tbinfo->dobj.catId.oid, false);
 
+		if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED)
+			table_type = "UNLOGGED ";
+		else if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			table_type = "GLOBAL TEMPORARY ";
+		else
+			table_type = "";
+
 		appendPQExpBuffer(q, "CREATE %s%s %s",
-						  tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ?
-						  "UNLOGGED " : "",
+						  table_type,
 						  reltypename,
 						  qualrelname);
 
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 7e524ea..0b2d8e7 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -448,6 +448,8 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 			 "         ON c.relnamespace = n.oid "
 			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
 			 CppAsString2(RELKIND_MATVIEW) ") AND "
+	/* exclude global temp tables */
+			 "    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
 	/* exclude possible orphaned temp tables */
 			 "    ((n.nspname !~ '^pg_temp_' AND "
 			 "      n.nspname !~ '^pg_toast_temp_' AND "
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index f3c7eb9..28134e2 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3723,7 +3723,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index dc03fbd..8bd6d09 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1016,6 +1016,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2378,6 +2380,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2586,6 +2591,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 78b33b2..4be89f6 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -168,6 +168,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index eb3c1a8..5206815 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5504,6 +5504,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4191',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4192',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{relid,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4193',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4194',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 048003c..af48cdf 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -19,7 +19,7 @@
 #include "storage/smgr.h"
 #include "utils/relcache.h"
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationTruncate(Relation rel, BlockNumber nblocks);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..3afd71d
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,43 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid);
+extern bool is_other_backend_use_gtt(RelFileNode node);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(RelFileNode node);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Relation relation,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void gtt_force_enable_index(Relation index);
+extern void gtt_fix_index_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 3f88683..f870e9a 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 8fda8e4..172eae9 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_TBM,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_SXACT,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index d217801..8adde87 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -117,6 +117,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId session_gtt_frozenxid;	/* session level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ce93ace..0f7262e 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -281,6 +281,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 44ed04d..6b6d2da 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	struct SMgrRelationData *rd_smgr;	/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -277,6 +277,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -535,11 +536,13 @@ typedef struct ViewOptions
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -547,6 +550,7 @@ typedef struct ViewOptions
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -559,6 +563,14 @@ typedef struct ViewOptions
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is either local temp relation of this session
+ * 		or global temp relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -602,6 +614,17 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..50ca9ac
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,7 @@
+reset search_path;
+drop schema gtt cascade;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..5e2daa9
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,297 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+ERROR:  cannot create indexes on global temporary tables using concurrent mode
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+ERROR:  not support cluster global temporary tables yet
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ok
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  can not defeine global temp table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test;
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR:  insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count 
+-------
+     1
+(1 row)
+
+select count(*) from orders;
+ count 
+-------
+     0
+(1 row)
+
+-- ERROR
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 1)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 300)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 18 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to table gtt_function.gt1
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..30d8a7b
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,84 @@
+set search_path=gtt,sys;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..850ef3e
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,161 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          475136 |                 974848
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |           409600 |        409600 |                 409600
+(2 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..9fe5fd4
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,9 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..0e21b30
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,76 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+(1 row)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+(1 row)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 634f825..6e1297a 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1349,6 +1349,93 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d2b17dd..80e577f 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..f3cf710
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,6 @@
+
+
+reset search_path;
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..0274d99
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,199 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ok
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test;
+
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ERROR
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d7d81de
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,42 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..5203c2b
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,76 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..2f4d883
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,16 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..f041892
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,42 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
#163曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: tushar (#161)
Re: [Proposal] Global temporary tables

2020年2月25日 下午11:31,tushar <tushar.ahuja@enterprisedb.com> 写道:

Hi,

I have created two global temporary tables like this -

Case 1-
postgres=# create global temp table foo(n int) with (on_commit_delete_rows='true');
CREATE TABLE

Case 2-
postgres=# create global temp table bar1(n int) on commit delete rows;
CREATE TABLE

but if i try to do the same having only 'temp' keyword , Case 2 is working fine but getting this error for case 1 -

postgres=# create temp table foo1(n int) with (on_commit_delete_rows='true');
ERROR: regular table cannot specifie on_commit_delete_rows
postgres=#

postgres=# create temp table bar1(n int) on commit delete rows;
CREATE TABLE

i think this error message need to be more clear .

Also fixed in global_temporary_table_v14-pg13.patch

Wenjing

Show quoted text

regards,
tushar

On 2/25/20 7:19 PM, Pavel Stehule wrote/:

út 25. 2. 2020 v 14:36 odesílatel Prabhat Sahu <prabhat.sahu@enterprisedb.com <mailto:prabhat.sahu@enterprisedb.com>> napsal:
Hi All,

Please check the below findings on GTT.
-- Scenario 1:
Under "information_schema", We are not allowed to create "temporary table", whereas we can CREATE/DROP "Global Temporary Table", is it expected ?

It is ok for me. temporary tables should be created only in proprietary schema. For GTT there is not risk of collision, so it can be created in any schema where are necessary access rights.

Pavel

postgres=# create temporary table information_schema.temp1(c1 int);
ERROR: cannot create temporary relation in non-temporary schema
LINE 1: create temporary table information_schema.temp1(c1 int);
^

postgres=# create global temporary table information_schema.temp1(c1 int);
CREATE TABLE

postgres=# drop table information_schema.temp1 ;
DROP TABLE

-- Scenario 2:
Here I am getting the same error message in both the below cases.
We may add a "global" keyword with GTT related error message.

postgres=# create global temporary table gtt1 (c1 int unique);
CREATE TABLE
postgres=# create temporary table tmp1 (c1 int unique);
CREATE TABLE

postgres=# create temporary table tmp2 (c1 int references gtt1(c1) );
ERROR: constraints on temporary tables may reference only temporary tables

postgres=# create global temporary table gtt2 (c1 int references tmp1(c1) );
ERROR: constraints on temporary tables may reference only temporary tables

Thanks,
Prabhat Sahu

On Tue, Feb 25, 2020 at 2:25 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> wrote:

2020年2月24日 下午5:44,Prabhat Sahu <prabhat.sahu@enterprisedb.com <mailto:prabhat.sahu@enterprisedb.com>> 写道:

On Fri, Feb 21, 2020 at 9:10 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> wrote:
Hi,
I have started testing the "Global temporary table" feature,
That's great, I see hope.
from "gtt_v11-pg13.patch". Below is my findings:

-- session 1:
postgres=# create global temporary table gtt1(a int);
CREATE TABLE

-- seeeion 2:
postgres=# truncate gtt1 ;
ERROR: could not open file "base/13585/t3_16384": No such file or directory

is it expected?

Oh ,this is a bug, I fixed it.
Thanks for the patch.
I have verified the same, Now the issue is resolved with v12 patch.

Kindly confirm the below scenario:

postgres=# create global temporary table gtt1 (c1 int unique);
CREATE TABLE

postgres=# create global temporary table gtt2 (c1 int references gtt1(c1) );
ERROR: referenced relation "gtt1" is not a global temp table

postgres=# create table tab2 (c1 int references gtt1(c1) );
ERROR: referenced relation "gtt1" is not a global temp table

Thanks,
Prabhat Sahu

GTT supports foreign key constraints in global_temporary_table_v13-pg13.patch

Wenjing

--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/ <https://www.enterprisedb.com/&gt;
The Enterprise PostgreSQL Company

#164曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: tushar (#160)
1 attachment(s)
Re: [Proposal] Global temporary tables

2020年2月25日 下午9:56,tushar <tushar.ahuja@enterprisedb.com> 写道:

Hi ,

pg_upgrade scenario is failing if database is containing global temporary table

=============================
centos@tushar-ldap-docker bin]$ ./psql postgres
psql (13devel)
Type "help" for help.

postgres=# create global temporary table t(n int);
CREATE TABLE
postgres=# \q
===============================

run pg_upgrade -

[centos@tushar-ldap-docker bin]$ ./pg_upgrade -d /tmp/t1/ -D /tmp/t2 -b . -B .
Performing Consistency Checks
-----------------------------
Checking cluster versions ok
Checking database user is the install user ok
Checking database connection settings ok
Checking for prepared transactions ok
Checking for reg* data types in user tables ok
--
--
If pg_upgrade fails after this point, you must re-initdb the
new cluster before continuing.

Performing Upgrade
------------------
Analyzing all rows in the new cluster ok
Freezing all rows in the new cluster ok
Deleting files from new pg_xact ok
--
--
Restoring database schemas in the new cluster
ok
Copying user relation files
/tmp/t1/base/13585/16384
error while copying relation "public.t": could not open file "/tmp/t1/base/13585/16384": No such file or directory
Failure, exiting

I fixed some bug in global_temporary_table_v14-pg13.patch

Please check global_temporary_table_v15-pg13.patch

Wenjing

Show quoted text

regards,

On 2/25/20 7:06 PM, Prabhat Sahu wrote:

Hi All,

Please check the below findings on GTT.
-- Scenario 1:
Under "information_schema", We are not allowed to create "temporary table", whereas we can CREATE/DROP "Global Temporary Table", is it expected ?

postgres=# create temporary table information_schema.temp1(c1 int);
ERROR: cannot create temporary relation in non-temporary schema
LINE 1: create temporary table information_schema.temp1(c1 int);
^

postgres=# create global temporary table information_schema.temp1(c1 int);
CREATE TABLE

postgres=# drop table information_schema.temp1 ;
DROP TABLE

-- Scenario 2:
Here I am getting the same error message in both the below cases.
We may add a "global" keyword with GTT related error message.

postgres=# create global temporary table gtt1 (c1 int unique);
CREATE TABLE
postgres=# create temporary table tmp1 (c1 int unique);
CREATE TABLE

postgres=# create temporary table tmp2 (c1 int references gtt1(c1) );
ERROR: constraints on temporary tables may reference only temporary tables

postgres=# create global temporary table gtt2 (c1 int references tmp1(c1) );
ERROR: constraints on temporary tables may reference only temporary tables

Thanks,
Prabhat Sahu

On Tue, Feb 25, 2020 at 2:25 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> wrote:

2020年2月24日 下午5:44,Prabhat Sahu <prabhat.sahu@enterprisedb.com <mailto:prabhat.sahu@enterprisedb.com>> 写道:

On Fri, Feb 21, 2020 at 9:10 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> wrote:
Hi,
I have started testing the "Global temporary table" feature,
That's great, I see hope.
from "gtt_v11-pg13.patch". Below is my findings:

-- session 1:
postgres=# create global temporary table gtt1(a int);
CREATE TABLE

-- seeeion 2:
postgres=# truncate gtt1 ;
ERROR: could not open file "base/13585/t3_16384": No such file or directory

is it expected?

Oh ,this is a bug, I fixed it.
Thanks for the patch.
I have verified the same, Now the issue is resolved with v12 patch.

Kindly confirm the below scenario:

postgres=# create global temporary table gtt1 (c1 int unique);
CREATE TABLE

postgres=# create global temporary table gtt2 (c1 int references gtt1(c1) );
ERROR: referenced relation "gtt1" is not a global temp table

postgres=# create table tab2 (c1 int references gtt1(c1) );
ERROR: referenced relation "gtt1" is not a global temp table

Thanks,
Prabhat Sahu

GTT supports foreign key constraints in global_temporary_table_v13-pg13.patch

Wenjing

--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/ <https://www.enterprisedb.com/&gt;
The Enterprise PostgreSQL Company

Attachments:

global_temporary_table_v15-pg13.patchapplication/octet-stream; name=global_temporary_table_v15-pg13.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 5325dd3..c76a685 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -168,6 +168,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * For global temp table only
+	 * use ShareUpdateExclusiveLock for ensure safety
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temp table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},	
 	/* list terminator */
 	{{NULL}}
 };
@@ -1496,6 +1509,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1596,13 +1611,18 @@ build_reloptions(Datum reloptions, bool validate,
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	/*
 	 * There are no options for partitioned tables yet, but this is able to do
 	 * some validation.
 	 */
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index dd975b1..1610e7d 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1013,7 +1013,9 @@ gistGetFakeLSN(Relation rel)
 {
 	static XLogRecPtr counter = FirstNormalUnloggedLSN;
 
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	/* global temp is same as local temp table */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 4871b7f..16b00c9 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -149,7 +149,9 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	/* global temp table is same as local temp table */
+	if (!(index->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		index->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 3fa4b76..b54882d 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -598,7 +598,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -651,7 +651,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 03c43ef..2674132 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -62,6 +62,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -399,9 +400,9 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
 	Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
-	/* not every AM requires these to be valid, but heap does */
-	Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
-	Assert(MultiXactIdIsValid(onerel->rd_rel->relminmxid));
+	/* not every AM requires these to be valid, but regular heap does */
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ MultiXactIdIsValid(onerel->rd_rel->relminmxid));
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 39b8f17..38b46d0 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -601,6 +602,10 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(rel->rd_node.relNode))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index d19408b..5014268 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6347,6 +6347,10 @@ StartupXLOG(void)
 	else
 		recoveryTargetTLI = ControlFile->checkPointCopy.ThisTimeLineID;
 
+	/* clean temp relation files */
+	if (max_active_gtt > 0)
+		RemovePgTempFiles();
+
 	/*
 	 * Check for signal files, and if so set up state for offline recovery
 	 */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index f8f0b48..dc8bbb1 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -42,6 +42,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 7d6acae..3be8d63 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -393,7 +393,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/* global temp table is same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index e31478b..1c382c6 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -427,7 +429,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -957,6 +959,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -995,8 +998,18 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/* global temp table not remember transaction info in catalog */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1358,6 +1371,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1938,6 +1952,13 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/* We allow to drop global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(rel->rd_node))
+			elog(ERROR, "can not drop relation when other backend attached this global temp table");
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3165,7 +3186,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3177,7 +3198,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3223,8 +3244,13 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		/* truncate global temp table only need RowExclusiveLock */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3257,6 +3283,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3265,23 +3292,37 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (!gtt_storage_attached(rel->rd_node.relNode))
+			return;
+
+		/*
+		 * Truncate global temp table only need RowExclusiveLock
+		 */
+		lockmode = RowExclusiveLock;
+	}
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(rel, 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 8880586..a23b499 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -52,6 +52,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -877,6 +878,19 @@ index_create(Relation heapRelation,
 						indexRelationName, RelationGetRelationName(heapRelation))));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temp table use concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot create indexes on global temporary tables using concurrent mode")));
+
+		/* if global temp table not init storage, then skip build index */
+		if (!gtt_storage_attached(heapRelation->rd_node.relNode))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -2054,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.
@@ -2660,6 +2681,11 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	/* update index stats into localhash and rel_rd_rel for global temp table */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		is_gtt = true;
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2745,21 +2771,35 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
+		if (is_gtt)
+			rel->rd_rel->relpages = (int32) relpages;
+		else if (rd_rel->relpages != (int32) relpages)
 		{
 			rd_rel->relpages = (int32) relpages;
 			dirty = true;
 		}
-		if (rd_rel->reltuples != (float4) reltuples)
+
+		if (is_gtt)
+			rel->rd_rel->reltuples = (float4) reltuples;
+		else if (rd_rel->reltuples != (float4) reltuples)
 		{
 			rd_rel->reltuples = (float4) reltuples;
 			dirty = true;
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+
+		if (is_gtt)
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+		else if (rd_rel->relallvisible != (int32) relallvisible)
 		{
 			rd_rel->relallvisible = (int32) relallvisible;
 			dirty = true;
 		}
+
+		if (is_gtt)
+		{
+			up_gtt_relstats(rel, relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
+		}
 	}
 
 	/*
@@ -2873,6 +2913,15 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, index, val);
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		if (!gtt_storage_attached(indexRelation->rd_node.relNode))
+		{
+			gtt_force_enable_index(indexRelation);
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3468,6 +3517,15 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 				 errmsg("cannot reindex temporary tables of other sessions")));
 
 	/*
+	 * Because global temp table cannot change relfilenode
+	 * no support reindex on global temp table yet.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(iRel))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot reindex global temporary tables")));
+
+	/*
 	 * Also check for active uses of the index in the current transaction; we
 	 * don't want to reindex underneath an open indexscan.
 	 */
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index e70243a..301da79 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -647,6 +647,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		/* global temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp relations in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index fddfbf1..671c614 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -26,6 +26,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -75,7 +76,7 @@ static PendingRelDelete *pendingDeletes = NULL; /* head of linked list */
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -85,6 +86,8 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		/* global temp table use same storage strategy as local temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -117,6 +120,10 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/* remember global temp table storage info to localhash */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel)
+		remember_gtt_storage_info(rnode, rel);
+
 	return srel;
 }
 
@@ -486,8 +493,15 @@ smgrDoPendingDeletes(bool isCommit)
 		smgrdounlinkall(srels, nrels, false);
 
 		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* clean global temp table flags when transaction commit or rollback */
+			if (SmgrIsTemp(srels[i]) && 
+				gtt_storage_attached(srels[i]->smgr_rnode.node.relNode))
+				forget_gtt_storage_info(srels[i]->smgr_rnode.node.relNode);
+		}
+
 		pfree(srels);
 	}
 }
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..e877b0c
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1260 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  code to create and destroy physical storage for global temparary table
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_relstats_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+typedef struct gtt_ctl_data
+{
+	LWLock			lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct
+{
+	RelFileNode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+	Oid			relid;
+
+	Oid			spcnode;
+	/* pg_class stat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+	char		relkind;
+	bool		on_commit_delete;
+
+	/* pg_statistic */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(RelFileNode rnode);
+static void gtt_storage_checkout(RelFileNode rnode, bool skiplock);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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;
+	int			natts = 0;
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_gtt to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temp table yet");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create stroage file", RelationGetRelationName(rel));
+
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		/* First time through: initialize the hash table */
+		HASHCTL		ctl;
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		gtt_storage_local_hash =
+			hash_create("global temp relation table",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_relstats_context =
+			AllocSetContextCreate(CacheMemoryContext,
+							"gtt relstats context",
+							ALLOCSET_DEFAULT_SIZES);
+	}
+
+	/* Look up or create an entry */
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_ENTER, &found);
+
+	if (found)
+	{
+		elog(ERROR, "backend %d relid %u already exists in global temp table local hash",
+					MyBackendId, relid);
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+	entry->spcnode = rnode.spcNode;
+	entry->relpages = 0;
+	entry->reltuples = 0;
+	entry->relallvisible = 0;
+	entry->relkind = rel->rd_rel->relkind;
+	natts = RelationGetNumberOfAttributes(rel);
+	entry->natts = natts;
+	entry->attnum = palloc0(sizeof(int) * natts);
+	entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+	/* only heap contain transaction information */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+		{
+			entry->on_commit_delete = true;
+			register_on_commit_action(rel->rd_node.relNode, ONCOMMIT_DELETE_ROWS);
+		}
+
+		entry->relfrozenxid = RecentXmin;
+		entry->relminmxid = GetOldestMultiXactId();
+		insert_gtt_relfrozenxid_to_ordered_list(entry->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+		gtt_storage_checkin(rnode);
+	}
+	else
+	{
+		entry->on_commit_delete = false;
+		entry->relfrozenxid = 0;
+		entry->relminmxid = 0;
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+void
+forget_gtt_storage_info(Oid relid)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry)
+	{
+		int		i;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			RelFileNode rnode;
+
+			rnode.spcNode = entry->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = entry->relid;
+
+			gtt_storage_checkout(rnode, false);
+
+			Assert(TransactionIdIsNormal(entry->relfrozenxid));
+			remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+
+		for (i = 0; i < entry->natts; i++)
+		{
+			if (entry->att_stat_tups[i])
+			{
+				heap_freetuple(entry->att_stat_tups[i]);
+				entry->att_stat_tups[i] = NULL;
+			}
+		}
+
+		pfree(entry->attnum);
+		pfree(entry->att_stat_tups);
+	}
+
+	hash_search(gtt_storage_local_hash,
+			(void *) &(relid), HASH_REMOVE, NULL);
+
+	return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool found;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (gtt_storage_local_hash == NULL)
+		return false;
+
+	hash_search(gtt_storage_local_hash,
+			(void *) &(relid), HASH_FIND, &found);
+
+	return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS				status;
+	gtt_local_hash_entry		*entry;
+	int			nrels = 0,
+				maxrels = 0;
+	SMgrRelation	*srels = NULL;
+	RelFileNode		*rnodes = NULL;
+	char			*relkinds = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		SMgrRelation srel;
+		RelFileNode rnode;
+
+		rnode.spcNode = entry->spcnode;
+		rnode.dbNode = MyDatabaseId;
+		rnode.relNode = entry->relid;
+
+		srel = smgropen(rnode, MyBackendId);
+
+		/* allocate the initial array, or extend it, if needed */
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			srels = palloc(sizeof(SMgrRelation) * maxrels);
+			rnodes = palloc(sizeof(RelFileNode) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+			rnodes = repalloc(rnodes, sizeof(RelFileNode) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		srels[nrels] = srel;
+		rnodes[nrels] = rnode;
+		relkinds[nrels] = entry->relkind;
+		nrels++;
+	}
+
+	if (nrels > 0)
+	{
+		int i;
+
+		smgrdounlinkall(srels, nrels, false);
+
+		LWLockAcquire(&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.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (gtt_storage_local_hash == NULL)
+		return false;
+
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_FIND, &found);
+
+	if (!found)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	if (relpages)
+		*relpages = entry->relpages;
+
+	if (reltuples)
+		*reltuples = entry->reltuples;
+
+	if (relallvisible)
+		*relallvisible = entry->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = entry->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = entry->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	MemoryContext			oldcontext;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+						(void *) &reloid, HASH_FIND, &found);
+
+	if (!found)
+		return;
+
+	/* todo */
+	if (entry->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	Assert(entry->relid == reloid);
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == 0)
+		{
+			entry->attnum[i] = attnum;
+			break;
+		}
+		else if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < entry->natts);
+	Assert(entry->att_stat_tups[i] == NULL);
+	entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+						(void *) &reloid, HASH_FIND, &found);
+
+	if (!found)
+		return NULL;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			return entry->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int			i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	HeapTuple	tuple;
+	Relation	rel = NULL;
+	int		attnum = PG_GETARG_INT32(1);
+	Oid		reloid = PG_GETARG_OID(0);
+	char		rel_persistence;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	  tupdesc;
+	MemoryContext oldcontext;
+	Tuplestorestate *tupstore;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = table_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+
+	table_close(rel, NoLock);
+	table_close(pg_tatistic, AccessShareLock);
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate	*tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Relation	rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	if (get_gtt_relstats(reloid,
+						&relpages, &reltuples, &relallvisible,
+						&relfrozenxid, &relminmxid))
+	{
+		Datum	values[5];
+		bool	isnull[5];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = Int32GetDatum(relpages);
+		values[1] = Float4GetDatum((float4)reltuples);
+		values[2] = Int32GetDatum(relallvisible);
+		values[3] = UInt32GetDatum(relfrozenxid);
+		values[4] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+
+	tuplestore_donestoring(tupstore);
+	table_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(rel->rd_node);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	table_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(indexOid));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid relOid = index->rd_index->indrelid;
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	if (gtt_storage_attached(relOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int			i;
+	Oid			toastrelid;
+	List		*seqlist = NIL;
+	ListCell	*seqcell = NULL;
+
+	if (operation != CMD_INSERT)
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_attached(relation->rd_node.relNode))
+		return;
+
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+
+		index_build(relation, index, info, true, false);
+	}
+
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid 		indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo 	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	seqlist = getOwnedSequences(RelationGetRelid(relation));
+	foreach(seqcell, seqlist)
+	{
+		Oid 		seq_relid = lfirst_oid(seqcell);
+		Relation	seq_rel;
+
+		seq_rel = relation_open(seq_relid, RowExclusiveLock);
+	
+		/* This check must match AlterSequence! */
+		if (!pg_class_ownercheck(seq_relid, GetUserId()))
+			aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SEQUENCE,
+						   RelationGetRelationName(seq_rel));
+
+		RelationCreateStorage(seq_rel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seq_rel);
+		relation_close(seq_rel, NoLock);
+	}
+
+	return;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index f681aaf..e1fa3ec 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM 
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c4420dd..22b9c18 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -184,6 +185,13 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(onerel->rd_node.relNode))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -586,14 +594,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 		 * pg_statistic for columns we didn't process, we leave them alone.)
 		 */
 		update_attstats(RelationGetRelid(onerel), inh,
-						attr_cnt, vacattrstats);
+						attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
 
 		for (ind = 0; ind < nindexes; ind++)
 		{
 			AnlIndexData *thisdata = &indexdata[ind];
 
 			update_attstats(RelationGetRelid(Irel[ind]), false,
-							thisdata->attr_cnt, thisdata->vacattrstats);
+							thisdata->attr_cnt, thisdata->vacattrstats,
+							RelationGetRelPersistence(Irel[ind]));
 		}
 
 		/*
@@ -1456,7 +1465,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
  *		by taking a self-exclusive lock on the relation in analyze_rel().
  */
 static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
 {
 	Relation	sd;
 	int			attno;
@@ -1558,31 +1567,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/* Update column statistic to localhash, not catalog */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 11ce1bb..552fd87 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -391,6 +391,12 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/* not support cluster global temp table yet */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("not support cluster global temporary tables yet")));
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index e79ede4..078694d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -27,6 +27,7 @@
 #include "catalog/dependency.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -1062,7 +1063,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, stmt->filename, stmt->is_program,
@@ -2840,6 +2841,8 @@ CopyFrom(CopyState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	estate->es_result_relations = resultRelInfo;
 	estate->es_num_result_relations = 1;
 	estate->es_result_relation_info = resultRelInfo;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index ec20ba3..a29cc00 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2614,6 +2614,16 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 			!isTempNamespace(classtuple->relnamespace))
 			continue;
 
+		/* not support reindex on global temp table, so skip it */
+		if (classtuple->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			ereport(WARNING,
+				(errmsg("global temp table \"%s.%s\" skip reindexed",
+					get_namespace_name(get_rel_namespace(relid)),
+					get_rel_name(relid))));
+			continue;
+		}
+
 		/* Check user/system classification, and optionally skip */
 		if (objectKind == REINDEX_OBJECT_SYSTEM &&
 			!IsSystemClass(relid, classtuple))
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index d8cafc4..deecde8 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -96,7 +96,8 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 	 * transaction.
 	 */
 	relpersistence = get_rel_persistence(relid);
-	if (relpersistence == RELPERSISTENCE_TEMP)
+	if (relpersistence == RELPERSISTENCE_TEMP ||
+		relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
 
 	/* Check permissions. */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..c55dcf7 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -94,7 +94,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +108,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -222,7 +223,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* now initialize the sequence's data */
 	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -327,7 +328,7 @@ ResetSequence(Oid seq_relid)
 	/*
 	 * Insert the modified tuple into the new storage file.
 	 */
-	fill_seq_with_data(seq_rel, tuple);
+	fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -340,17 +341,21 @@ ResetSequence(Oid seq_relid)
  * Initialize a sequence's relation with the specified tuple as content
  */
 static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 	sequence_magic *sm;
 	OffsetNumber offnum;
+	bool lockBuffer = false;
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ReadBuffer(rel, P_NEW);
-	Assert(BufferGetBlockNumber(buf) == 0);
+	if (buf == InvalidBuffer)
+	{
+		buf = ReadBuffer(rel, P_NEW);
+		Assert(BufferGetBlockNumber(buf) == 0);
+		lockBuffer = true;
+	}
 
 	page = BufferGetPage(buf);
 
@@ -360,7 +365,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* Now insert sequence tuple */
 
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+	if (lockBuffer)
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +416,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	END_CRIT_SECTION();
 
-	UnlockReleaseBuffer(buf);
+	if (lockBuffer)
+		UnlockReleaseBuffer(buf);
 }
 
 /*
@@ -502,7 +509,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * Insert the modified tuple into the new storage file.
 		 */
-		fill_seq_with_data(seqrel, newdatatuple);
+		fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
 	}
 
 	/* process OWNED BY if given */
@@ -611,7 +618,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +943,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1178,6 +1185,25 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
 	LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
 
 	page = BufferGetPage(*buf);
+	if (GlobalTempRelationPageIsNotInitialized(rel, page))
+	{
+		/* Initialize sequence for global temporary tables */
+		Datum		value[SEQ_COL_LASTCOL] = {0};
+		bool		null[SEQ_COL_LASTCOL] = {false};
+		HeapTuple	tuple;
+		int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+		/*
+		 * last_value from pg_sequence.seqstart
+		 * log_cnt = 0
+		 * is_called = false
+		 */
+		value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+		tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+		fill_seq_with_data(rel, tuple, *buf);
+		heap_freetuple(tuple);
+	}
 	sm = (sequence_magic *) PageGetSpecialPointer(page);
 
 	if (sm->magic != SEQ_MAGIC)
@@ -1954,3 +1980,23 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index b7c8d66..e9704f8 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -553,6 +554,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 								  Relation partitionTbl);
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static bool has_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -598,6 +600,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
@@ -608,8 +611,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Check consistency of arguments
 	 */
+	/* global temp table same as local temp table */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !(stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+			 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -639,7 +644,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	/* global temp table same as local temp table */
+	if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+		 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -740,6 +747,53 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* global temp table */
+	has_oncommit_clause = has_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* check parent table*/
+		if (inheritOids && stmt->oncommit == ONCOMMIT_NOOP && !has_oncommit_clause)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			table_close(relation, NoLock);
+		}
+
+		if (has_oncommit_clause)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "can not defeine global temp table with on commit and with clause at same time");
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temp table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (has_oncommit_clause)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1824,7 +1878,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);
@@ -3392,6 +3447,13 @@ RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal, bo
 	 * specially.
 	 */
 	targetrelation = relation_open(myrelid, is_index ? ShareUpdateExclusiveLock : AccessExclusiveLock);
+
+	if (RELATION_IS_GLOBAL_TEMP(targetrelation))
+	{
+		if (is_other_backend_use_gtt(targetrelation->rd_node))
+			elog(ERROR, "can not rename relation when other backend attached this global temp table");
+	}
+
 	namespaceId = RelationGetNamespace(targetrelation);
 
 	/*
@@ -3567,6 +3629,13 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(rel->rd_node))
+			elog(ERROR, "can not alter relation when other backend attached this global temp table");
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -8179,6 +8248,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -13221,7 +13296,7 @@ index_copy_data(Relation rel, RelFileNode newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
@@ -14628,7 +14703,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",
@@ -17269,3 +17346,20 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
 		table_close(rel, NoLock);
 	}
 }
+
+static bool
+has_oncommit_option(List *options)
+{
+	ListCell   *listptr;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (pg_strcasecmp(def->defname, "on_commit_delete_rows") == 0)
+			return true;
+	}
+
+	return false;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index d625d17..f58c44d 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/vacuum.h"
@@ -1217,6 +1218,17 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	 /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		is_gtt = true;
+		up_gtt_relstats(relation,
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1230,17 +1242,26 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relpages = (int32) num_pages;
+	else if (pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (is_gtt)
+		relation->rd_rel->reltuples = (float4) num_tuples;
+	else if (pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	else if (pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1285,7 +1306,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1296,7 +1318,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1394,6 +1417,10 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/* global temp table relstats not in pg_class */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1451,6 +1478,25 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/*
+	 * Global temp table get frozenxid from MyProc
+	 * to avoid the vacuum truncate clog that gtt need.
+	 */
+	if (max_active_gtt > 0)
+	{
+		TransactionId oldest_gtt_frozenxid =
+			list_all_session_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid) &&
+			TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+		{
+			ereport(WARNING,
+				(errmsg("global temp table oldest FrozenXid is far in the past"),
+				 errhint("please truncate them or kill those sessions that use them.")));
+			newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1778,6 +1824,18 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(onerel->rd_node.relNode))
+	{
+		ereport(WARNING,
+				(errmsg("skipping vacuum empty global temp table \"%s\"",
+						RelationGetRelationName(onerel))));
+		relation_close(onerel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index ee5c3a6..0d7a2d4 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandTag((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index c13b1d3..253054d 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
@@ -543,6 +544,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	init_gtt_storage(mtstate->operation, leaf_part_rri);
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d71c0a4..0900907 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,6 +42,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -2372,6 +2373,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			ExecOpenIndices(resultRelInfo,
 							node->onConflictAction != ONCONFLICT_NONE);
 
+		init_gtt_storage(operation, resultRelInfo);
+
 		/*
 		 * If this is an UPDATE and a BEFORE UPDATE trigger is present, the
 		 * trigger itself might modify the partition-key values. So arrange
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 905bbe7..170963d 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -591,6 +591,8 @@ static void
 set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 						  RangeTblEntry *rte)
 {
+	char relpersistence;
+
 	/*
 	 * The flag has previously been initialized to false, so we can just
 	 * return if it becomes clear that we can't safely set it.
@@ -618,7 +620,11 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			/* global temp table is same as local temp table */
+			relpersistence = get_rel_persistence(rte->relid);
+
+			if (relpersistence == RELPERSISTENCE_TEMP ||
+				relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b44efd6..14dbaaf 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6307,7 +6307,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
+	/* global temp table is same as local temp table */
 	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index d82fc5a..93c6d97 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -28,6 +28,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temp table */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(indexRelation->rd_node.relNode))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6676412..0685c1c 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2585,6 +2585,11 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("materialized views must not use temporary tables or views")));
 
+		if (is_query_using_gtt(query))
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("materialized views must not use global temporary tables or views")));
+
 		/*
 		 * A materialized view would either need to save parameters for use in
 		 * maintaining/loading the data or prohibit them entirely.  The latter
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 96e7fdb..30f585c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3290,17 +3290,11 @@ OptTemp:	TEMPORARY					{ $$ = RELPERSISTENCE_TEMP; }
 			| LOCAL TEMP				{ $$ = RELPERSISTENCE_TEMP; }
 			| GLOBAL TEMPORARY
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
-					$$ = RELPERSISTENCE_TEMP;
+					$$ = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
-					$$ = RELPERSISTENCE_TEMP;
+					$$ = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED					{ $$ = RELPERSISTENCE_UNLOGGED; }
 			| /*EMPTY*/					{ $$ = RELPERSISTENCE_PERMANENT; }
@@ -11594,19 +11588,13 @@ OptTempTableName:
 				}
 			| GLOBAL TEMPORARY opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED opt_table qualified_name
 				{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b875a50..123945b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index ee2d2b5..9c9abaa 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 6d1f28c..ed837d1 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2086,6 +2086,11 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* autovacuum skip vacuum global temp table */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2152,7 +2157,9 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		/* autovacuum skip vacuum global temp table */
+		if (classForm->relpersistence == RELPERSISTENCE_TEMP || 
+			classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 5880054..98f7b43 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -52,6 +53,7 @@
 #include "utils/rel.h"
 #include "utils/resowner_private.h"
 #include "utils/timestamp.h"
+#include "utils/guc.h"
 
 
 /* Note: these two macros only work on shared buffers, not local ones! */
@@ -2809,6 +2811,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(relation->rd_node.relNode))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d5..7adb969 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -21,6 +21,7 @@
 #include "access/nbtree.h"
 #include "access/subtrans.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, active_gtt_shared_hash_size());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* global temporary table */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 4a5b26c..9754168 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -62,6 +62,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -4087,3 +4088,77 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temp table.
+ */
+int
+list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct *arrayP = procArray;
+	TransactionId result = InvalidTransactionId;
+	int			index;
+	int			flags = 0;
+	int			i = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+		*n = 0;
+	}
+
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		volatile PGXACT *pgxact = &allPgXact[pgprocno];
+
+		if (pgxact->vacuumFlags & flags)
+			continue;
+
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->session_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->session_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->session_gtt_frozenxid, result))
+				result = proc->session_gtt_frozenxid;
+
+			if (max_size > 0)
+			{
+				pids[i] = proc->pid;
+				xids[i] = proc->session_gtt_frozenxid;
+				i++;
+			}
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (max_size > 0)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index eb321f7..3893cef 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 8406644..0d99321 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -1008,6 +1008,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 0be26fe..c169c99 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -112,6 +112,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -4570,12 +4571,25 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -4700,15 +4714,27 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6090,6 +6116,7 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	{
 		/* Simple variable --- look to stats for the underlying table */
 		RangeTblEntry *rte = planner_rt_fetch(index->rel->relid, root);
+		char	rel_persistence = get_rel_persistence(rte->relid);
 
 		Assert(rte->rtekind == RTE_RELATION);
 		relid = rte->relid;
@@ -6107,6 +6134,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6118,6 +6152,8 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	}
 	else
 	{
+		char	rel_persistence = get_rel_persistence(index->indexoid);
+
 		/* Expression --- maybe there are stats for the index itself */
 		relid = index->indexoid;
 		colnum = 1;
@@ -6133,6 +6169,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7046,6 +7089,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		/* attempt to lookup stats in relation for this index column */
 		if (attnum != 0)
 		{
+			char	rel_persistence = get_rel_persistence(rte->relid);
+
 			/* Simple variable -- look to stats for the underlying table */
 			if (get_relation_stats_hook &&
 				(*get_relation_stats_hook) (root, rte, attnum, &vardata))
@@ -7058,6 +7103,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7070,6 +7123,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		}
 		else
 		{
+			char	rel_persistence = get_rel_persistence(index->indexoid);
+
 			/*
 			 * Looks like we've found an expression column in the index. Let's
 			 * see if there's any stats for it.
@@ -7089,6 +7144,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index fb0599f..13f4d8a 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -2878,6 +2879,18 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index ff70326..1cf7063 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -63,6 +63,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1127,6 +1128,24 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1181,6 +1200,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			gtt_fix_index_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -2220,6 +2240,8 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		gtt_fix_index_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3316,6 +3338,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3430,6 +3456,9 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
 
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+		elog(ERROR, "global temp table does not allow setting new relfilenode");
+
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
@@ -3470,7 +3499,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 				/* handle these directly, at least for now */
 				SMgrRelation srel;
 
-				srel = RelationCreateStorage(newrnode, persistence);
+				srel = RelationCreateStorage(newrnode, persistence, relation);
 				smgrclose(srel);
 			}
 			break;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 464f264..0173156 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -140,6 +140,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temp table feature.
+ * global temp table define can still storage in catalog
+ * just can not use.
+ * num > 0 means database can management num active global temp table.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2047,6 +2059,15 @@ static struct config_bool ConfigureNamesBool[] =
 static struct config_int ConfigureNamesInt[] =
 {
 	{
+		{"max_active_global_temporary_table", PGC_POSTMASTER, UNGROUPED,
+			gettext_noop("max active global temporary table."),
+			NULL
+		},
+		&max_active_gtt,
+		DEFAULT_NUM_ACTIVE_GTT, MIN_NUM_ACTIVE_GTT, MAX_NUM_ACTIVE_GTT,
+		NULL, NULL, NULL
+	},
+	{
 		{"archive_timeout", PGC_SIGHUP, WAL_ARCHIVING,
 			gettext_noop("Forces a switch to the next WAL file if a "
 						 "new file has not been started within N seconds."),
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index d92a662..07c99c5 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15598,6 +15598,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	{
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
+		char		*table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15649,9 +15650,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 			binary_upgrade_set_pg_class_oids(fout, q,
 											 tbinfo->dobj.catId.oid, false);
 
+		if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED)
+			table_type = "UNLOGGED ";
+		else if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			table_type = "GLOBAL TEMPORARY ";
+		else
+			table_type = "";
+
 		appendPQExpBuffer(q, "CREATE %s%s %s",
-						  tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ?
-						  "UNLOGGED " : "",
+						  table_type,
 						  reltypename,
 						  qualrelname);
 
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 5f9a102..3e40297 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -84,7 +84,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -157,7 +157,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 7e524ea..371168f 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -441,22 +443,46 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 	 * output, so we have to copy that system table.  It's easiest to do that
 	 * by treating it as a user table.
 	 */
-	snprintf(query + strlen(query), sizeof(query) - strlen(query),
-			 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
-			 "  SELECT c.oid, 0::oid, 0::oid "
-			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
-			 "         ON c.relnamespace = n.oid "
-			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
-	/* exclude possible orphaned temp tables */
-			 "    ((n.nspname !~ '^pg_temp_' AND "
-			 "      n.nspname !~ '^pg_toast_temp_' AND "
-			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
-			 "                        'binary_upgrade', 'pg_toast') AND "
-			 "      c.oid >= %u::pg_catalog.oid) OR "
-			 "     (n.nspname = 'pg_catalog' AND "
-			 "      relname IN ('pg_largeobject') ))), ",
-			 FirstNormalObjectId);
+	if (skip_gtt)
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude global temp tables */
+				 "    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
+	else
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
 
 	/*
 	 * Add a CTE that collects OIDs of toast tables belonging to the tables
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 70194eb..8dd9e66 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -411,7 +411,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -642,7 +642,9 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -653,7 +655,9 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index b156b51..7b888ff 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -391,7 +391,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index f3c7eb9..28134e2 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3723,7 +3723,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b6b08d0..16377e6 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1016,6 +1016,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2378,6 +2380,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2586,6 +2591,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 78b33b2..4be89f6 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -168,6 +168,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 07a86c7..b3128d9 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5507,6 +5507,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4191',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4192',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{relid,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4193',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4194',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 048003c..af48cdf 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -19,7 +19,7 @@
 #include "storage/smgr.h"
 #include "utils/relcache.h"
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationTruncate(Relation rel, BlockNumber nblocks);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..3afd71d
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,43 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid);
+extern bool is_other_backend_use_gtt(RelFileNode node);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(RelFileNode node);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Relation relation,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void gtt_force_enable_index(Relation index);
+extern void gtt_fix_index_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 3f88683..f870e9a 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 8fda8e4..172eae9 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_TBM,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_SXACT,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index d217801..8adde87 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -117,6 +117,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId session_gtt_frozenxid;	/* session level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ce93ace..0f7262e 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -281,6 +281,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 44ed04d..6b6d2da 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	struct SMgrRelationData *rd_smgr;	/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -277,6 +277,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -535,11 +536,13 @@ typedef struct ViewOptions
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -547,6 +550,7 @@ typedef struct ViewOptions
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -559,6 +563,14 @@ typedef struct ViewOptions
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is either local temp relation of this session
+ * 		or global temp relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -602,6 +614,17 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..50ca9ac
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,7 @@
+reset search_path;
+drop schema gtt cascade;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..5e2daa9
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,297 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+ERROR:  cannot create indexes on global temporary tables using concurrent mode
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+ERROR:  not support cluster global temporary tables yet
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ok
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  can not defeine global temp table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test;
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR:  insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count 
+-------
+     1
+(1 row)
+
+select count(*) from orders;
+ count 
+-------
+     0
+(1 row)
+
+-- ERROR
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 1)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 300)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 18 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to table gtt_function.gt1
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..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..366518d
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,161 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          114688 |                 614400
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |            49152 |         49152 |                  49152
+(2 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..9fe5fd4
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,9 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..0e21b30
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,76 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+(1 row)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+(1 row)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 634f825..6e1297a 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1349,6 +1349,93 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d2b17dd..80e577f 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..f3cf710
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,6 @@
+
+
+reset search_path;
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..0274d99
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,199 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ok
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test;
+
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ERROR
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d7d81de
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,42 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..5203c2b
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,76 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..2f4d883
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,16 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..f041892
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,42 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
#165曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Prabhat Sahu (#158)
Re: [Proposal] Global temporary tables

2020年2月25日 下午9:36,Prabhat Sahu <prabhat.sahu@enterprisedb.com> 写道:

Hi All,

Please check the below findings on GTT.
-- Scenario 1:
Under "information_schema", We are not allowed to create "temporary table", whereas we can CREATE/DROP "Global Temporary Table", is it expected ?

postgres=# create temporary table information_schema.temp1(c1 int);
ERROR: cannot create temporary relation in non-temporary schema
LINE 1: create temporary table information_schema.temp1(c1 int);
^

postgres=# create global temporary table information_schema.temp1(c1 int);
CREATE TABLE

postgres=# drop table information_schema.temp1 ;
DROP TABLE

-- Scenario 2:
Here I am getting the same error message in both the below cases.
We may add a "global" keyword with GTT related error message.

postgres=# create global temporary table gtt1 (c1 int unique);
CREATE TABLE
postgres=# create temporary table tmp1 (c1 int unique);
CREATE TABLE

postgres=# create temporary table tmp2 (c1 int references gtt1(c1) );
ERROR: constraints on temporary tables may reference only temporary tables

postgres=# create global temporary table gtt2 (c1 int references tmp1(c1) );
ERROR: constraints on temporary tables may reference only temporary tables

Fixed in global_temporary_table_v15-pg13.patch

Wenjing

Show quoted text

Thanks,
Prabhat Sahu

On Tue, Feb 25, 2020 at 2:25 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> wrote:

2020年2月24日 下午5:44,Prabhat Sahu <prabhat.sahu@enterprisedb.com <mailto:prabhat.sahu@enterprisedb.com>> 写道:

On Fri, Feb 21, 2020 at 9:10 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> wrote:
Hi,
I have started testing the "Global temporary table" feature,
That's great, I see hope.
from "gtt_v11-pg13.patch". Below is my findings:

-- session 1:
postgres=# create global temporary table gtt1(a int);
CREATE TABLE

-- seeeion 2:
postgres=# truncate gtt1 ;
ERROR: could not open file "base/13585/t3_16384": No such file or directory

is it expected?

Oh ,this is a bug, I fixed it.
Thanks for the patch.
I have verified the same, Now the issue is resolved with v12 patch.

Kindly confirm the below scenario:

postgres=# create global temporary table gtt1 (c1 int unique);
CREATE TABLE

postgres=# create global temporary table gtt2 (c1 int references gtt1(c1) );
ERROR: referenced relation "gtt1" is not a global temp table

postgres=# create table tab2 (c1 int references gtt1(c1) );
ERROR: referenced relation "gtt1" is not a global temp table

Thanks,
Prabhat Sahu

GTT supports foreign key constraints in global_temporary_table_v13-pg13.patch

Wenjing

--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

#166tushar
tushar.ahuja@enterprisedb.com
In reply to: 曾文旌(义从) (#165)
Re: [Proposal] Global temporary tables

On 2/27/20 9:43 AM, 曾文旌(义从) wrote:

_-- Scenario 2:_
Here I am getting the same error message in both the below cases.
We may add a "global" keyword with GTT related error message.

postgres=# create global temporary table gtt1 (c1 int unique);
CREATE TABLE
postgres=# create temporary table tmp1 (c1 int unique);
CREATE TABLE

postgres=# create temporary table tmp2 (c1 int references gtt1(c1) );
ERROR:  constraints on temporary tables may reference only temporary
tables

postgres=# create global temporary table gtt2 (c1 int references
tmp1(c1) );
ERROR:  constraints on temporary tables may reference only temporary
tables

Fixed in global_temporary_table_v15-pg13.patch

Thanks Wenjing.

This below scenario is not working  i.e even 'on_commit_delete_rows' is
true then after commit -  rows are NOT removing

postgres=#  create global  temp table foo1(n int) with
(on_commit_delete_rows='true');
CREATE TABLE
postgres=#
postgres=# begin;
BEGIN
postgres=*# insert into foo1 values (9);
INSERT 0 1
postgres=*# insert into foo1 values (9);
INSERT 0 1
postgres=*# select * from foo1;
 n
---
 9
 9
(2 rows)

postgres=*# commit;
COMMIT
postgres=# select * from foo1;   -- after commit -there should be 0 row
as on_commit_delete_rows is 'true'
 n
---
 9
 9
(2 rows)

postgres=# \d+ foo1
                                   Table "public.foo1"
 Column |  Type   | Collation | Nullable | Default | Storage | Stats
target | Description
--------+---------+-----------+----------+---------+---------+--------------+-------------
 n      | integer |           |          |         | plain
|              |
Access method: heap
Options: on_commit_delete_rows=true

postgres=#

but if user - create table this way then it is working as expected

postgres=#  create global  temp table foo2(n int) *on commit delete rows;*
CREATE TABLE
postgres=# begin; insert into foo2 values (9); insert into foo2 values
(9); commit; select * from foo2;
BEGIN
INSERT 0 1
INSERT 0 1
COMMIT
 n
---
(0 rows)

postgres=#

i guess , problem is something with this syntax - create global temp
table foo1(n int) *with (on_commit_delete_rows='true'); *

--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company

#167曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: tushar (#166)
1 attachment(s)
Re: [Proposal] Global temporary tables

2020年3月2日 下午10:47,tushar <tushar.ahuja@enterprisedb.com> 写道:

On 2/27/20 9:43 AM, 曾文旌(义从) wrote:

-- Scenario 2:
Here I am getting the same error message in both the below cases.
We may add a "global" keyword with GTT related error message.

postgres=# create global temporary table gtt1 (c1 int unique);
CREATE TABLE
postgres=# create temporary table tmp1 (c1 int unique);
CREATE TABLE

postgres=# create temporary table tmp2 (c1 int references gtt1(c1) );
ERROR: constraints on temporary tables may reference only temporary tables

postgres=# create global temporary table gtt2 (c1 int references tmp1(c1) );
ERROR: constraints on temporary tables may reference only temporary tables

Fixed in global_temporary_table_v15-pg13.patch

Thanks Wenjing.

This below scenario is not working i.e even 'on_commit_delete_rows' is true then after commit - rows are NOT removing

postgres=# create global temp table foo1(n int) with (on_commit_delete_rows='true');
CREATE TABLE
postgres=#
postgres=# begin;
BEGIN
postgres=*# insert into foo1 values (9);
INSERT 0 1
postgres=*# insert into foo1 values (9);
INSERT 0 1
postgres=*# select * from foo1;
n
---
9
9
(2 rows)

postgres=*# commit;
COMMIT
postgres=# select * from foo1; -- after commit -there should be 0 row as on_commit_delete_rows is 'true'
n
---
9
9
(2 rows)

postgres=# \d+ foo1
Table "public.foo1"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
--------+---------+-----------+----------+---------+---------+--------------+-------------
n | integer | | | | plain | |
Access method: heap
Options: on_commit_delete_rows=true

postgres=#

but if user - create table this way then it is working as expected

postgres=# create global temp table foo2(n int) on commit delete rows;
CREATE TABLE
postgres=# begin; insert into foo2 values (9); insert into foo2 values (9); commit; select * from foo2;
BEGIN
INSERT 0 1
INSERT 0 1
COMMIT
n
---
(0 rows)

postgres=#

i guess , problem is something with this syntax - create global temp table foo1(n int) with (on_commit_delete_rows='true');

Thanks for review.

I fixed in global_temporary_table_v16-pg13.patch.

Wenjing

Show quoted text

--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/ <https://www.enterprisedb.com/&gt;
The Enterprise PostgreSQL Company

Attachments:

global_temporary_table_v16-pg13.patchapplication/octet-stream; name=global_temporary_table_v16-pg13.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 5325dd3..c76a685 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -168,6 +168,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * For global temp table only
+	 * use ShareUpdateExclusiveLock for ensure safety
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temp table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},	
 	/* list terminator */
 	{{NULL}}
 };
@@ -1496,6 +1509,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1596,13 +1611,18 @@ build_reloptions(Datum reloptions, bool validate,
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	/*
 	 * There are no options for partitioned tables yet, but this is able to do
 	 * some validation.
 	 */
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index dd975b1..1610e7d 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1013,7 +1013,9 @@ gistGetFakeLSN(Relation rel)
 {
 	static XLogRecPtr counter = FirstNormalUnloggedLSN;
 
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	/* global temp is same as local temp table */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 4871b7f..16b00c9 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -149,7 +149,9 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	/* global temp table is same as local temp table */
+	if (!(index->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		index->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 3fa4b76..b54882d 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -598,7 +598,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -651,7 +651,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 03c43ef..2674132 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -62,6 +62,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -399,9 +400,9 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
 	Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
-	/* not every AM requires these to be valid, but heap does */
-	Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
-	Assert(MultiXactIdIsValid(onerel->rd_rel->relminmxid));
+	/* not every AM requires these to be valid, but regular heap does */
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ MultiXactIdIsValid(onerel->rd_rel->relminmxid));
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 39b8f17..38b46d0 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -601,6 +602,10 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(rel->rd_node.relNode))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 4361568..08ef041 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6348,6 +6348,10 @@ StartupXLOG(void)
 	else
 		recoveryTargetTLI = ControlFile->checkPointCopy.ThisTimeLineID;
 
+	/* clean temp relation files */
+	if (max_active_gtt > 0)
+		RemovePgTempFiles();
+
 	/*
 	 * Check for signal files, and if so set up state for offline recovery
 	 */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index f8f0b48..dc8bbb1 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -42,6 +42,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 7d6acae..3be8d63 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -393,7 +393,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/* global temp table is same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index e31478b..1c382c6 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -427,7 +429,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -957,6 +959,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -995,8 +998,18 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/* global temp table not remember transaction info in catalog */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1358,6 +1371,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1938,6 +1952,13 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/* We allow to drop global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(rel->rd_node))
+			elog(ERROR, "can not drop relation when other backend attached this global temp table");
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3165,7 +3186,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3177,7 +3198,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3223,8 +3244,13 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		/* truncate global temp table only need RowExclusiveLock */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3257,6 +3283,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3265,23 +3292,37 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (!gtt_storage_attached(rel->rd_node.relNode))
+			return;
+
+		/*
+		 * Truncate global temp table only need RowExclusiveLock
+		 */
+		lockmode = RowExclusiveLock;
+	}
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(rel, 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 1681f61..9b6c844 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -52,6 +52,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -877,6 +878,19 @@ index_create(Relation heapRelation,
 						indexRelationName, RelationGetRelationName(heapRelation))));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temp table use concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot create indexes on global temporary tables using concurrent mode")));
+
+		/* if global temp table not init storage, then skip build index */
+		if (!gtt_storage_attached(heapRelation->rd_node.relNode))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -2060,6 +2074,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.
@@ -2666,6 +2687,11 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	/* update index stats into localhash and rel_rd_rel for global temp table */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		is_gtt = true;
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2751,21 +2777,35 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
+		if (is_gtt)
+			rel->rd_rel->relpages = (int32) relpages;
+		else if (rd_rel->relpages != (int32) relpages)
 		{
 			rd_rel->relpages = (int32) relpages;
 			dirty = true;
 		}
-		if (rd_rel->reltuples != (float4) reltuples)
+
+		if (is_gtt)
+			rel->rd_rel->reltuples = (float4) reltuples;
+		else if (rd_rel->reltuples != (float4) reltuples)
 		{
 			rd_rel->reltuples = (float4) reltuples;
 			dirty = true;
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+
+		if (is_gtt)
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+		else if (rd_rel->relallvisible != (int32) relallvisible)
 		{
 			rd_rel->relallvisible = (int32) relallvisible;
 			dirty = true;
 		}
+
+		if (is_gtt)
+		{
+			up_gtt_relstats(rel, relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
+		}
 	}
 
 	/*
@@ -2879,6 +2919,15 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, index, val);
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		if (!gtt_storage_attached(indexRelation->rd_node.relNode))
+		{
+			gtt_force_enable_index(indexRelation);
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3474,6 +3523,15 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 				 errmsg("cannot reindex temporary tables of other sessions")));
 
 	/*
+	 * Because global temp table cannot change relfilenode
+	 * no support reindex on global temp table yet.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(iRel))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot reindex global temporary tables")));
+
+	/*
 	 * Also check for active uses of the index in the current transaction; we
 	 * don't want to reindex underneath an open indexscan.
 	 */
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 5ff7824..9b58f2a 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -647,6 +647,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		/* global temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp relations in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index fddfbf1..671c614 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -26,6 +26,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -75,7 +76,7 @@ static PendingRelDelete *pendingDeletes = NULL; /* head of linked list */
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -85,6 +86,8 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		/* global temp table use same storage strategy as local temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -117,6 +120,10 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/* remember global temp table storage info to localhash */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel)
+		remember_gtt_storage_info(rnode, rel);
+
 	return srel;
 }
 
@@ -486,8 +493,15 @@ smgrDoPendingDeletes(bool isCommit)
 		smgrdounlinkall(srels, nrels, false);
 
 		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* clean global temp table flags when transaction commit or rollback */
+			if (SmgrIsTemp(srels[i]) && 
+				gtt_storage_attached(srels[i]->smgr_rnode.node.relNode))
+				forget_gtt_storage_info(srels[i]->smgr_rnode.node.relNode);
+		}
+
 		pfree(srels);
 	}
 }
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..e877b0c
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1260 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  code to create and destroy physical storage for global temparary table
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_relstats_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+typedef struct gtt_ctl_data
+{
+	LWLock			lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct
+{
+	RelFileNode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+	Oid			relid;
+
+	Oid			spcnode;
+	/* pg_class stat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+	char		relkind;
+	bool		on_commit_delete;
+
+	/* pg_statistic */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(RelFileNode rnode);
+static void gtt_storage_checkout(RelFileNode rnode, bool skiplock);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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;
+	int			natts = 0;
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_gtt to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temp table yet");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create stroage file", RelationGetRelationName(rel));
+
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		/* First time through: initialize the hash table */
+		HASHCTL		ctl;
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		gtt_storage_local_hash =
+			hash_create("global temp relation table",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_relstats_context =
+			AllocSetContextCreate(CacheMemoryContext,
+							"gtt relstats context",
+							ALLOCSET_DEFAULT_SIZES);
+	}
+
+	/* Look up or create an entry */
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_ENTER, &found);
+
+	if (found)
+	{
+		elog(ERROR, "backend %d relid %u already exists in global temp table local hash",
+					MyBackendId, relid);
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+	entry->spcnode = rnode.spcNode;
+	entry->relpages = 0;
+	entry->reltuples = 0;
+	entry->relallvisible = 0;
+	entry->relkind = rel->rd_rel->relkind;
+	natts = RelationGetNumberOfAttributes(rel);
+	entry->natts = natts;
+	entry->attnum = palloc0(sizeof(int) * natts);
+	entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+	/* only heap contain transaction information */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+		{
+			entry->on_commit_delete = true;
+			register_on_commit_action(rel->rd_node.relNode, ONCOMMIT_DELETE_ROWS);
+		}
+
+		entry->relfrozenxid = RecentXmin;
+		entry->relminmxid = GetOldestMultiXactId();
+		insert_gtt_relfrozenxid_to_ordered_list(entry->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+		gtt_storage_checkin(rnode);
+	}
+	else
+	{
+		entry->on_commit_delete = false;
+		entry->relfrozenxid = 0;
+		entry->relminmxid = 0;
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+void
+forget_gtt_storage_info(Oid relid)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry)
+	{
+		int		i;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			RelFileNode rnode;
+
+			rnode.spcNode = entry->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = entry->relid;
+
+			gtt_storage_checkout(rnode, false);
+
+			Assert(TransactionIdIsNormal(entry->relfrozenxid));
+			remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+
+		for (i = 0; i < entry->natts; i++)
+		{
+			if (entry->att_stat_tups[i])
+			{
+				heap_freetuple(entry->att_stat_tups[i]);
+				entry->att_stat_tups[i] = NULL;
+			}
+		}
+
+		pfree(entry->attnum);
+		pfree(entry->att_stat_tups);
+	}
+
+	hash_search(gtt_storage_local_hash,
+			(void *) &(relid), HASH_REMOVE, NULL);
+
+	return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool found;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (gtt_storage_local_hash == NULL)
+		return false;
+
+	hash_search(gtt_storage_local_hash,
+			(void *) &(relid), HASH_FIND, &found);
+
+	return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS				status;
+	gtt_local_hash_entry		*entry;
+	int			nrels = 0,
+				maxrels = 0;
+	SMgrRelation	*srels = NULL;
+	RelFileNode		*rnodes = NULL;
+	char			*relkinds = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		SMgrRelation srel;
+		RelFileNode rnode;
+
+		rnode.spcNode = entry->spcnode;
+		rnode.dbNode = MyDatabaseId;
+		rnode.relNode = entry->relid;
+
+		srel = smgropen(rnode, MyBackendId);
+
+		/* allocate the initial array, or extend it, if needed */
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			srels = palloc(sizeof(SMgrRelation) * maxrels);
+			rnodes = palloc(sizeof(RelFileNode) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+			rnodes = repalloc(rnodes, sizeof(RelFileNode) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		srels[nrels] = srel;
+		rnodes[nrels] = rnode;
+		relkinds[nrels] = entry->relkind;
+		nrels++;
+	}
+
+	if (nrels > 0)
+	{
+		int i;
+
+		smgrdounlinkall(srels, nrels, false);
+
+		LWLockAcquire(&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.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (gtt_storage_local_hash == NULL)
+		return false;
+
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_FIND, &found);
+
+	if (!found)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	if (relpages)
+		*relpages = entry->relpages;
+
+	if (reltuples)
+		*reltuples = entry->reltuples;
+
+	if (relallvisible)
+		*relallvisible = entry->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = entry->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = entry->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	MemoryContext			oldcontext;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+						(void *) &reloid, HASH_FIND, &found);
+
+	if (!found)
+		return;
+
+	/* todo */
+	if (entry->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	Assert(entry->relid == reloid);
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == 0)
+		{
+			entry->attnum[i] = attnum;
+			break;
+		}
+		else if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < entry->natts);
+	Assert(entry->att_stat_tups[i] == NULL);
+	entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+						(void *) &reloid, HASH_FIND, &found);
+
+	if (!found)
+		return NULL;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			return entry->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int			i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	HeapTuple	tuple;
+	Relation	rel = NULL;
+	int		attnum = PG_GETARG_INT32(1);
+	Oid		reloid = PG_GETARG_OID(0);
+	char		rel_persistence;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	  tupdesc;
+	MemoryContext oldcontext;
+	Tuplestorestate *tupstore;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = table_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+
+	table_close(rel, NoLock);
+	table_close(pg_tatistic, AccessShareLock);
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate	*tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Relation	rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	if (get_gtt_relstats(reloid,
+						&relpages, &reltuples, &relallvisible,
+						&relfrozenxid, &relminmxid))
+	{
+		Datum	values[5];
+		bool	isnull[5];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = Int32GetDatum(relpages);
+		values[1] = Float4GetDatum((float4)reltuples);
+		values[2] = Int32GetDatum(relallvisible);
+		values[3] = UInt32GetDatum(relfrozenxid);
+		values[4] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+
+	tuplestore_donestoring(tupstore);
+	table_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(rel->rd_node);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	table_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(indexOid));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid relOid = index->rd_index->indrelid;
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	if (gtt_storage_attached(relOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int			i;
+	Oid			toastrelid;
+	List		*seqlist = NIL;
+	ListCell	*seqcell = NULL;
+
+	if (operation != CMD_INSERT)
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_attached(relation->rd_node.relNode))
+		return;
+
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+
+		index_build(relation, index, info, true, false);
+	}
+
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid 		indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo 	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	seqlist = getOwnedSequences(RelationGetRelid(relation));
+	foreach(seqcell, seqlist)
+	{
+		Oid 		seq_relid = lfirst_oid(seqcell);
+		Relation	seq_rel;
+
+		seq_rel = relation_open(seq_relid, RowExclusiveLock);
+	
+		/* This check must match AlterSequence! */
+		if (!pg_class_ownercheck(seq_relid, GetUserId()))
+			aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SEQUENCE,
+						   RelationGetRelationName(seq_rel));
+
+		RelationCreateStorage(seq_rel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seq_rel);
+		relation_close(seq_rel, NoLock);
+	}
+
+	return;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index b8a3f46..491db16 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM 
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c4420dd..22b9c18 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -184,6 +185,13 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(onerel->rd_node.relNode))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -586,14 +594,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 		 * pg_statistic for columns we didn't process, we leave them alone.)
 		 */
 		update_attstats(RelationGetRelid(onerel), inh,
-						attr_cnt, vacattrstats);
+						attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
 
 		for (ind = 0; ind < nindexes; ind++)
 		{
 			AnlIndexData *thisdata = &indexdata[ind];
 
 			update_attstats(RelationGetRelid(Irel[ind]), false,
-							thisdata->attr_cnt, thisdata->vacattrstats);
+							thisdata->attr_cnt, thisdata->vacattrstats,
+							RelationGetRelPersistence(Irel[ind]));
 		}
 
 		/*
@@ -1456,7 +1465,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
  *		by taking a self-exclusive lock on the relation in analyze_rel().
  */
 static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
 {
 	Relation	sd;
 	int			attno;
@@ -1558,31 +1567,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/* Update column statistic to localhash, not catalog */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 11ce1bb..552fd87 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -391,6 +391,12 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/* not support cluster global temp table yet */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("not support cluster global temporary tables yet")));
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index e79ede4..078694d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -27,6 +27,7 @@
 #include "catalog/dependency.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -1062,7 +1063,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, stmt->filename, stmt->is_program,
@@ -2840,6 +2841,8 @@ CopyFrom(CopyState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	estate->es_result_relations = resultRelInfo;
 	estate->es_num_result_relations = 1;
 	estate->es_result_relation_info = resultRelInfo;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index ec20ba3..a29cc00 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2614,6 +2614,16 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 			!isTempNamespace(classtuple->relnamespace))
 			continue;
 
+		/* not support reindex on global temp table, so skip it */
+		if (classtuple->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			ereport(WARNING,
+				(errmsg("global temp table \"%s.%s\" skip reindexed",
+					get_namespace_name(get_rel_namespace(relid)),
+					get_rel_name(relid))));
+			continue;
+		}
+
 		/* Check user/system classification, and optionally skip */
 		if (objectKind == REINDEX_OBJECT_SYSTEM &&
 			!IsSystemClass(relid, classtuple))
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index d8cafc4..deecde8 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -96,7 +96,8 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 	 * transaction.
 	 */
 	relpersistence = get_rel_persistence(relid);
-	if (relpersistence == RELPERSISTENCE_TEMP)
+	if (relpersistence == RELPERSISTENCE_TEMP ||
+		relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
 
 	/* Check permissions. */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..c55dcf7 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -94,7 +94,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +108,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -222,7 +223,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* now initialize the sequence's data */
 	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -327,7 +328,7 @@ ResetSequence(Oid seq_relid)
 	/*
 	 * Insert the modified tuple into the new storage file.
 	 */
-	fill_seq_with_data(seq_rel, tuple);
+	fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -340,17 +341,21 @@ ResetSequence(Oid seq_relid)
  * Initialize a sequence's relation with the specified tuple as content
  */
 static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 	sequence_magic *sm;
 	OffsetNumber offnum;
+	bool lockBuffer = false;
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ReadBuffer(rel, P_NEW);
-	Assert(BufferGetBlockNumber(buf) == 0);
+	if (buf == InvalidBuffer)
+	{
+		buf = ReadBuffer(rel, P_NEW);
+		Assert(BufferGetBlockNumber(buf) == 0);
+		lockBuffer = true;
+	}
 
 	page = BufferGetPage(buf);
 
@@ -360,7 +365,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* Now insert sequence tuple */
 
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+	if (lockBuffer)
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +416,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	END_CRIT_SECTION();
 
-	UnlockReleaseBuffer(buf);
+	if (lockBuffer)
+		UnlockReleaseBuffer(buf);
 }
 
 /*
@@ -502,7 +509,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * Insert the modified tuple into the new storage file.
 		 */
-		fill_seq_with_data(seqrel, newdatatuple);
+		fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
 	}
 
 	/* process OWNED BY if given */
@@ -611,7 +618,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +943,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1178,6 +1185,25 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
 	LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
 
 	page = BufferGetPage(*buf);
+	if (GlobalTempRelationPageIsNotInitialized(rel, page))
+	{
+		/* Initialize sequence for global temporary tables */
+		Datum		value[SEQ_COL_LASTCOL] = {0};
+		bool		null[SEQ_COL_LASTCOL] = {false};
+		HeapTuple	tuple;
+		int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+		/*
+		 * last_value from pg_sequence.seqstart
+		 * log_cnt = 0
+		 * is_called = false
+		 */
+		value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+		tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+		fill_seq_with_data(rel, tuple, *buf);
+		heap_freetuple(tuple);
+	}
 	sm = (sequence_magic *) PageGetSpecialPointer(page);
 
 	if (sm->magic != SEQ_MAGIC)
@@ -1954,3 +1980,23 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 02a7c04..1b4d78b 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -553,6 +554,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 								  Relation partitionTbl);
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static OnCommitAction gtt_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -598,6 +600,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -608,8 +611,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Check consistency of arguments
 	 */
+	/* global temp table same as local temp table */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !(stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+			 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -639,7 +644,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	/* global temp table same as local temp table */
+	if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+		 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -740,6 +747,55 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* global temp table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* check parent table*/
+		if (inheritOids)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			table_close(relation, NoLock);
+		}
+
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "can not defeine global temp table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temp table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1824,7 +1880,8 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
 		 * table or the current physical file to be thrown away anyway.
 		 */
 		if (rel->rd_createSubid == mySubid ||
-			rel->rd_newRelfilenodeSubid == mySubid)
+			rel->rd_newRelfilenodeSubid == mySubid ||
+			RELATION_IS_GLOBAL_TEMP(rel))
 		{
 			/* Immediate, non-rollbackable truncation is OK */
 			heap_truncate_one_rel(rel);
@@ -3392,6 +3449,13 @@ RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal, bo
 	 * specially.
 	 */
 	targetrelation = relation_open(myrelid, is_index ? ShareUpdateExclusiveLock : AccessExclusiveLock);
+
+	if (RELATION_IS_GLOBAL_TEMP(targetrelation))
+	{
+		if (is_other_backend_use_gtt(targetrelation->rd_node))
+			elog(ERROR, "can not rename relation when other backend attached this global temp table");
+	}
+
 	namespaceId = RelationGetNamespace(targetrelation);
 
 	/*
@@ -3567,6 +3631,13 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(rel->rd_node))
+			elog(ERROR, "can not alter relation when other backend attached this global temp table");
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -8179,6 +8250,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -13221,7 +13298,7 @@ index_copy_data(Relation rel, RelFileNode newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
@@ -14628,7 +14705,9 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
 	 */
 	switch (rel->rd_rel->relpersistence)
 	{
+		/* global temp table same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -17220,3 +17299,28 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
 		table_close(rel, NoLock);
 	}
 }
+
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			if (defGetBoolean(def))
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index d625d17..f58c44d 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/vacuum.h"
@@ -1217,6 +1218,17 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	 /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		is_gtt = true;
+		up_gtt_relstats(relation,
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1230,17 +1242,26 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relpages = (int32) num_pages;
+	else if (pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (is_gtt)
+		relation->rd_rel->reltuples = (float4) num_tuples;
+	else if (pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	else if (pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1285,7 +1306,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1296,7 +1318,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1394,6 +1417,10 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/* global temp table relstats not in pg_class */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1451,6 +1478,25 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/*
+	 * Global temp table get frozenxid from MyProc
+	 * to avoid the vacuum truncate clog that gtt need.
+	 */
+	if (max_active_gtt > 0)
+	{
+		TransactionId oldest_gtt_frozenxid =
+			list_all_session_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid) &&
+			TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+		{
+			ereport(WARNING,
+				(errmsg("global temp table oldest FrozenXid is far in the past"),
+				 errhint("please truncate them or kill those sessions that use them.")));
+			newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1778,6 +1824,18 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(onerel->rd_node.relNode))
+	{
+		ereport(WARNING,
+				(errmsg("skipping vacuum empty global temp table \"%s\"",
+						RelationGetRelationName(onerel))));
+		relation_close(onerel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 28130fb..dcb4a0b 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index c13b1d3..253054d 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
@@ -543,6 +544,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	init_gtt_storage(mtstate->operation, leaf_part_rri);
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d71c0a4..0900907 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,6 +42,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -2372,6 +2373,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			ExecOpenIndices(resultRelInfo,
 							node->onConflictAction != ONCONFLICT_NONE);
 
+		init_gtt_storage(operation, resultRelInfo);
+
 		/*
 		 * If this is an UPDATE and a BEFORE UPDATE trigger is present, the
 		 * trigger itself might modify the partition-key values. So arrange
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 905bbe7..170963d 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -591,6 +591,8 @@ static void
 set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 						  RangeTblEntry *rte)
 {
+	char relpersistence;
+
 	/*
 	 * The flag has previously been initialized to false, so we can just
 	 * return if it becomes clear that we can't safely set it.
@@ -618,7 +620,11 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			/* global temp table is same as local temp table */
+			relpersistence = get_rel_persistence(rte->relid);
+
+			if (relpersistence == RELPERSISTENCE_TEMP ||
+				relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b44efd6..14dbaaf 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6307,7 +6307,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
+	/* global temp table is same as local temp table */
 	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index d82fc5a..93c6d97 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -28,6 +28,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temp table */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(indexRelation->rd_node.relNode))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6676412..0685c1c 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2585,6 +2585,11 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("materialized views must not use temporary tables or views")));
 
+		if (is_query_using_gtt(query))
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("materialized views must not use global temporary tables or views")));
+
 		/*
 		 * A materialized view would either need to save parameters for use in
 		 * maintaining/loading the data or prohibit them entirely.  The latter
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 96e7fdb..30f585c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3290,17 +3290,11 @@ OptTemp:	TEMPORARY					{ $$ = RELPERSISTENCE_TEMP; }
 			| LOCAL TEMP				{ $$ = RELPERSISTENCE_TEMP; }
 			| GLOBAL TEMPORARY
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
-					$$ = RELPERSISTENCE_TEMP;
+					$$ = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
-					$$ = RELPERSISTENCE_TEMP;
+					$$ = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED					{ $$ = RELPERSISTENCE_UNLOGGED; }
 			| /*EMPTY*/					{ $$ = RELPERSISTENCE_PERMANENT; }
@@ -11594,19 +11588,13 @@ OptTempTableName:
 				}
 			| GLOBAL TEMPORARY opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED opt_table qualified_name
 				{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b875a50..123945b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 973eab6..5b72e55 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index e3a43d3..ca460ca 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2087,6 +2087,11 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* autovacuum skip vacuum global temp table */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2153,7 +2158,9 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		/* autovacuum skip vacuum global temp table */
+		if (classForm->relpersistence == RELPERSISTENCE_TEMP || 
+			classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 5880054..98f7b43 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -52,6 +53,7 @@
 #include "utils/rel.h"
 #include "utils/resowner_private.h"
 #include "utils/timestamp.h"
+#include "utils/guc.h"
 
 
 /* Note: these two macros only work on shared buffers, not local ones! */
@@ -2809,6 +2811,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(relation->rd_node.relNode))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d5..7adb969 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -21,6 +21,7 @@
 #include "access/nbtree.h"
 #include "access/subtrans.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, active_gtt_shared_hash_size());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* global temporary table */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 4a5b26c..9754168 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -62,6 +62,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -4087,3 +4088,77 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temp table.
+ */
+int
+list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct *arrayP = procArray;
+	TransactionId result = InvalidTransactionId;
+	int			index;
+	int			flags = 0;
+	int			i = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+		*n = 0;
+	}
+
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		volatile PGXACT *pgxact = &allPgXact[pgprocno];
+
+		if (pgxact->vacuumFlags & flags)
+			continue;
+
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->session_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->session_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->session_gtt_frozenxid, result))
+				result = proc->session_gtt_frozenxid;
+
+			if (max_size > 0)
+			{
+				pids[i] = proc->pid;
+				xids[i] = proc->session_gtt_frozenxid;
+				i++;
+			}
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (max_size > 0)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index eb321f7..3893cef 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 8406644..0d99321 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -1008,6 +1008,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 0be26fe..c169c99 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -112,6 +112,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -4570,12 +4571,25 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -4700,15 +4714,27 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6090,6 +6116,7 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	{
 		/* Simple variable --- look to stats for the underlying table */
 		RangeTblEntry *rte = planner_rt_fetch(index->rel->relid, root);
+		char	rel_persistence = get_rel_persistence(rte->relid);
 
 		Assert(rte->rtekind == RTE_RELATION);
 		relid = rte->relid;
@@ -6107,6 +6134,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6118,6 +6152,8 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	}
 	else
 	{
+		char	rel_persistence = get_rel_persistence(index->indexoid);
+
 		/* Expression --- maybe there are stats for the index itself */
 		relid = index->indexoid;
 		colnum = 1;
@@ -6133,6 +6169,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7046,6 +7089,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		/* attempt to lookup stats in relation for this index column */
 		if (attnum != 0)
 		{
+			char	rel_persistence = get_rel_persistence(rte->relid);
+
 			/* Simple variable -- look to stats for the underlying table */
 			if (get_relation_stats_hook &&
 				(*get_relation_stats_hook) (root, rte, attnum, &vardata))
@@ -7058,6 +7103,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7070,6 +7123,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		}
 		else
 		{
+			char	rel_persistence = get_rel_persistence(index->indexoid);
+
 			/*
 			 * Looks like we've found an expression column in the index. Let's
 			 * see if there's any stats for it.
@@ -7089,6 +7144,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index fb0599f..13f4d8a 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -2878,6 +2879,18 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index ff70326..1cf7063 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -63,6 +63,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1127,6 +1128,24 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1181,6 +1200,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			gtt_fix_index_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -2220,6 +2240,8 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		gtt_fix_index_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3316,6 +3338,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3430,6 +3456,9 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
 
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+		elog(ERROR, "global temp table does not allow setting new relfilenode");
+
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
@@ -3470,7 +3499,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 				/* handle these directly, at least for now */
 				SMgrRelation srel;
 
-				srel = RelationCreateStorage(newrnode, persistence);
+				srel = RelationCreateStorage(newrnode, persistence, relation);
 				smgrclose(srel);
 			}
 			break;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 464f264..0173156 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -140,6 +140,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temp table feature.
+ * global temp table define can still storage in catalog
+ * just can not use.
+ * num > 0 means database can management num active global temp table.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2047,6 +2059,15 @@ static struct config_bool ConfigureNamesBool[] =
 static struct config_int ConfigureNamesInt[] =
 {
 	{
+		{"max_active_global_temporary_table", PGC_POSTMASTER, UNGROUPED,
+			gettext_noop("max active global temporary table."),
+			NULL
+		},
+		&max_active_gtt,
+		DEFAULT_NUM_ACTIVE_GTT, MIN_NUM_ACTIVE_GTT, MAX_NUM_ACTIVE_GTT,
+		NULL, NULL, NULL
+	},
+	{
 		{"archive_timeout", PGC_SIGHUP, WAL_ARCHIVING,
 			gettext_noop("Forces a switch to the next WAL file if a "
 						 "new file has not been started within N seconds."),
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ef15390..a0f898a 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15599,6 +15599,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	{
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
+		char		*table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15650,9 +15651,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 			binary_upgrade_set_pg_class_oids(fout, q,
 											 tbinfo->dobj.catId.oid, false);
 
+		if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED)
+			table_type = "UNLOGGED ";
+		else if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			table_type = "GLOBAL TEMPORARY ";
+		else
+			table_type = "";
+
 		appendPQExpBuffer(q, "CREATE %s%s %s",
-						  tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ?
-						  "UNLOGGED " : "",
+						  table_type,
 						  reltypename,
 						  qualrelname);
 
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 5f9a102..3e40297 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -84,7 +84,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -157,7 +157,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 7e524ea..371168f 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -441,22 +443,46 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 	 * output, so we have to copy that system table.  It's easiest to do that
 	 * by treating it as a user table.
 	 */
-	snprintf(query + strlen(query), sizeof(query) - strlen(query),
-			 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
-			 "  SELECT c.oid, 0::oid, 0::oid "
-			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
-			 "         ON c.relnamespace = n.oid "
-			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
-	/* exclude possible orphaned temp tables */
-			 "    ((n.nspname !~ '^pg_temp_' AND "
-			 "      n.nspname !~ '^pg_toast_temp_' AND "
-			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
-			 "                        'binary_upgrade', 'pg_toast') AND "
-			 "      c.oid >= %u::pg_catalog.oid) OR "
-			 "     (n.nspname = 'pg_catalog' AND "
-			 "      relname IN ('pg_largeobject') ))), ",
-			 FirstNormalObjectId);
+	if (skip_gtt)
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude global temp tables */
+				 "    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
+	else
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
 
 	/*
 	 * Add a CTE that collects OIDs of toast tables belonging to the tables
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 70194eb..8dd9e66 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -411,7 +411,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -642,7 +642,9 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -653,7 +655,9 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index b156b51..7b888ff 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -391,7 +391,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index f3c7eb9..28134e2 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3723,7 +3723,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b6b08d0..16377e6 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1016,6 +1016,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2378,6 +2380,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2586,6 +2591,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 78b33b2..4be89f6 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -168,6 +168,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 07a86c7..b3128d9 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5507,6 +5507,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4191',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4192',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{relid,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4193',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4194',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 048003c..af48cdf 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -19,7 +19,7 @@
 #include "storage/smgr.h"
 #include "utils/relcache.h"
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationTruncate(Relation rel, BlockNumber nblocks);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..3afd71d
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,43 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid);
+extern bool is_other_backend_use_gtt(RelFileNode node);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(RelFileNode node);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Relation relation,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void gtt_force_enable_index(Relation index);
+extern void gtt_fix_index_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 3f88683..f870e9a 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 8fda8e4..172eae9 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_TBM,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_SXACT,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index d217801..8adde87 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -117,6 +117,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId session_gtt_frozenxid;	/* session level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ce93ace..0f7262e 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -281,6 +281,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 44ed04d..6b6d2da 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	struct SMgrRelationData *rd_smgr;	/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -277,6 +277,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -535,11 +536,13 @@ typedef struct ViewOptions
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -547,6 +550,7 @@ typedef struct ViewOptions
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -559,6 +563,14 @@ typedef struct ViewOptions
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is either local temp relation of this session
+ * 		or global temp relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -602,6 +614,17 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..50ca9ac
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,7 @@
+reset search_path;
+drop schema gtt cascade;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..f11bf5e
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,315 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+ n 
+---
+ 9
+(1 row)
+
+commit;
+-- 0 row
+select * from gtt6;
+ n 
+---
+(0 rows)
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+ERROR:  cannot create indexes on global temporary tables using concurrent mode
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+ERROR:  not support cluster global temporary tables yet
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ok
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  can not defeine global temp table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test;
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR:  insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count 
+-------
+     1
+(1 row)
+
+select count(*) from orders;
+ count 
+-------
+     0
+(1 row)
+
+-- ERROR
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 1)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 300)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 19 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.gtt6
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to table gtt_function.gt1
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..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..366518d
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,161 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          114688 |                 614400
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |            49152 |         49152 |                  49152
+(2 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..9fe5fd4
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,9 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..0e21b30
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,76 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+(1 row)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+(1 row)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index c730461..8a39594 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1349,6 +1349,93 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d2b17dd..80e577f 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..f3cf710
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,6 @@
+
+
+reset search_path;
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..8d6f760
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,209 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+commit;
+-- 0 row
+select * from gtt6;
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ok
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test;
+
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ERROR
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d7d81de
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,42 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..5203c2b
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,76 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..2f4d883
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,16 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..f041892
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,42 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
#168Prabhat Sahu
prabhat.sahu@enterprisedb.com
In reply to: 曾文旌(义从) (#167)
Re: [Proposal] Global temporary tables

On Tue, Mar 3, 2020 at 2:11 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com> wrote:

I fixed in global_temporary_table_v16-pg13.patch.

Thank you Wenjing for the patch.
Now we are getting corruption with GTT with below scenario.

postgres=# CREATE GLOBAL TEMPORARY TABLE gtt1(c1 bigint, c2 bigserial) on
commit delete rows;
CREATE TABLE
postgres=# CREATE GLOBAL TEMPORARY TABLE gtt2(c1 bigint, c2 bigserial) on
commit preserve rows;
CREATE TABLE
postgres=# \q

[edb@localhost bin]$ echo "1

2
3
"> t.dat

[edb@localhost bin]$ ./psql postgres
psql (13devel)
Type "help" for help.

postgres=# \copy gtt1(c1) from 't.dat' with csv;
ERROR: could not read block 0 in file "base/13585/t3_16384": read only 0
of 8192 bytes
CONTEXT: COPY gtt1, line 1: "1"

postgres=# \copy gtt2(c1) from 't.dat' with csv;
ERROR: could not read block 0 in file "base/13585/t3_16390": read only 0
of 8192 bytes
CONTEXT: COPY gtt2, line 1: "1"

NOTE: We end with such corruption for "bigserial/smallserial/serial"
datatype columns.

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

#169tushar
tushar.ahuja@enterprisedb.com
In reply to: 曾文旌(义从) (#167)
Re: [Proposal] Global temporary tables

On 3/3/20 2:10 PM, 曾文旌(义从) wrote:

I fixed in global_temporary_table_v16-pg13.patch.

Thanks Wenjing. The reported  issue is fixed now  but  there is an
another similar  scenario -
if we enable 'on_commit_delete_rows' to true using alter command then
getting same issue i.e rows are not removing after commit.

x=# create global  temp table foo123(n int) with
(on_commit_delete_rows='false');
CREATE TABLE
x=#
x=# alter table foo123 set ( on_commit_delete_rows='true');
ALTER TABLE
x=#
x=# insert into foo123 values (1);
INSERT 0 1
x=# select * from foo123;   <- row should get removed.
 n
---
 1
(1 row)

--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company

#170tushar
tushar.ahuja@enterprisedb.com
In reply to: 曾文旌(义从) (#167)
Re: [Proposal] Global temporary tables

On 3/3/20 2:10 PM, 曾文旌(义从) wrote:

I fixed in global_temporary_table_v16-pg13.patch.

Please refer this scenario -

--Connect to psql -

postgres=# alter system set max_active_global_temporary_table =1;
ALTER SYSTEM

--restart the server (./pg_ctl -D data restart)

--create global temp table

postgres=# create global temp  table ccc1  (c int);
CREATE TABLE

--Try to Create another global temp table

*postgres=# create global temp  table ccc2  (c int);**
**WARNING:  relfilenode 13589/1663/19063 not exist in gtt shared hash
when forget**
**ERROR:  out of shared memory**
**HINT:  You might need to increase max_active_gtt.**
*
postgres=# show max_active_gtt;
ERROR:  unrecognized configuration parameter "max_active_gtt"
postgres=#
postgres=# show max_active_global_temporary_table ;
 max_active_global_temporary_table
-----------------------------------
 1
(1 row)

postgres=#

I cannot find "max_active_gtt"  GUC . I think you are referring to 
"max_active_global_temporary_table" here ?

also , would be great  if we can make this error message  user friendly
like  - "max connection reached"  rather than memory error

--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company

#171Robert Haas
robertmhaas@gmail.com
In reply to: tushar (#170)
Re: [Proposal] Global temporary tables

On Thu, Mar 5, 2020 at 9:19 AM tushar <tushar.ahuja@enterprisedb.com> wrote:

WARNING: relfilenode 13589/1663/19063 not exist in gtt shared hash when forget
ERROR: out of shared memory
HINT: You might need to increase max_active_gtt.

also , would be great if we can make this error message user friendly like - "max connection reached" rather than memory error

That would be nice, but the bigger problem is that the WARNING there
looks totally unacceptable. It's looks like it's complaining of some
internal issue (i.e. a bug or corruption) and the grammar is poor,
too.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#172曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Prabhat Sahu (#168)
1 attachment(s)
Re: [Proposal] Global temporary tables

2020年3月4日 下午3:49,Prabhat Sahu <prabhat.sahu@enterprisedb.com> 写道:

On Tue, Mar 3, 2020 at 2:11 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> wrote:

I fixed in global_temporary_table_v16-pg13.patch.

Thank you Wenjing for the patch.
Now we are getting corruption with GTT with below scenario.

postgres=# CREATE GLOBAL TEMPORARY TABLE gtt1(c1 bigint, c2 bigserial) on commit delete rows;
CREATE TABLE
postgres=# CREATE GLOBAL TEMPORARY TABLE gtt2(c1 bigint, c2 bigserial) on commit preserve rows;
CREATE TABLE
postgres=# \q

[edb@localhost bin]$ echo "1

2
3
"> t.dat

[edb@localhost bin]$ ./psql postgres
psql (13devel)
Type "help" for help.

postgres=# \copy gtt1(c1) from 't.dat' with csv;
ERROR: could not read block 0 in file "base/13585/t3_16384": read only 0 of 8192 bytes
CONTEXT: COPY gtt1, line 1: "1"

postgres=# \copy gtt2(c1) from 't.dat' with csv;
ERROR: could not read block 0 in file "base/13585/t3_16390": read only 0 of 8192 bytes
CONTEXT: COPY gtt2, line 1: "1"

NOTE: We end with such corruption for "bigserial/smallserial/serial" datatype columns.

Thanks for review.

I fixed this issue in global_temporary_table_v17-pg13.patch

Wenjing

Show quoted text

--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

Attachments:

global_temporary_table_v17-pg13.patchapplication/octet-stream; name=global_temporary_table_v17-pg13.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index c3d45c7..3f9e875 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -168,6 +168,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * For global temp table only
+	 * use ShareUpdateExclusiveLock for ensure safety
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temp table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},	
 	/* list terminator */
 	{{NULL}}
 };
@@ -1496,6 +1509,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1596,13 +1611,18 @@ build_reloptions(Datum reloptions, bool validate,
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	/*
 	 * There are no options for partitioned tables yet, but this is able to do
 	 * some validation.
 	 */
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index dd975b1..1610e7d 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1013,7 +1013,9 @@ gistGetFakeLSN(Relation rel)
 {
 	static XLogRecPtr counter = FirstNormalUnloggedLSN;
 
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	/* global temp is same as local temp table */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 4871b7f..16b00c9 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -149,7 +149,9 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	/* global temp table is same as local temp table */
+	if (!(index->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		index->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ca52846..f0153cf 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -598,7 +598,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -651,7 +651,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 03c43ef..2674132 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -62,6 +62,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -399,9 +400,9 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
 	Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
-	/* not every AM requires these to be valid, but heap does */
-	Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
-	Assert(MultiXactIdIsValid(onerel->rd_rel->relminmxid));
+	/* not every AM requires these to be valid, but regular heap does */
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ MultiXactIdIsValid(onerel->rd_rel->relminmxid));
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 39b8f17..38b46d0 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -601,6 +602,10 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(rel->rd_node.relNode))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 4361568..08ef041 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6348,6 +6348,10 @@ StartupXLOG(void)
 	else
 		recoveryTargetTLI = ControlFile->checkPointCopy.ThisTimeLineID;
 
+	/* clean temp relation files */
+	if (max_active_gtt > 0)
+		RemovePgTempFiles();
+
 	/*
 	 * Check for signal files, and if so set up state for offline recovery
 	 */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index f8f0b48..dc8bbb1 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -42,6 +42,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 7d6acae..3be8d63 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -393,7 +393,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/* global temp table is same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 9d9e915..1c50263 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -427,7 +429,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -957,6 +959,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -995,8 +998,18 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/* global temp table not remember transaction info in catalog */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1358,6 +1371,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1938,6 +1952,14 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/* We allow to drop global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(rel->rd_node))
+			elog(ERROR, "can not drop relation %s when other backend attached this global temp table",
+						RelationGetRelationName(rel));
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3165,7 +3187,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3177,7 +3199,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3223,8 +3245,13 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		/* truncate global temp table only need RowExclusiveLock */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3257,6 +3284,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3265,23 +3293,37 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (!gtt_storage_attached(rel->rd_node.relNode))
+			return;
+
+		/*
+		 * Truncate global temp table only need RowExclusiveLock
+		 */
+		lockmode = RowExclusiveLock;
+	}
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(rel, 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 7223679..2a0144e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -52,6 +52,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -877,6 +878,19 @@ index_create(Relation heapRelation,
 						indexRelationName, RelationGetRelationName(heapRelation))));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temp table use concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot create indexes on global temporary tables using concurrent mode")));
+
+		/* if global temp table not init storage, then skip build index */
+		if (!gtt_storage_attached(heapRelation->rd_node.relNode))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -2061,6 +2075,14 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/* We allow to drop index on global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation))
+	{
+		if (is_other_backend_use_gtt(userHeapRelation->rd_node))
+			elog(ERROR, "can not drop index %s when other backend attached this global temp table.",
+						RelationGetRelationName(userHeapRelation));
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2667,6 +2689,11 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	/* update index stats into localhash and rel_rd_rel for global temp table */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		is_gtt = true;
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2752,21 +2779,35 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
+		if (is_gtt)
+			rel->rd_rel->relpages = (int32) relpages;
+		else if (rd_rel->relpages != (int32) relpages)
 		{
 			rd_rel->relpages = (int32) relpages;
 			dirty = true;
 		}
-		if (rd_rel->reltuples != (float4) reltuples)
+
+		if (is_gtt)
+			rel->rd_rel->reltuples = (float4) reltuples;
+		else if (rd_rel->reltuples != (float4) reltuples)
 		{
 			rd_rel->reltuples = (float4) reltuples;
 			dirty = true;
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+
+		if (is_gtt)
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+		else if (rd_rel->relallvisible != (int32) relallvisible)
 		{
 			rd_rel->relallvisible = (int32) relallvisible;
 			dirty = true;
 		}
+
+		if (is_gtt)
+		{
+			up_gtt_relstats(rel, relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
+		}
 	}
 
 	/*
@@ -2880,6 +2921,15 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, index, val);
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		if (!gtt_storage_attached(indexRelation->rd_node.relNode))
+		{
+			gtt_force_enable_index(indexRelation);
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3475,6 +3525,15 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 				 errmsg("cannot reindex temporary tables of other sessions")));
 
 	/*
+	 * Because global temp table cannot change relfilenode
+	 * no support reindex on global temp table yet.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(iRel))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot reindex global temporary tables")));
+
+	/*
 	 * Also check for active uses of the index in the current transaction; we
 	 * don't want to reindex underneath an open indexscan.
 	 */
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 5ff7824..9b58f2a 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -647,6 +647,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		/* global temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp relations in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index fddfbf1..2982bc7 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -26,6 +26,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -75,7 +76,7 @@ static PendingRelDelete *pendingDeletes = NULL; /* head of linked list */
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -85,6 +86,8 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		/* global temp table use same storage strategy as local temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -117,6 +120,10 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/* remember global temp table storage info to localhash */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel)
+		remember_gtt_storage_info(rnode, rel);
+
 	return srel;
 }
 
@@ -486,8 +493,15 @@ smgrDoPendingDeletes(bool isCommit)
 		smgrdounlinkall(srels, nrels, false);
 
 		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* clean global temp table flags when transaction commit or rollback */
+			if (SmgrIsTemp(srels[i]) && 
+				gtt_storage_attached(srels[i]->smgr_rnode.node.relNode))
+				forget_gtt_storage_info(srels[i]->smgr_rnode.node.relNode, isCommit);
+		}
+
 		pfree(srels);
 	}
 }
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..04f7fcc
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1261 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  code to create and destroy physical storage for global temparary table
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_relstats_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+typedef struct gtt_ctl_data
+{
+	LWLock			lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct
+{
+	RelFileNode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+	Oid			relid;
+
+	Oid			spcnode;
+	/* pg_class stat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+	char		relkind;
+	bool		on_commit_delete;
+
+	/* pg_statistic */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(RelFileNode rnode);
+static void gtt_storage_checkout(RelFileNode rnode, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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_global_temporary_table.")));
+	}
+
+	if (found == false)
+	{
+		int			wordnum;
+
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(RelFileNode rnode, bool skiplock, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (!skiplock)
+		LWLockAcquire(&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);
+
+		if (isCommit)
+		{
+			elog(WARNING, "relfilenode %u/%u/%u not exist in gtt shared hash when forget",
+						rnode.dbNode, rnode.spcNode, rnode.relNode);
+		}
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+	bms_del_member(entry->map, MyBackendId);
+
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &rnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+
+	if (!skiplock)
+		LWLockRelease(&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;
+	int			natts = 0;
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		/* First time through: initialize the hash table */
+		HASHCTL		ctl;
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_relstats_context =
+			AllocSetContextCreate(CacheMemoryContext,
+							"gtt relstats context",
+							ALLOCSET_DEFAULT_SIZES);
+	}
+
+	/* Look up or create an entry */
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_ENTER, &found);
+
+	if (found)
+	{
+		elog(ERROR, "backend %d relid %u already exists in global temporary table local hash",
+					MyBackendId, relid);
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+	entry->spcnode = rnode.spcNode;
+	entry->relpages = 0;
+	entry->reltuples = 0;
+	entry->relallvisible = 0;
+	entry->relfrozenxid = InvalidTransactionId;
+	entry->relminmxid = InvalidMultiXactId;
+	entry->relkind = rel->rd_rel->relkind;
+	entry->on_commit_delete = false;
+	entry->natts = 0;
+	entry->attnum = NULL;
+	entry->att_stat_tups = NULL;
+
+	natts = RelationGetNumberOfAttributes(rel);
+	entry->attnum = palloc0(sizeof(int) * natts);
+	entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+	entry->natts = natts;
+	/* only heap contain transaction information */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+		{
+			entry->on_commit_delete = true;
+			register_on_commit_action(rel->rd_node.relNode, ONCOMMIT_DELETE_ROWS);
+		}
+
+		entry->relfrozenxid = RecentXmin;
+		entry->relminmxid = GetOldestMultiXactId();
+		insert_gtt_relfrozenxid_to_ordered_list(entry->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	if (entry->relkind == RELKIND_RELATION ||
+		entry->relkind == RELKIND_SEQUENCE)
+	{
+		gtt_storage_checkin(rnode);
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry)
+	{
+		int		i;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			Assert(TransactionIdIsNormal(entry->relfrozenxid) || !isCommit);
+			if (TransactionIdIsValid(entry->relfrozenxid))
+			{
+				remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+				set_gtt_session_relfrozenxid();
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			RelFileNode rnode;
+			rnode.spcNode = entry->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = entry->relid;
+			gtt_storage_checkout(rnode, false, isCommit);
+		}
+
+		for (i = 0; i < entry->natts; i++)
+		{
+			if (entry->att_stat_tups[i])
+			{
+				heap_freetuple(entry->att_stat_tups[i]);
+				entry->att_stat_tups[i] = NULL;
+			}
+		}
+
+		if (entry->attnum)
+			pfree(entry->attnum);
+
+		if (entry->att_stat_tups)
+			pfree(entry->att_stat_tups);
+	}
+
+	hash_search(gtt_storage_local_hash,
+			(void *) &(relid), HASH_REMOVE, NULL);
+
+	return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool found;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (gtt_storage_local_hash == NULL)
+		return false;
+
+	hash_search(gtt_storage_local_hash,
+			(void *) &(relid), HASH_FIND, &found);
+
+	return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS				status;
+	gtt_local_hash_entry		*entry;
+	int			nrels = 0,
+				maxrels = 0;
+	SMgrRelation	*srels = NULL;
+	RelFileNode		*rnodes = NULL;
+	char			*relkinds = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		SMgrRelation srel;
+		RelFileNode rnode;
+
+		rnode.spcNode = entry->spcnode;
+		rnode.dbNode = MyDatabaseId;
+		rnode.relNode = entry->relid;
+
+		srel = smgropen(rnode, MyBackendId);
+
+		/* allocate the initial array, or extend it, if needed */
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			srels = palloc(sizeof(SMgrRelation) * maxrels);
+			rnodes = palloc(sizeof(RelFileNode) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+			rnodes = repalloc(rnodes, sizeof(RelFileNode) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		srels[nrels] = srel;
+		rnodes[nrels] = rnode;
+		relkinds[nrels] = entry->relkind;
+		nrels++;
+	}
+
+	if (nrels > 0)
+	{
+		int i;
+
+		smgrdounlinkall(srels, nrels, false);
+
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+		for (i = 0; i < nrels; i++)
+		{
+			smgrclose(srels[i]);
+			if (relkinds[i] == RELKIND_RELATION ||
+				relkinds[i] == RELKIND_SEQUENCE)
+				gtt_storage_checkout(rnodes[i], true, false);
+		}
+		LWLockRelease(&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.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (gtt_storage_local_hash == NULL)
+		return false;
+
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_FIND, &found);
+
+	if (!found)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	if (relpages)
+		*relpages = entry->relpages;
+
+	if (reltuples)
+		*reltuples = entry->reltuples;
+
+	if (relallvisible)
+		*relallvisible = entry->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = entry->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = entry->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	MemoryContext			oldcontext;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+						(void *) &reloid, HASH_FIND, &found);
+
+	if (!found)
+		return;
+
+	/* todo */
+	if (entry->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	Assert(entry->relid == reloid);
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == 0)
+		{
+			entry->attnum[i] = attnum;
+			break;
+		}
+		else if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < entry->natts);
+	Assert(entry->att_stat_tups[i] == NULL);
+	entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+						(void *) &reloid, HASH_FIND, &found);
+
+	if (!found)
+		return NULL;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			return entry->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int			i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	HeapTuple	tuple;
+	Relation	rel = NULL;
+	int		attnum = PG_GETARG_INT32(1);
+	Oid		reloid = PG_GETARG_OID(0);
+	char		rel_persistence;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	  tupdesc;
+	MemoryContext oldcontext;
+	Tuplestorestate *tupstore;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = table_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+
+	table_close(rel, NoLock);
+	table_close(pg_tatistic, AccessShareLock);
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate	*tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Relation	rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	if (get_gtt_relstats(reloid,
+						&relpages, &reltuples, &relallvisible,
+						&relfrozenxid, &relminmxid))
+	{
+		Datum	values[5];
+		bool	isnull[5];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = Int32GetDatum(relpages);
+		values[1] = Float4GetDatum((float4)reltuples);
+		values[2] = Int32GetDatum(relallvisible);
+		values[3] = UInt32GetDatum(relfrozenxid);
+		values[4] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+
+	tuplestore_donestoring(tupstore);
+	table_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(rel->rd_node);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	table_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(indexOid));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid relOid = index->rd_index->indrelid;
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	if (gtt_storage_attached(relOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int			i;
+	Oid			toastrelid;
+
+	if (operation != CMD_INSERT)
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_attached(relation->rd_node.relNode))
+		return;
+
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+
+		index_build(relation, index, info, true, false);
+	}
+
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid 		indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo 	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index b8a3f46..491db16 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM 
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 924ef37..7efc5a9 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -184,6 +185,13 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(onerel->rd_node.relNode))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -586,14 +594,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 		 * pg_statistic for columns we didn't process, we leave them alone.)
 		 */
 		update_attstats(RelationGetRelid(onerel), inh,
-						attr_cnt, vacattrstats);
+						attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
 
 		for (ind = 0; ind < nindexes; ind++)
 		{
 			AnlIndexData *thisdata = &indexdata[ind];
 
 			update_attstats(RelationGetRelid(Irel[ind]), false,
-							thisdata->attr_cnt, thisdata->vacattrstats);
+							thisdata->attr_cnt, thisdata->vacattrstats,
+							RelationGetRelPersistence(Irel[ind]));
 		}
 
 		/*
@@ -1456,7 +1465,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
  *		by taking a self-exclusive lock on the relation in analyze_rel().
  */
 static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
 {
 	Relation	sd;
 	int			attno;
@@ -1558,31 +1567,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/* Update column statistic to localhash, not catalog */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 11ce1bb..552fd87 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -391,6 +391,12 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/* not support cluster global temp table yet */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("not support cluster global temporary tables yet")));
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index e79ede4..078694d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -27,6 +27,7 @@
 #include "catalog/dependency.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -1062,7 +1063,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, stmt->filename, stmt->is_program,
@@ -2840,6 +2841,8 @@ CopyFrom(CopyState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	estate->es_result_relations = resultRelInfo;
 	estate->es_num_result_relations = 1;
 	estate->es_result_relation_info = resultRelInfo;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 3f3a89f..928c01e 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2586,6 +2586,16 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 			!isTempNamespace(classtuple->relnamespace))
 			continue;
 
+		/* not support reindex on global temp table, so skip it */
+		if (classtuple->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			ereport(WARNING,
+				(errmsg("global temp table \"%s.%s\" skip reindexed",
+					get_namespace_name(get_rel_namespace(relid)),
+					get_rel_name(relid))));
+			continue;
+		}
+
 		/* Check user/system classification, and optionally skip */
 		if (objectKind == REINDEX_OBJECT_SYSTEM &&
 			!IsSystemClass(relid, classtuple))
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index d8cafc4..deecde8 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -96,7 +96,8 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 	 * transaction.
 	 */
 	relpersistence = get_rel_persistence(relid);
-	if (relpersistence == RELPERSISTENCE_TEMP)
+	if (relpersistence == RELPERSISTENCE_TEMP ||
+		relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
 
 	/* Check permissions. */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..fada1ac 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -94,7 +96,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -222,7 +225,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* now initialize the sequence's data */
 	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -327,7 +330,7 @@ ResetSequence(Oid seq_relid)
 	/*
 	 * Insert the modified tuple into the new storage file.
 	 */
-	fill_seq_with_data(seq_rel, tuple);
+	fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -340,17 +343,21 @@ ResetSequence(Oid seq_relid)
  * Initialize a sequence's relation with the specified tuple as content
  */
 static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 	sequence_magic *sm;
 	OffsetNumber offnum;
+	bool lockBuffer = false;
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ReadBuffer(rel, P_NEW);
-	Assert(BufferGetBlockNumber(buf) == 0);
+	if (buf == InvalidBuffer)
+	{
+		buf = ReadBuffer(rel, P_NEW);
+		Assert(BufferGetBlockNumber(buf) == 0);
+		lockBuffer = true;
+	}
 
 	page = BufferGetPage(buf);
 
@@ -360,7 +367,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* Now insert sequence tuple */
 
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+	if (lockBuffer)
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +418,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	END_CRIT_SECTION();
 
-	UnlockReleaseBuffer(buf);
+	if (lockBuffer)
+		UnlockReleaseBuffer(buf);
 }
 
 /*
@@ -502,7 +511,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * Insert the modified tuple into the new storage file.
 		 */
-		fill_seq_with_data(seqrel, newdatatuple);
+		fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
 	}
 
 	/* process OWNED BY if given */
@@ -611,7 +620,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +945,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1154,6 +1163,13 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(seqrel->rd_node.relNode))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1954,3 +1970,46 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+void
+gtt_init_seq(Relation rel)
+{
+	/* Initialize sequence for global temporary tables */
+	Datum		value[SEQ_COL_LASTCOL] = {0};
+	bool		null[SEQ_COL_LASTCOL] = {false};
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 7a13b97..536083e 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -553,6 +554,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 								  Relation partitionTbl);
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static OnCommitAction gtt_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -598,6 +600,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -608,8 +611,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Check consistency of arguments
 	 */
+	/* global temp table same as local temp table */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !(stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+			 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -639,7 +644,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	/* global temp table same as local temp table */
+	if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+		 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -740,6 +747,55 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* global temp table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* check parent table*/
+		if (inheritOids)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			table_close(relation, NoLock);
+		}
+
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "can not defeine global temp table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temp table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1824,7 +1880,8 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
 		 * table or the current physical file to be thrown away anyway.
 		 */
 		if (rel->rd_createSubid == mySubid ||
-			rel->rd_newRelfilenodeSubid == mySubid)
+			rel->rd_newRelfilenodeSubid == mySubid ||
+			RELATION_IS_GLOBAL_TEMP(rel))
 		{
 			/* Immediate, non-rollbackable truncation is OK */
 			heap_truncate_one_rel(rel);
@@ -3567,6 +3624,14 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(rel->rd_node))
+			elog(ERROR, "can not alter table %s when other backend attached this global temp table",
+						RelationGetRelationName(rel));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -8179,6 +8244,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -12647,6 +12718,12 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+			elog(ERROR, "The global temp table does not allow modification on commit action.");
+	}
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -12849,6 +12926,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temp table");
+
 	/*
 	 * No work if no change in tablespace.
 	 */
@@ -13221,7 +13301,7 @@ index_copy_data(Relation rel, RelFileNode newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
@@ -14628,7 +14708,9 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
 	 */
 	switch (rel->rd_rel->relpersistence)
 	{
+		/* global temp table same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -17220,3 +17302,28 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
 		table_close(rel, NoLock);
 	}
 }
+
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			if (defGetBoolean(def))
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index d625d17..f58c44d 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/vacuum.h"
@@ -1217,6 +1218,17 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	 /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		is_gtt = true;
+		up_gtt_relstats(relation,
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1230,17 +1242,26 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relpages = (int32) num_pages;
+	else if (pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (is_gtt)
+		relation->rd_rel->reltuples = (float4) num_tuples;
+	else if (pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	else if (pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1285,7 +1306,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1296,7 +1318,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1394,6 +1417,10 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/* global temp table relstats not in pg_class */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1451,6 +1478,25 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/*
+	 * Global temp table get frozenxid from MyProc
+	 * to avoid the vacuum truncate clog that gtt need.
+	 */
+	if (max_active_gtt > 0)
+	{
+		TransactionId oldest_gtt_frozenxid =
+			list_all_session_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid) &&
+			TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+		{
+			ereport(WARNING,
+				(errmsg("global temp table oldest FrozenXid is far in the past"),
+				 errhint("please truncate them or kill those sessions that use them.")));
+			newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1778,6 +1824,18 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(onerel->rd_node.relNode))
+	{
+		ereport(WARNING,
+				(errmsg("skipping vacuum empty global temp table \"%s\"",
+						RelationGetRelationName(onerel))));
+		relation_close(onerel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 28130fb..dcb4a0b 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index c13b1d3..253054d 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
@@ -543,6 +544,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	init_gtt_storage(mtstate->operation, leaf_part_rri);
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d71c0a4..0900907 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,6 +42,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -2372,6 +2373,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			ExecOpenIndices(resultRelInfo,
 							node->onConflictAction != ONCONFLICT_NONE);
 
+		init_gtt_storage(operation, resultRelInfo);
+
 		/*
 		 * If this is an UPDATE and a BEFORE UPDATE trigger is present, the
 		 * trigger itself might modify the partition-key values. So arrange
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 905bbe7..170963d 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -591,6 +591,8 @@ static void
 set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 						  RangeTblEntry *rte)
 {
+	char relpersistence;
+
 	/*
 	 * The flag has previously been initialized to false, so we can just
 	 * return if it becomes clear that we can't safely set it.
@@ -618,7 +620,11 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			/* global temp table is same as local temp table */
+			relpersistence = get_rel_persistence(rte->relid);
+
+			if (relpersistence == RELPERSISTENCE_TEMP ||
+				relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b44efd6..14dbaaf 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6307,7 +6307,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
+	/* global temp table is same as local temp table */
 	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index d82fc5a..93c6d97 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -28,6 +28,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temp table */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(indexRelation->rd_node.relNode))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6676412..0685c1c 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2585,6 +2585,11 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("materialized views must not use temporary tables or views")));
 
+		if (is_query_using_gtt(query))
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("materialized views must not use global temporary tables or views")));
+
 		/*
 		 * A materialized view would either need to save parameters for use in
 		 * maintaining/loading the data or prohibit them entirely.  The latter
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 96e7fdb..30f585c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3290,17 +3290,11 @@ OptTemp:	TEMPORARY					{ $$ = RELPERSISTENCE_TEMP; }
 			| LOCAL TEMP				{ $$ = RELPERSISTENCE_TEMP; }
 			| GLOBAL TEMPORARY
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
-					$$ = RELPERSISTENCE_TEMP;
+					$$ = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
-					$$ = RELPERSISTENCE_TEMP;
+					$$ = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED					{ $$ = RELPERSISTENCE_UNLOGGED; }
 			| /*EMPTY*/					{ $$ = RELPERSISTENCE_PERMANENT; }
@@ -11594,19 +11588,13 @@ OptTempTableName:
 				}
 			| GLOBAL TEMPORARY opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED opt_table qualified_name
 				{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b875a50..123945b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index af77f18..67d34f0 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index e3a43d3..ca460ca 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2087,6 +2087,11 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* autovacuum skip vacuum global temp table */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2153,7 +2158,9 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		/* autovacuum skip vacuum global temp table */
+		if (classForm->relpersistence == RELPERSISTENCE_TEMP || 
+			classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 5880054..98f7b43 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -52,6 +53,7 @@
 #include "utils/rel.h"
 #include "utils/resowner_private.h"
 #include "utils/timestamp.h"
+#include "utils/guc.h"
 
 
 /* Note: these two macros only work on shared buffers, not local ones! */
@@ -2809,6 +2811,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(relation->rd_node.relNode))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d5..7adb969 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -21,6 +21,7 @@
 #include "access/nbtree.h"
 #include "access/subtrans.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, active_gtt_shared_hash_size());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* global temporary table */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 4a5b26c..9754168 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -62,6 +62,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -4087,3 +4088,77 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temp table.
+ */
+int
+list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct *arrayP = procArray;
+	TransactionId result = InvalidTransactionId;
+	int			index;
+	int			flags = 0;
+	int			i = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+		*n = 0;
+	}
+
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		volatile PGXACT *pgxact = &allPgXact[pgprocno];
+
+		if (pgxact->vacuumFlags & flags)
+			continue;
+
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->session_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->session_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->session_gtt_frozenxid, result))
+				result = proc->session_gtt_frozenxid;
+
+			if (max_size > 0)
+			{
+				pids[i] = proc->pid;
+				xids[i] = proc->session_gtt_frozenxid;
+				i++;
+			}
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (max_size > 0)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index eb321f7..3893cef 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 8406644..0d99321 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -1008,6 +1008,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 0be26fe..c169c99 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -112,6 +112,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -4570,12 +4571,25 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -4700,15 +4714,27 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6090,6 +6116,7 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	{
 		/* Simple variable --- look to stats for the underlying table */
 		RangeTblEntry *rte = planner_rt_fetch(index->rel->relid, root);
+		char	rel_persistence = get_rel_persistence(rte->relid);
 
 		Assert(rte->rtekind == RTE_RELATION);
 		relid = rte->relid;
@@ -6107,6 +6134,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6118,6 +6152,8 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	}
 	else
 	{
+		char	rel_persistence = get_rel_persistence(index->indexoid);
+
 		/* Expression --- maybe there are stats for the index itself */
 		relid = index->indexoid;
 		colnum = 1;
@@ -6133,6 +6169,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7046,6 +7089,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		/* attempt to lookup stats in relation for this index column */
 		if (attnum != 0)
 		{
+			char	rel_persistence = get_rel_persistence(rte->relid);
+
 			/* Simple variable -- look to stats for the underlying table */
 			if (get_relation_stats_hook &&
 				(*get_relation_stats_hook) (root, rte, attnum, &vardata))
@@ -7058,6 +7103,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7070,6 +7123,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		}
 		else
 		{
+			char	rel_persistence = get_rel_persistence(index->indexoid);
+
 			/*
 			 * Looks like we've found an expression column in the index. Let's
 			 * see if there's any stats for it.
@@ -7089,6 +7144,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 3da90cb..97240ff 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -2878,6 +2879,18 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index ff70326..1cf7063 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -63,6 +63,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1127,6 +1128,24 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1181,6 +1200,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			gtt_fix_index_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -2220,6 +2240,8 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		gtt_fix_index_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3316,6 +3338,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3430,6 +3456,9 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
 
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+		elog(ERROR, "global temp table does not allow setting new relfilenode");
+
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
@@ -3470,7 +3499,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 				/* handle these directly, at least for now */
 				SMgrRelation srel;
 
-				srel = RelationCreateStorage(newrnode, persistence);
+				srel = RelationCreateStorage(newrnode, persistence, relation);
 				smgrclose(srel);
 			}
 			break;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index dbecc00..6acf3f3 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -140,6 +140,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temp table feature.
+ * global temp table define can still storage in catalog
+ * just can not use.
+ * num > 0 means database can management num active global temp table.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2047,6 +2059,15 @@ static struct config_bool ConfigureNamesBool[] =
 static struct config_int ConfigureNamesInt[] =
 {
 	{
+		{"max_active_global_temporary_table", PGC_POSTMASTER, UNGROUPED,
+			gettext_noop("max active global temporary table."),
+			NULL
+		},
+		&max_active_gtt,
+		DEFAULT_NUM_ACTIVE_GTT, MIN_NUM_ACTIVE_GTT, MAX_NUM_ACTIVE_GTT,
+		NULL, NULL, NULL
+	},
+	{
 		{"archive_timeout", PGC_SIGHUP, WAL_ARCHIVING,
 			gettext_noop("Forces a switch to the next WAL file if a "
 						 "new file has not been started within N seconds."),
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a12c8d0..58b22ea 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15595,6 +15595,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	{
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
+		char		*table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15646,9 +15647,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 			binary_upgrade_set_pg_class_oids(fout, q,
 											 tbinfo->dobj.catId.oid, false);
 
+		if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED)
+			table_type = "UNLOGGED ";
+		else if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			table_type = "GLOBAL TEMPORARY ";
+		else
+			table_type = "";
+
 		appendPQExpBuffer(q, "CREATE %s%s %s",
-						  tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ?
-						  "UNLOGGED " : "",
+						  table_type,
 						  reltypename,
 						  qualrelname);
 
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 5f9a102..3e40297 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -84,7 +84,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -157,7 +157,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 7e524ea..371168f 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -441,22 +443,46 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 	 * output, so we have to copy that system table.  It's easiest to do that
 	 * by treating it as a user table.
 	 */
-	snprintf(query + strlen(query), sizeof(query) - strlen(query),
-			 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
-			 "  SELECT c.oid, 0::oid, 0::oid "
-			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
-			 "         ON c.relnamespace = n.oid "
-			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
-	/* exclude possible orphaned temp tables */
-			 "    ((n.nspname !~ '^pg_temp_' AND "
-			 "      n.nspname !~ '^pg_toast_temp_' AND "
-			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
-			 "                        'binary_upgrade', 'pg_toast') AND "
-			 "      c.oid >= %u::pg_catalog.oid) OR "
-			 "     (n.nspname = 'pg_catalog' AND "
-			 "      relname IN ('pg_largeobject') ))), ",
-			 FirstNormalObjectId);
+	if (skip_gtt)
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude global temp tables */
+				 "    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
+	else
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
 
 	/*
 	 * Add a CTE that collects OIDs of toast tables belonging to the tables
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 70194eb..8dd9e66 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -411,7 +411,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -642,7 +642,9 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -653,7 +655,9 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 8b90cef..d155205 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -389,7 +389,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index f3c7eb9..28134e2 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3723,7 +3723,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b6b08d0..16377e6 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1016,6 +1016,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2378,6 +2380,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2586,6 +2591,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 78b33b2..4be89f6 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -168,6 +168,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 7fb574f..6f8591c 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5507,6 +5507,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4191',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4192',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{relid,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4193',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4194',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 048003c..af48cdf 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -19,7 +19,7 @@
 #include "storage/smgr.h"
 #include "utils/relcache.h"
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationTruncate(Relation rel, BlockNumber nblocks);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..63f7241
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,43 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, bool isCommit);
+extern bool is_other_backend_use_gtt(RelFileNode node);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(RelFileNode node);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Relation relation,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void gtt_force_enable_index(Relation index);
+extern void gtt_fix_index_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index e2638ab..89a5ce4 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 3f88683..f870e9a 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 8fda8e4..172eae9 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_TBM,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_SXACT,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index d217801..8adde87 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -117,6 +117,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId session_gtt_frozenxid;	/* session level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ce93ace..0f7262e 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -281,6 +281,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 44ed04d..6b6d2da 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	struct SMgrRelationData *rd_smgr;	/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -277,6 +277,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -535,11 +536,13 @@ typedef struct ViewOptions
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -547,6 +550,7 @@ typedef struct ViewOptions
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -559,6 +563,14 @@ typedef struct ViewOptions
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is either local temp relation of this session
+ * 		or global temp relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -602,6 +614,17 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..73d3d7b
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,8 @@
+reset search_path;
+drop schema gtt cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
+drop cascades to table gtt.gtt_with_seq
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..067393b
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,322 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+ n 
+---
+ 9
+(1 row)
+
+commit;
+-- 0 row
+select * from gtt6;
+ n 
+---
+(0 rows)
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+ERROR:  cannot create indexes on global temporary tables using concurrent mode
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+ERROR:  not support cluster global temporary tables yet
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temp table
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+ERROR:  The global temp table does not allow modification on commit action.
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  can not defeine global temp table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test;
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR:  insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count 
+-------
+     1
+(1 row)
+
+select count(*) from orders;
+ count 
+-------
+     0
+(1 row)
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 1)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 300)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 19 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.gtt6
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to table gtt_function.gt1
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..0646aae
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,90 @@
+set search_path=gtt,sys;
+select nextval('gtt_with_seq_c2_seq');
+ nextval 
+---------
+       1
+(1 row)
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..b2ef23b
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,168 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          114688 |                 614400
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |            49152 |         49152 |                  49152
+(2 rows)
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+ c1 | c2 
+----+----
+  1 |  1
+(1 row)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..8c0c376
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,10 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..0e21b30
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,76 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+(1 row)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+(1 row)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index c730461..8a39594 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1349,6 +1349,93 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d2b17dd..80e577f 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..f3cf710
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,6 @@
+
+
+reset search_path;
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..c32c0bd
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,217 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+commit;
+-- 0 row
+select * from gtt6;
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test;
+
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d05745e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,44 @@
+
+
+set search_path=gtt,sys;
+
+select nextval('gtt_with_seq_c2_seq');
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..bf0b922
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,79 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..dbe84d1
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,19 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..f041892
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,42 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
#173曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: tushar (#169)
1 attachment(s)
Re: [Proposal] Global temporary tables

2020年3月4日 下午11:39,tushar <tushar.ahuja@enterprisedb.com> 写道:

On 3/3/20 2:10 PM, 曾文旌(义从) wrote:

I fixed in global_temporary_table_v16-pg13.patch.

Thanks Wenjing. The reported issue is fixed now but there is an another similar scenario -
if we enable 'on_commit_delete_rows' to true using alter command then getting same issue i.e rows are not removing after commit.

x=# create global temp table foo123(n int) with (on_commit_delete_rows='false');
CREATE TABLE
x=#
x=# alter table foo123 set ( on_commit_delete_rows='true');
ALTER TABLE

I blocked modify this parameter.

Fixed in global_temporary_table_v17-pg13.patch

Wenjing

Attachments:

global_temporary_table_v17-pg13.patchapplication/octet-stream; name=global_temporary_table_v17-pg13.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index c3d45c7..3f9e875 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -168,6 +168,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * For global temp table only
+	 * use ShareUpdateExclusiveLock for ensure safety
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temp table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},	
 	/* list terminator */
 	{{NULL}}
 };
@@ -1496,6 +1509,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1596,13 +1611,18 @@ build_reloptions(Datum reloptions, bool validate,
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	/*
 	 * There are no options for partitioned tables yet, but this is able to do
 	 * some validation.
 	 */
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index dd975b1..1610e7d 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1013,7 +1013,9 @@ gistGetFakeLSN(Relation rel)
 {
 	static XLogRecPtr counter = FirstNormalUnloggedLSN;
 
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	/* global temp is same as local temp table */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 4871b7f..16b00c9 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -149,7 +149,9 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	/* global temp table is same as local temp table */
+	if (!(index->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		index->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ca52846..f0153cf 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -598,7 +598,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -651,7 +651,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 03c43ef..2674132 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -62,6 +62,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -399,9 +400,9 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
 	Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
-	/* not every AM requires these to be valid, but heap does */
-	Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
-	Assert(MultiXactIdIsValid(onerel->rd_rel->relminmxid));
+	/* not every AM requires these to be valid, but regular heap does */
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ MultiXactIdIsValid(onerel->rd_rel->relminmxid));
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 39b8f17..38b46d0 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -601,6 +602,10 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(rel->rd_node.relNode))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 4361568..08ef041 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6348,6 +6348,10 @@ StartupXLOG(void)
 	else
 		recoveryTargetTLI = ControlFile->checkPointCopy.ThisTimeLineID;
 
+	/* clean temp relation files */
+	if (max_active_gtt > 0)
+		RemovePgTempFiles();
+
 	/*
 	 * Check for signal files, and if so set up state for offline recovery
 	 */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index f8f0b48..dc8bbb1 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -42,6 +42,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 7d6acae..3be8d63 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -393,7 +393,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/* global temp table is same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 9d9e915..1c50263 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -427,7 +429,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -957,6 +959,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -995,8 +998,18 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/* global temp table not remember transaction info in catalog */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1358,6 +1371,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1938,6 +1952,14 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/* We allow to drop global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(rel->rd_node))
+			elog(ERROR, "can not drop relation %s when other backend attached this global temp table",
+						RelationGetRelationName(rel));
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3165,7 +3187,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3177,7 +3199,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3223,8 +3245,13 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		/* truncate global temp table only need RowExclusiveLock */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3257,6 +3284,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3265,23 +3293,37 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (!gtt_storage_attached(rel->rd_node.relNode))
+			return;
+
+		/*
+		 * Truncate global temp table only need RowExclusiveLock
+		 */
+		lockmode = RowExclusiveLock;
+	}
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(rel, 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 7223679..2a0144e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -52,6 +52,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -877,6 +878,19 @@ index_create(Relation heapRelation,
 						indexRelationName, RelationGetRelationName(heapRelation))));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temp table use concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot create indexes on global temporary tables using concurrent mode")));
+
+		/* if global temp table not init storage, then skip build index */
+		if (!gtt_storage_attached(heapRelation->rd_node.relNode))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -2061,6 +2075,14 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/* We allow to drop index on global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation))
+	{
+		if (is_other_backend_use_gtt(userHeapRelation->rd_node))
+			elog(ERROR, "can not drop index %s when other backend attached this global temp table.",
+						RelationGetRelationName(userHeapRelation));
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2667,6 +2689,11 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	/* update index stats into localhash and rel_rd_rel for global temp table */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		is_gtt = true;
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2752,21 +2779,35 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
+		if (is_gtt)
+			rel->rd_rel->relpages = (int32) relpages;
+		else if (rd_rel->relpages != (int32) relpages)
 		{
 			rd_rel->relpages = (int32) relpages;
 			dirty = true;
 		}
-		if (rd_rel->reltuples != (float4) reltuples)
+
+		if (is_gtt)
+			rel->rd_rel->reltuples = (float4) reltuples;
+		else if (rd_rel->reltuples != (float4) reltuples)
 		{
 			rd_rel->reltuples = (float4) reltuples;
 			dirty = true;
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+
+		if (is_gtt)
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+		else if (rd_rel->relallvisible != (int32) relallvisible)
 		{
 			rd_rel->relallvisible = (int32) relallvisible;
 			dirty = true;
 		}
+
+		if (is_gtt)
+		{
+			up_gtt_relstats(rel, relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
+		}
 	}
 
 	/*
@@ -2880,6 +2921,15 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, index, val);
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		if (!gtt_storage_attached(indexRelation->rd_node.relNode))
+		{
+			gtt_force_enable_index(indexRelation);
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3475,6 +3525,15 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 				 errmsg("cannot reindex temporary tables of other sessions")));
 
 	/*
+	 * Because global temp table cannot change relfilenode
+	 * no support reindex on global temp table yet.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(iRel))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot reindex global temporary tables")));
+
+	/*
 	 * Also check for active uses of the index in the current transaction; we
 	 * don't want to reindex underneath an open indexscan.
 	 */
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 5ff7824..9b58f2a 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -647,6 +647,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		/* global temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp relations in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index fddfbf1..2982bc7 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -26,6 +26,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -75,7 +76,7 @@ static PendingRelDelete *pendingDeletes = NULL; /* head of linked list */
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -85,6 +86,8 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		/* global temp table use same storage strategy as local temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -117,6 +120,10 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/* remember global temp table storage info to localhash */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel)
+		remember_gtt_storage_info(rnode, rel);
+
 	return srel;
 }
 
@@ -486,8 +493,15 @@ smgrDoPendingDeletes(bool isCommit)
 		smgrdounlinkall(srels, nrels, false);
 
 		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* clean global temp table flags when transaction commit or rollback */
+			if (SmgrIsTemp(srels[i]) && 
+				gtt_storage_attached(srels[i]->smgr_rnode.node.relNode))
+				forget_gtt_storage_info(srels[i]->smgr_rnode.node.relNode, isCommit);
+		}
+
 		pfree(srels);
 	}
 }
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..04f7fcc
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1261 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  code to create and destroy physical storage for global temparary table
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_relstats_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+typedef struct gtt_ctl_data
+{
+	LWLock			lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct
+{
+	RelFileNode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+	Oid			relid;
+
+	Oid			spcnode;
+	/* pg_class stat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+	char		relkind;
+	bool		on_commit_delete;
+
+	/* pg_statistic */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(RelFileNode rnode);
+static void gtt_storage_checkout(RelFileNode rnode, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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_global_temporary_table.")));
+	}
+
+	if (found == false)
+	{
+		int			wordnum;
+
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(RelFileNode rnode, bool skiplock, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (!skiplock)
+		LWLockAcquire(&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);
+
+		if (isCommit)
+		{
+			elog(WARNING, "relfilenode %u/%u/%u not exist in gtt shared hash when forget",
+						rnode.dbNode, rnode.spcNode, rnode.relNode);
+		}
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+	bms_del_member(entry->map, MyBackendId);
+
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &rnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+
+	if (!skiplock)
+		LWLockRelease(&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;
+	int			natts = 0;
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		/* First time through: initialize the hash table */
+		HASHCTL		ctl;
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_relstats_context =
+			AllocSetContextCreate(CacheMemoryContext,
+							"gtt relstats context",
+							ALLOCSET_DEFAULT_SIZES);
+	}
+
+	/* Look up or create an entry */
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_ENTER, &found);
+
+	if (found)
+	{
+		elog(ERROR, "backend %d relid %u already exists in global temporary table local hash",
+					MyBackendId, relid);
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+	entry->spcnode = rnode.spcNode;
+	entry->relpages = 0;
+	entry->reltuples = 0;
+	entry->relallvisible = 0;
+	entry->relfrozenxid = InvalidTransactionId;
+	entry->relminmxid = InvalidMultiXactId;
+	entry->relkind = rel->rd_rel->relkind;
+	entry->on_commit_delete = false;
+	entry->natts = 0;
+	entry->attnum = NULL;
+	entry->att_stat_tups = NULL;
+
+	natts = RelationGetNumberOfAttributes(rel);
+	entry->attnum = palloc0(sizeof(int) * natts);
+	entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+	entry->natts = natts;
+	/* only heap contain transaction information */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+		{
+			entry->on_commit_delete = true;
+			register_on_commit_action(rel->rd_node.relNode, ONCOMMIT_DELETE_ROWS);
+		}
+
+		entry->relfrozenxid = RecentXmin;
+		entry->relminmxid = GetOldestMultiXactId();
+		insert_gtt_relfrozenxid_to_ordered_list(entry->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	if (entry->relkind == RELKIND_RELATION ||
+		entry->relkind == RELKIND_SEQUENCE)
+	{
+		gtt_storage_checkin(rnode);
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry)
+	{
+		int		i;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			Assert(TransactionIdIsNormal(entry->relfrozenxid) || !isCommit);
+			if (TransactionIdIsValid(entry->relfrozenxid))
+			{
+				remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+				set_gtt_session_relfrozenxid();
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			RelFileNode rnode;
+			rnode.spcNode = entry->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = entry->relid;
+			gtt_storage_checkout(rnode, false, isCommit);
+		}
+
+		for (i = 0; i < entry->natts; i++)
+		{
+			if (entry->att_stat_tups[i])
+			{
+				heap_freetuple(entry->att_stat_tups[i]);
+				entry->att_stat_tups[i] = NULL;
+			}
+		}
+
+		if (entry->attnum)
+			pfree(entry->attnum);
+
+		if (entry->att_stat_tups)
+			pfree(entry->att_stat_tups);
+	}
+
+	hash_search(gtt_storage_local_hash,
+			(void *) &(relid), HASH_REMOVE, NULL);
+
+	return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool found;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (gtt_storage_local_hash == NULL)
+		return false;
+
+	hash_search(gtt_storage_local_hash,
+			(void *) &(relid), HASH_FIND, &found);
+
+	return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS				status;
+	gtt_local_hash_entry		*entry;
+	int			nrels = 0,
+				maxrels = 0;
+	SMgrRelation	*srels = NULL;
+	RelFileNode		*rnodes = NULL;
+	char			*relkinds = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		SMgrRelation srel;
+		RelFileNode rnode;
+
+		rnode.spcNode = entry->spcnode;
+		rnode.dbNode = MyDatabaseId;
+		rnode.relNode = entry->relid;
+
+		srel = smgropen(rnode, MyBackendId);
+
+		/* allocate the initial array, or extend it, if needed */
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			srels = palloc(sizeof(SMgrRelation) * maxrels);
+			rnodes = palloc(sizeof(RelFileNode) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+			rnodes = repalloc(rnodes, sizeof(RelFileNode) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		srels[nrels] = srel;
+		rnodes[nrels] = rnode;
+		relkinds[nrels] = entry->relkind;
+		nrels++;
+	}
+
+	if (nrels > 0)
+	{
+		int i;
+
+		smgrdounlinkall(srels, nrels, false);
+
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+		for (i = 0; i < nrels; i++)
+		{
+			smgrclose(srels[i]);
+			if (relkinds[i] == RELKIND_RELATION ||
+				relkinds[i] == RELKIND_SEQUENCE)
+				gtt_storage_checkout(rnodes[i], true, false);
+		}
+		LWLockRelease(&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.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (gtt_storage_local_hash == NULL)
+		return false;
+
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_FIND, &found);
+
+	if (!found)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	if (relpages)
+		*relpages = entry->relpages;
+
+	if (reltuples)
+		*reltuples = entry->reltuples;
+
+	if (relallvisible)
+		*relallvisible = entry->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = entry->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = entry->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	MemoryContext			oldcontext;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+						(void *) &reloid, HASH_FIND, &found);
+
+	if (!found)
+		return;
+
+	/* todo */
+	if (entry->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	Assert(entry->relid == reloid);
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == 0)
+		{
+			entry->attnum[i] = attnum;
+			break;
+		}
+		else if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < entry->natts);
+	Assert(entry->att_stat_tups[i] == NULL);
+	entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+						(void *) &reloid, HASH_FIND, &found);
+
+	if (!found)
+		return NULL;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			return entry->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int			i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	HeapTuple	tuple;
+	Relation	rel = NULL;
+	int		attnum = PG_GETARG_INT32(1);
+	Oid		reloid = PG_GETARG_OID(0);
+	char		rel_persistence;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	  tupdesc;
+	MemoryContext oldcontext;
+	Tuplestorestate *tupstore;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = table_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+
+	table_close(rel, NoLock);
+	table_close(pg_tatistic, AccessShareLock);
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate	*tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Relation	rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	if (get_gtt_relstats(reloid,
+						&relpages, &reltuples, &relallvisible,
+						&relfrozenxid, &relminmxid))
+	{
+		Datum	values[5];
+		bool	isnull[5];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = Int32GetDatum(relpages);
+		values[1] = Float4GetDatum((float4)reltuples);
+		values[2] = Int32GetDatum(relallvisible);
+		values[3] = UInt32GetDatum(relfrozenxid);
+		values[4] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+
+	tuplestore_donestoring(tupstore);
+	table_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(rel->rd_node);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	table_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(indexOid));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid relOid = index->rd_index->indrelid;
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	if (gtt_storage_attached(relOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int			i;
+	Oid			toastrelid;
+
+	if (operation != CMD_INSERT)
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_attached(relation->rd_node.relNode))
+		return;
+
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+
+		index_build(relation, index, info, true, false);
+	}
+
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid 		indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo 	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index b8a3f46..491db16 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM 
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 924ef37..7efc5a9 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -184,6 +185,13 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(onerel->rd_node.relNode))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -586,14 +594,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 		 * pg_statistic for columns we didn't process, we leave them alone.)
 		 */
 		update_attstats(RelationGetRelid(onerel), inh,
-						attr_cnt, vacattrstats);
+						attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
 
 		for (ind = 0; ind < nindexes; ind++)
 		{
 			AnlIndexData *thisdata = &indexdata[ind];
 
 			update_attstats(RelationGetRelid(Irel[ind]), false,
-							thisdata->attr_cnt, thisdata->vacattrstats);
+							thisdata->attr_cnt, thisdata->vacattrstats,
+							RelationGetRelPersistence(Irel[ind]));
 		}
 
 		/*
@@ -1456,7 +1465,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
  *		by taking a self-exclusive lock on the relation in analyze_rel().
  */
 static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
 {
 	Relation	sd;
 	int			attno;
@@ -1558,31 +1567,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/* Update column statistic to localhash, not catalog */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 11ce1bb..552fd87 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -391,6 +391,12 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/* not support cluster global temp table yet */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("not support cluster global temporary tables yet")));
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index e79ede4..078694d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -27,6 +27,7 @@
 #include "catalog/dependency.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -1062,7 +1063,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, stmt->filename, stmt->is_program,
@@ -2840,6 +2841,8 @@ CopyFrom(CopyState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	estate->es_result_relations = resultRelInfo;
 	estate->es_num_result_relations = 1;
 	estate->es_result_relation_info = resultRelInfo;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 3f3a89f..928c01e 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2586,6 +2586,16 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 			!isTempNamespace(classtuple->relnamespace))
 			continue;
 
+		/* not support reindex on global temp table, so skip it */
+		if (classtuple->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			ereport(WARNING,
+				(errmsg("global temp table \"%s.%s\" skip reindexed",
+					get_namespace_name(get_rel_namespace(relid)),
+					get_rel_name(relid))));
+			continue;
+		}
+
 		/* Check user/system classification, and optionally skip */
 		if (objectKind == REINDEX_OBJECT_SYSTEM &&
 			!IsSystemClass(relid, classtuple))
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index d8cafc4..deecde8 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -96,7 +96,8 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 	 * transaction.
 	 */
 	relpersistence = get_rel_persistence(relid);
-	if (relpersistence == RELPERSISTENCE_TEMP)
+	if (relpersistence == RELPERSISTENCE_TEMP ||
+		relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
 
 	/* Check permissions. */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..fada1ac 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -94,7 +96,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -222,7 +225,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* now initialize the sequence's data */
 	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -327,7 +330,7 @@ ResetSequence(Oid seq_relid)
 	/*
 	 * Insert the modified tuple into the new storage file.
 	 */
-	fill_seq_with_data(seq_rel, tuple);
+	fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -340,17 +343,21 @@ ResetSequence(Oid seq_relid)
  * Initialize a sequence's relation with the specified tuple as content
  */
 static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 	sequence_magic *sm;
 	OffsetNumber offnum;
+	bool lockBuffer = false;
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ReadBuffer(rel, P_NEW);
-	Assert(BufferGetBlockNumber(buf) == 0);
+	if (buf == InvalidBuffer)
+	{
+		buf = ReadBuffer(rel, P_NEW);
+		Assert(BufferGetBlockNumber(buf) == 0);
+		lockBuffer = true;
+	}
 
 	page = BufferGetPage(buf);
 
@@ -360,7 +367,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* Now insert sequence tuple */
 
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+	if (lockBuffer)
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +418,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	END_CRIT_SECTION();
 
-	UnlockReleaseBuffer(buf);
+	if (lockBuffer)
+		UnlockReleaseBuffer(buf);
 }
 
 /*
@@ -502,7 +511,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * Insert the modified tuple into the new storage file.
 		 */
-		fill_seq_with_data(seqrel, newdatatuple);
+		fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
 	}
 
 	/* process OWNED BY if given */
@@ -611,7 +620,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +945,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1154,6 +1163,13 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(seqrel->rd_node.relNode))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1954,3 +1970,46 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+void
+gtt_init_seq(Relation rel)
+{
+	/* Initialize sequence for global temporary tables */
+	Datum		value[SEQ_COL_LASTCOL] = {0};
+	bool		null[SEQ_COL_LASTCOL] = {false};
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 7a13b97..536083e 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -553,6 +554,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 								  Relation partitionTbl);
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static OnCommitAction gtt_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -598,6 +600,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -608,8 +611,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Check consistency of arguments
 	 */
+	/* global temp table same as local temp table */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !(stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+			 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -639,7 +644,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	/* global temp table same as local temp table */
+	if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+		 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -740,6 +747,55 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* global temp table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* check parent table*/
+		if (inheritOids)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			table_close(relation, NoLock);
+		}
+
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "can not defeine global temp table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temp table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1824,7 +1880,8 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
 		 * table or the current physical file to be thrown away anyway.
 		 */
 		if (rel->rd_createSubid == mySubid ||
-			rel->rd_newRelfilenodeSubid == mySubid)
+			rel->rd_newRelfilenodeSubid == mySubid ||
+			RELATION_IS_GLOBAL_TEMP(rel))
 		{
 			/* Immediate, non-rollbackable truncation is OK */
 			heap_truncate_one_rel(rel);
@@ -3567,6 +3624,14 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(rel->rd_node))
+			elog(ERROR, "can not alter table %s when other backend attached this global temp table",
+						RelationGetRelationName(rel));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -8179,6 +8244,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -12647,6 +12718,12 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+			elog(ERROR, "The global temp table does not allow modification on commit action.");
+	}
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -12849,6 +12926,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temp table");
+
 	/*
 	 * No work if no change in tablespace.
 	 */
@@ -13221,7 +13301,7 @@ index_copy_data(Relation rel, RelFileNode newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
@@ -14628,7 +14708,9 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
 	 */
 	switch (rel->rd_rel->relpersistence)
 	{
+		/* global temp table same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -17220,3 +17302,28 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
 		table_close(rel, NoLock);
 	}
 }
+
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			if (defGetBoolean(def))
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index d625d17..f58c44d 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/vacuum.h"
@@ -1217,6 +1218,17 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	 /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		is_gtt = true;
+		up_gtt_relstats(relation,
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1230,17 +1242,26 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relpages = (int32) num_pages;
+	else if (pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (is_gtt)
+		relation->rd_rel->reltuples = (float4) num_tuples;
+	else if (pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	else if (pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1285,7 +1306,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1296,7 +1318,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1394,6 +1417,10 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/* global temp table relstats not in pg_class */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1451,6 +1478,25 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/*
+	 * Global temp table get frozenxid from MyProc
+	 * to avoid the vacuum truncate clog that gtt need.
+	 */
+	if (max_active_gtt > 0)
+	{
+		TransactionId oldest_gtt_frozenxid =
+			list_all_session_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid) &&
+			TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+		{
+			ereport(WARNING,
+				(errmsg("global temp table oldest FrozenXid is far in the past"),
+				 errhint("please truncate them or kill those sessions that use them.")));
+			newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1778,6 +1824,18 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(onerel->rd_node.relNode))
+	{
+		ereport(WARNING,
+				(errmsg("skipping vacuum empty global temp table \"%s\"",
+						RelationGetRelationName(onerel))));
+		relation_close(onerel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 28130fb..dcb4a0b 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index c13b1d3..253054d 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
@@ -543,6 +544,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	init_gtt_storage(mtstate->operation, leaf_part_rri);
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d71c0a4..0900907 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,6 +42,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -2372,6 +2373,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			ExecOpenIndices(resultRelInfo,
 							node->onConflictAction != ONCONFLICT_NONE);
 
+		init_gtt_storage(operation, resultRelInfo);
+
 		/*
 		 * If this is an UPDATE and a BEFORE UPDATE trigger is present, the
 		 * trigger itself might modify the partition-key values. So arrange
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 905bbe7..170963d 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -591,6 +591,8 @@ static void
 set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 						  RangeTblEntry *rte)
 {
+	char relpersistence;
+
 	/*
 	 * The flag has previously been initialized to false, so we can just
 	 * return if it becomes clear that we can't safely set it.
@@ -618,7 +620,11 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			/* global temp table is same as local temp table */
+			relpersistence = get_rel_persistence(rte->relid);
+
+			if (relpersistence == RELPERSISTENCE_TEMP ||
+				relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b44efd6..14dbaaf 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6307,7 +6307,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
+	/* global temp table is same as local temp table */
 	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index d82fc5a..93c6d97 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -28,6 +28,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temp table */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(indexRelation->rd_node.relNode))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6676412..0685c1c 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2585,6 +2585,11 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("materialized views must not use temporary tables or views")));
 
+		if (is_query_using_gtt(query))
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("materialized views must not use global temporary tables or views")));
+
 		/*
 		 * A materialized view would either need to save parameters for use in
 		 * maintaining/loading the data or prohibit them entirely.  The latter
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 96e7fdb..30f585c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3290,17 +3290,11 @@ OptTemp:	TEMPORARY					{ $$ = RELPERSISTENCE_TEMP; }
 			| LOCAL TEMP				{ $$ = RELPERSISTENCE_TEMP; }
 			| GLOBAL TEMPORARY
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
-					$$ = RELPERSISTENCE_TEMP;
+					$$ = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
-					$$ = RELPERSISTENCE_TEMP;
+					$$ = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED					{ $$ = RELPERSISTENCE_UNLOGGED; }
 			| /*EMPTY*/					{ $$ = RELPERSISTENCE_PERMANENT; }
@@ -11594,19 +11588,13 @@ OptTempTableName:
 				}
 			| GLOBAL TEMPORARY opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED opt_table qualified_name
 				{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b875a50..123945b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index af77f18..67d34f0 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index e3a43d3..ca460ca 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2087,6 +2087,11 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* autovacuum skip vacuum global temp table */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2153,7 +2158,9 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		/* autovacuum skip vacuum global temp table */
+		if (classForm->relpersistence == RELPERSISTENCE_TEMP || 
+			classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 5880054..98f7b43 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -52,6 +53,7 @@
 #include "utils/rel.h"
 #include "utils/resowner_private.h"
 #include "utils/timestamp.h"
+#include "utils/guc.h"
 
 
 /* Note: these two macros only work on shared buffers, not local ones! */
@@ -2809,6 +2811,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(relation->rd_node.relNode))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d5..7adb969 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -21,6 +21,7 @@
 #include "access/nbtree.h"
 #include "access/subtrans.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, active_gtt_shared_hash_size());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* global temporary table */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 4a5b26c..9754168 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -62,6 +62,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -4087,3 +4088,77 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temp table.
+ */
+int
+list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct *arrayP = procArray;
+	TransactionId result = InvalidTransactionId;
+	int			index;
+	int			flags = 0;
+	int			i = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+		*n = 0;
+	}
+
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		volatile PGXACT *pgxact = &allPgXact[pgprocno];
+
+		if (pgxact->vacuumFlags & flags)
+			continue;
+
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->session_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->session_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->session_gtt_frozenxid, result))
+				result = proc->session_gtt_frozenxid;
+
+			if (max_size > 0)
+			{
+				pids[i] = proc->pid;
+				xids[i] = proc->session_gtt_frozenxid;
+				i++;
+			}
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (max_size > 0)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index eb321f7..3893cef 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 8406644..0d99321 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -1008,6 +1008,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 0be26fe..c169c99 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -112,6 +112,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -4570,12 +4571,25 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -4700,15 +4714,27 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6090,6 +6116,7 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	{
 		/* Simple variable --- look to stats for the underlying table */
 		RangeTblEntry *rte = planner_rt_fetch(index->rel->relid, root);
+		char	rel_persistence = get_rel_persistence(rte->relid);
 
 		Assert(rte->rtekind == RTE_RELATION);
 		relid = rte->relid;
@@ -6107,6 +6134,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6118,6 +6152,8 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	}
 	else
 	{
+		char	rel_persistence = get_rel_persistence(index->indexoid);
+
 		/* Expression --- maybe there are stats for the index itself */
 		relid = index->indexoid;
 		colnum = 1;
@@ -6133,6 +6169,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7046,6 +7089,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		/* attempt to lookup stats in relation for this index column */
 		if (attnum != 0)
 		{
+			char	rel_persistence = get_rel_persistence(rte->relid);
+
 			/* Simple variable -- look to stats for the underlying table */
 			if (get_relation_stats_hook &&
 				(*get_relation_stats_hook) (root, rte, attnum, &vardata))
@@ -7058,6 +7103,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7070,6 +7123,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		}
 		else
 		{
+			char	rel_persistence = get_rel_persistence(index->indexoid);
+
 			/*
 			 * Looks like we've found an expression column in the index. Let's
 			 * see if there's any stats for it.
@@ -7089,6 +7144,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 3da90cb..97240ff 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -2878,6 +2879,18 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index ff70326..1cf7063 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -63,6 +63,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1127,6 +1128,24 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1181,6 +1200,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			gtt_fix_index_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -2220,6 +2240,8 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		gtt_fix_index_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3316,6 +3338,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3430,6 +3456,9 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
 
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+		elog(ERROR, "global temp table does not allow setting new relfilenode");
+
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
@@ -3470,7 +3499,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 				/* handle these directly, at least for now */
 				SMgrRelation srel;
 
-				srel = RelationCreateStorage(newrnode, persistence);
+				srel = RelationCreateStorage(newrnode, persistence, relation);
 				smgrclose(srel);
 			}
 			break;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index dbecc00..6acf3f3 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -140,6 +140,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temp table feature.
+ * global temp table define can still storage in catalog
+ * just can not use.
+ * num > 0 means database can management num active global temp table.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2047,6 +2059,15 @@ static struct config_bool ConfigureNamesBool[] =
 static struct config_int ConfigureNamesInt[] =
 {
 	{
+		{"max_active_global_temporary_table", PGC_POSTMASTER, UNGROUPED,
+			gettext_noop("max active global temporary table."),
+			NULL
+		},
+		&max_active_gtt,
+		DEFAULT_NUM_ACTIVE_GTT, MIN_NUM_ACTIVE_GTT, MAX_NUM_ACTIVE_GTT,
+		NULL, NULL, NULL
+	},
+	{
 		{"archive_timeout", PGC_SIGHUP, WAL_ARCHIVING,
 			gettext_noop("Forces a switch to the next WAL file if a "
 						 "new file has not been started within N seconds."),
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a12c8d0..58b22ea 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15595,6 +15595,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	{
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
+		char		*table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15646,9 +15647,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 			binary_upgrade_set_pg_class_oids(fout, q,
 											 tbinfo->dobj.catId.oid, false);
 
+		if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED)
+			table_type = "UNLOGGED ";
+		else if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			table_type = "GLOBAL TEMPORARY ";
+		else
+			table_type = "";
+
 		appendPQExpBuffer(q, "CREATE %s%s %s",
-						  tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ?
-						  "UNLOGGED " : "",
+						  table_type,
 						  reltypename,
 						  qualrelname);
 
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 5f9a102..3e40297 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -84,7 +84,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -157,7 +157,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 7e524ea..371168f 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -441,22 +443,46 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 	 * output, so we have to copy that system table.  It's easiest to do that
 	 * by treating it as a user table.
 	 */
-	snprintf(query + strlen(query), sizeof(query) - strlen(query),
-			 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
-			 "  SELECT c.oid, 0::oid, 0::oid "
-			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
-			 "         ON c.relnamespace = n.oid "
-			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
-	/* exclude possible orphaned temp tables */
-			 "    ((n.nspname !~ '^pg_temp_' AND "
-			 "      n.nspname !~ '^pg_toast_temp_' AND "
-			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
-			 "                        'binary_upgrade', 'pg_toast') AND "
-			 "      c.oid >= %u::pg_catalog.oid) OR "
-			 "     (n.nspname = 'pg_catalog' AND "
-			 "      relname IN ('pg_largeobject') ))), ",
-			 FirstNormalObjectId);
+	if (skip_gtt)
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude global temp tables */
+				 "    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
+	else
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
 
 	/*
 	 * Add a CTE that collects OIDs of toast tables belonging to the tables
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 70194eb..8dd9e66 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -411,7 +411,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -642,7 +642,9 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -653,7 +655,9 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 8b90cef..d155205 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -389,7 +389,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index f3c7eb9..28134e2 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3723,7 +3723,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b6b08d0..16377e6 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1016,6 +1016,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2378,6 +2380,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2586,6 +2591,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 78b33b2..4be89f6 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -168,6 +168,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 7fb574f..6f8591c 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5507,6 +5507,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4191',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4192',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{relid,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4193',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4194',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 048003c..af48cdf 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -19,7 +19,7 @@
 #include "storage/smgr.h"
 #include "utils/relcache.h"
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationTruncate(Relation rel, BlockNumber nblocks);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..63f7241
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,43 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, bool isCommit);
+extern bool is_other_backend_use_gtt(RelFileNode node);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(RelFileNode node);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Relation relation,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void gtt_force_enable_index(Relation index);
+extern void gtt_fix_index_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index e2638ab..89a5ce4 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 3f88683..f870e9a 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 8fda8e4..172eae9 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_TBM,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_SXACT,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index d217801..8adde87 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -117,6 +117,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId session_gtt_frozenxid;	/* session level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ce93ace..0f7262e 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -281,6 +281,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 44ed04d..6b6d2da 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	struct SMgrRelationData *rd_smgr;	/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -277,6 +277,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -535,11 +536,13 @@ typedef struct ViewOptions
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -547,6 +550,7 @@ typedef struct ViewOptions
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -559,6 +563,14 @@ typedef struct ViewOptions
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is either local temp relation of this session
+ * 		or global temp relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -602,6 +614,17 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..73d3d7b
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,8 @@
+reset search_path;
+drop schema gtt cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
+drop cascades to table gtt.gtt_with_seq
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..067393b
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,322 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+ n 
+---
+ 9
+(1 row)
+
+commit;
+-- 0 row
+select * from gtt6;
+ n 
+---
+(0 rows)
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+ERROR:  cannot create indexes on global temporary tables using concurrent mode
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+ERROR:  not support cluster global temporary tables yet
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temp table
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+ERROR:  The global temp table does not allow modification on commit action.
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  can not defeine global temp table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test;
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR:  insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count 
+-------
+     1
+(1 row)
+
+select count(*) from orders;
+ count 
+-------
+     0
+(1 row)
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 1)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 300)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 19 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.gtt6
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to table gtt_function.gt1
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..0646aae
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,90 @@
+set search_path=gtt,sys;
+select nextval('gtt_with_seq_c2_seq');
+ nextval 
+---------
+       1
+(1 row)
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..b2ef23b
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,168 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          114688 |                 614400
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |            49152 |         49152 |                  49152
+(2 rows)
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+ c1 | c2 
+----+----
+  1 |  1
+(1 row)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..8c0c376
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,10 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..0e21b30
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,76 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+(1 row)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+(1 row)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index c730461..8a39594 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1349,6 +1349,93 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d2b17dd..80e577f 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..f3cf710
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,6 @@
+
+
+reset search_path;
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..c32c0bd
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,217 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+commit;
+-- 0 row
+select * from gtt6;
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test;
+
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d05745e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,44 @@
+
+
+set search_path=gtt,sys;
+
+select nextval('gtt_with_seq_c2_seq');
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..bf0b922
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,79 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..dbe84d1
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,19 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..f041892
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,42 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
#174曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: tushar (#170)
Re: [Proposal] Global temporary tables

2020年3月5日 下午10:19,tushar <tushar.ahuja@enterprisedb.com> 写道:

On 3/3/20 2:10 PM, 曾文旌(义从) wrote:

I fixed in global_temporary_table_v16-pg13.patch.

Please refer this scenario -

--Connect to psql -

postgres=# alter system set max_active_global_temporary_table =1;
ALTER SYSTEM

--restart the server (./pg_ctl -D data restart)

--create global temp table

postgres=# create global temp table ccc1 (c int);
CREATE TABLE

--Try to Create another global temp table

postgres=# create global temp table ccc2 (c int);
WARNING: relfilenode 13589/1663/19063 not exist in gtt shared hash when forget
ERROR: out of shared memory
HINT: You might need to increase max_active_gtt.

postgres=# show max_active_gtt;
ERROR: unrecognized configuration parameter "max_active_gtt"
postgres=#
postgres=# show max_active_global_temporary_table ;
max_active_global_temporary_table
-----------------------------------
1
(1 row)

postgres=#

I cannot find "max_active_gtt" GUC . I think you are referring to "max_active_global_temporary_table" here ?

You're right.

Fixed in global_temporary_table_v17-pg13.patch

Wenjing

Show quoted text

also , would be great if we can make this error message user friendly like - "max connection reached" rather than memory error

--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/ <https://www.enterprisedb.com/&gt;
The Enterprise PostgreSQL Company

#175曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Robert Haas (#171)
Re: [Proposal] Global temporary tables

2020年3月5日 下午10:38,Robert Haas <robertmhaas@gmail.com> 写道:

On Thu, Mar 5, 2020 at 9:19 AM tushar <tushar.ahuja@enterprisedb.com> wrote:

WARNING: relfilenode 13589/1663/19063 not exist in gtt shared hash when forget
ERROR: out of shared memory
HINT: You might need to increase max_active_gtt.

also , would be great if we can make this error message user friendly like - "max connection reached" rather than memory error

That would be nice, but the bigger problem is that the WARNING there
looks totally unacceptable. It's looks like it's complaining of some
internal issue (i.e. a bug or corruption) and the grammar is poor,
too.

Yes, WARNING should not exist.
This is a bug in the rollback process and I have fixed it in global_temporary_table_v17-pg13.patch

Wenjing

Show quoted text

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#176Prabhat Sahu
prabhat.sahu@enterprisedb.com
In reply to: 曾文旌(义从) (#175)
Re: [Proposal] Global temporary tables

Hi All,

Kindly check the below scenario.

*Case 1: *
postgres=# CREATE GLOBAL TEMPORARY TABLE gtt1(c1 int) on commit delete rows;
CREATE TABLE
postgres=# CREATE GLOBAL TEMPORARY TABLE gtt2(c1 int) on commit preserve
rows;
CREATE TABLE
postgres=# vacuum gtt1;
VACUUM
postgres=# vacuum gtt2;
VACUUM
postgres=# vacuum;
VACUUM
postgres=# \q

*Case 2: Exit and reconnect to psql prompt.*
[edb@localhost bin]$ ./psql postgres
psql (13devel)
Type "help" for help.

postgres=# vacuum gtt1;
WARNING: skipping vacuum empty global temp table "gtt1"
VACUUM
postgres=# vacuum gtt2;
WARNING: skipping vacuum empty global temp table "gtt2"
VACUUM
postgres=# vacuum;
WARNING: skipping vacuum empty global temp table "gtt1"
WARNING: skipping vacuum empty global temp table "gtt2"
VACUUM

Although in "Case1" the gtt1/gtt2 are empty, we are not getting "WARNING:
skipping vacuum empty global temp table" for VACUUM in "Case 1".
whereas we are getting the "WARNING" for VACUUM in "Case2".

On Fri, Mar 6, 2020 at 12:41 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com> wrote:

2020年3月5日 下午10:38,Robert Haas <robertmhaas@gmail.com> 写道:

On Thu, Mar 5, 2020 at 9:19 AM tushar <tushar.ahuja@enterprisedb.com>

wrote:

WARNING: relfilenode 13589/1663/19063 not exist in gtt shared hash

when forget

ERROR: out of shared memory
HINT: You might need to increase max_active_gtt.

also , would be great if we can make this error message user friendly

like - "max connection reached" rather than memory error

That would be nice, but the bigger problem is that the WARNING there
looks totally unacceptable. It's looks like it's complaining of some
internal issue (i.e. a bug or corruption) and the grammar is poor,
too.

Yes, WARNING should not exist.
This is a bug in the rollback process and I have fixed it in
global_temporary_table_v17-pg13.patch

Wenjing

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

#177tushar
tushar.ahuja@enterprisedb.com
In reply to: 曾文旌(义从) (#173)
Re: [Proposal] Global temporary tables

On 3/6/20 12:35 PM, 曾文旌(义从) wrote:

Fixed in global_temporary_table_v17-pg13.patch

Thanks Wenjing.

Please refer this scenario , where i am able to set
'on_commit_delete_rows=true'  on regular table using 'alter' Syntax 
which is not allowed using 'Create' Syntax

--Expected -

postgres=# CREATE TABLE foo () WITH (on_commit_delete_rows='true');
ERROR:  The parameter on_commit_delete_rows is exclusive to the global
temp table, which cannot be specified by a regular table

--But user can do this with 'alter' command -
postgres=# create table foo();
CREATE TABLE
postgres=# alter table foo set (on_commit_delete_rows='true');
ALTER TABLE
postgres=#

--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company

#178tushar
tushar.ahuja@enterprisedb.com
In reply to: 曾文旌(义从) (#173)
Re: [Proposal] Global temporary tables

On 3/6/20 12:35 PM, 曾文旌(义从) wrote:

Fixed in global_temporary_table_v17-pg13.patch

I observed that , we do support 'global temp' keyword with views

postgres=# create or replace  global temp view v1 as select 5;
CREATE VIEW

but if we take the dump( using pg_dumpall) then it only display 'create
view'

look like we are skipping it ?

--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company

#179曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Prabhat Sahu (#176)
1 attachment(s)
Re: [Proposal] Global temporary tables

2020年3月9日 下午8:24,Prabhat Sahu <prabhat.sahu@enterprisedb.com> 写道:

Hi All,

Kindly check the below scenario.

Case 1:
postgres=# CREATE GLOBAL TEMPORARY TABLE gtt1(c1 int) on commit delete rows;
CREATE TABLE
postgres=# CREATE GLOBAL TEMPORARY TABLE gtt2(c1 int) on commit preserve rows;
CREATE TABLE
postgres=# vacuum gtt1;
VACUUM
postgres=# vacuum gtt2;
VACUUM
postgres=# vacuum;
VACUUM
postgres=# \q

Case 2: Exit and reconnect to psql prompt.
[edb@localhost bin]$ ./psql postgres
psql (13devel)
Type "help" for help.

postgres=# vacuum gtt1;
WARNING: skipping vacuum empty global temp table "gtt1"
VACUUM
postgres=# vacuum gtt2;
WARNING: skipping vacuum empty global temp table "gtt2"
VACUUM
postgres=# vacuum;
WARNING: skipping vacuum empty global temp table "gtt1"
WARNING: skipping vacuum empty global temp table "gtt2"
VACUUM

Although in "Case1" the gtt1/gtt2 are empty, we are not getting "WARNING: skipping vacuum empty global temp table" for VACUUM in "Case 1".
whereas we are getting the "WARNING" for VACUUM in "Case2".

I fixed the warning message, It's more accurate now.

Wenjing

Show quoted text

On Fri, Mar 6, 2020 at 12:41 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> wrote:

2020年3月5日 下午10:38,Robert Haas <robertmhaas@gmail.com <mailto:robertmhaas@gmail.com>> 写道:

On Thu, Mar 5, 2020 at 9:19 AM tushar <tushar.ahuja@enterprisedb.com <mailto:tushar.ahuja@enterprisedb.com>> wrote:

WARNING: relfilenode 13589/1663/19063 not exist in gtt shared hash when forget
ERROR: out of shared memory
HINT: You might need to increase max_active_gtt.

also , would be great if we can make this error message user friendly like - "max connection reached" rather than memory error

That would be nice, but the bigger problem is that the WARNING there
looks totally unacceptable. It's looks like it's complaining of some
internal issue (i.e. a bug or corruption) and the grammar is poor,
too.

Yes, WARNING should not exist.
This is a bug in the rollback process and I have fixed it in global_temporary_table_v17-pg13.patch

Wenjing

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;
The Enterprise PostgreSQL Company

--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

Attachments:

global_temporary_table_v18-pg13.patchapplication/octet-stream; name=global_temporary_table_v18-pg13.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index c3d45c7..3f9e875 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -168,6 +168,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * For global temp table only
+	 * use ShareUpdateExclusiveLock for ensure safety
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temp table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},	
 	/* list terminator */
 	{{NULL}}
 };
@@ -1496,6 +1509,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1596,13 +1611,18 @@ build_reloptions(Datum reloptions, bool validate,
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	/*
 	 * There are no options for partitioned tables yet, but this is able to do
 	 * some validation.
 	 */
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index dd975b1..1610e7d 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1013,7 +1013,9 @@ gistGetFakeLSN(Relation rel)
 {
 	static XLogRecPtr counter = FirstNormalUnloggedLSN;
 
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	/* global temp is same as local temp table */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 4871b7f..16b00c9 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -149,7 +149,9 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	/* global temp table is same as local temp table */
+	if (!(index->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		index->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ca52846..f0153cf 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -598,7 +598,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -651,7 +651,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 03c43ef..2674132 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -62,6 +62,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -399,9 +400,9 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
 	Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
-	/* not every AM requires these to be valid, but heap does */
-	Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
-	Assert(MultiXactIdIsValid(onerel->rd_rel->relminmxid));
+	/* not every AM requires these to be valid, but regular heap does */
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ MultiXactIdIsValid(onerel->rd_rel->relminmxid));
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 39b8f17..38b46d0 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -601,6 +602,10 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(rel->rd_node.relNode))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index b0e953f..e366507 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6348,6 +6348,10 @@ StartupXLOG(void)
 	else
 		recoveryTargetTLI = ControlFile->checkPointCopy.ThisTimeLineID;
 
+	/* clean temp relation files */
+	if (max_active_gtt > 0)
+		RemovePgTempFiles();
+
 	/*
 	 * Check for signal files, and if so set up state for offline recovery
 	 */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index f8f0b48..dc8bbb1 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -42,6 +42,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 7d6acae..3be8d63 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -393,7 +393,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/* global temp table is same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 9d9e915..1c50263 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -427,7 +429,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -957,6 +959,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -995,8 +998,18 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/* global temp table not remember transaction info in catalog */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1358,6 +1371,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1938,6 +1952,14 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/* We allow to drop global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(rel->rd_node))
+			elog(ERROR, "can not drop relation %s when other backend attached this global temp table",
+						RelationGetRelationName(rel));
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3165,7 +3187,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3177,7 +3199,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3223,8 +3245,13 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		/* truncate global temp table only need RowExclusiveLock */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3257,6 +3284,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3265,23 +3293,37 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (!gtt_storage_attached(rel->rd_node.relNode))
+			return;
+
+		/*
+		 * Truncate global temp table only need RowExclusiveLock
+		 */
+		lockmode = RowExclusiveLock;
+	}
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(rel, 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 7223679..2a0144e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -52,6 +52,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -877,6 +878,19 @@ index_create(Relation heapRelation,
 						indexRelationName, RelationGetRelationName(heapRelation))));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temp table use concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot create indexes on global temporary tables using concurrent mode")));
+
+		/* if global temp table not init storage, then skip build index */
+		if (!gtt_storage_attached(heapRelation->rd_node.relNode))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -2061,6 +2075,14 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/* We allow to drop index on global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation))
+	{
+		if (is_other_backend_use_gtt(userHeapRelation->rd_node))
+			elog(ERROR, "can not drop index %s when other backend attached this global temp table.",
+						RelationGetRelationName(userHeapRelation));
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2667,6 +2689,11 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	/* update index stats into localhash and rel_rd_rel for global temp table */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		is_gtt = true;
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2752,21 +2779,35 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
+		if (is_gtt)
+			rel->rd_rel->relpages = (int32) relpages;
+		else if (rd_rel->relpages != (int32) relpages)
 		{
 			rd_rel->relpages = (int32) relpages;
 			dirty = true;
 		}
-		if (rd_rel->reltuples != (float4) reltuples)
+
+		if (is_gtt)
+			rel->rd_rel->reltuples = (float4) reltuples;
+		else if (rd_rel->reltuples != (float4) reltuples)
 		{
 			rd_rel->reltuples = (float4) reltuples;
 			dirty = true;
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+
+		if (is_gtt)
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+		else if (rd_rel->relallvisible != (int32) relallvisible)
 		{
 			rd_rel->relallvisible = (int32) relallvisible;
 			dirty = true;
 		}
+
+		if (is_gtt)
+		{
+			up_gtt_relstats(rel, relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
+		}
 	}
 
 	/*
@@ -2880,6 +2921,15 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, index, val);
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		if (!gtt_storage_attached(indexRelation->rd_node.relNode))
+		{
+			gtt_force_enable_index(indexRelation);
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3475,6 +3525,15 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 				 errmsg("cannot reindex temporary tables of other sessions")));
 
 	/*
+	 * Because global temp table cannot change relfilenode
+	 * no support reindex on global temp table yet.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(iRel))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot reindex global temporary tables")));
+
+	/*
 	 * Also check for active uses of the index in the current transaction; we
 	 * don't want to reindex underneath an open indexscan.
 	 */
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 5ff7824..9b58f2a 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -647,6 +647,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		/* global temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp relations in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index fddfbf1..2982bc7 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -26,6 +26,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -75,7 +76,7 @@ static PendingRelDelete *pendingDeletes = NULL; /* head of linked list */
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -85,6 +86,8 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		/* global temp table use same storage strategy as local temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -117,6 +120,10 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/* remember global temp table storage info to localhash */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel)
+		remember_gtt_storage_info(rnode, rel);
+
 	return srel;
 }
 
@@ -486,8 +493,15 @@ smgrDoPendingDeletes(bool isCommit)
 		smgrdounlinkall(srels, nrels, false);
 
 		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* clean global temp table flags when transaction commit or rollback */
+			if (SmgrIsTemp(srels[i]) && 
+				gtt_storage_attached(srels[i]->smgr_rnode.node.relNode))
+				forget_gtt_storage_info(srels[i]->smgr_rnode.node.relNode, isCommit);
+		}
+
 		pfree(srels);
 	}
 }
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..04f7fcc
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1261 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  code to create and destroy physical storage for global temparary table
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_relstats_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+typedef struct gtt_ctl_data
+{
+	LWLock			lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct
+{
+	RelFileNode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+	Oid			relid;
+
+	Oid			spcnode;
+	/* pg_class stat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+	char		relkind;
+	bool		on_commit_delete;
+
+	/* pg_statistic */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(RelFileNode rnode);
+static void gtt_storage_checkout(RelFileNode rnode, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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_global_temporary_table.")));
+	}
+
+	if (found == false)
+	{
+		int			wordnum;
+
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(RelFileNode rnode, bool skiplock, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (!skiplock)
+		LWLockAcquire(&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);
+
+		if (isCommit)
+		{
+			elog(WARNING, "relfilenode %u/%u/%u not exist in gtt shared hash when forget",
+						rnode.dbNode, rnode.spcNode, rnode.relNode);
+		}
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+	bms_del_member(entry->map, MyBackendId);
+
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &rnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+
+	if (!skiplock)
+		LWLockRelease(&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;
+	int			natts = 0;
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		/* First time through: initialize the hash table */
+		HASHCTL		ctl;
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_relstats_context =
+			AllocSetContextCreate(CacheMemoryContext,
+							"gtt relstats context",
+							ALLOCSET_DEFAULT_SIZES);
+	}
+
+	/* Look up or create an entry */
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_ENTER, &found);
+
+	if (found)
+	{
+		elog(ERROR, "backend %d relid %u already exists in global temporary table local hash",
+					MyBackendId, relid);
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+	entry->spcnode = rnode.spcNode;
+	entry->relpages = 0;
+	entry->reltuples = 0;
+	entry->relallvisible = 0;
+	entry->relfrozenxid = InvalidTransactionId;
+	entry->relminmxid = InvalidMultiXactId;
+	entry->relkind = rel->rd_rel->relkind;
+	entry->on_commit_delete = false;
+	entry->natts = 0;
+	entry->attnum = NULL;
+	entry->att_stat_tups = NULL;
+
+	natts = RelationGetNumberOfAttributes(rel);
+	entry->attnum = palloc0(sizeof(int) * natts);
+	entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+	entry->natts = natts;
+	/* only heap contain transaction information */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+		{
+			entry->on_commit_delete = true;
+			register_on_commit_action(rel->rd_node.relNode, ONCOMMIT_DELETE_ROWS);
+		}
+
+		entry->relfrozenxid = RecentXmin;
+		entry->relminmxid = GetOldestMultiXactId();
+		insert_gtt_relfrozenxid_to_ordered_list(entry->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	if (entry->relkind == RELKIND_RELATION ||
+		entry->relkind == RELKIND_SEQUENCE)
+	{
+		gtt_storage_checkin(rnode);
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry)
+	{
+		int		i;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			Assert(TransactionIdIsNormal(entry->relfrozenxid) || !isCommit);
+			if (TransactionIdIsValid(entry->relfrozenxid))
+			{
+				remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+				set_gtt_session_relfrozenxid();
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			RelFileNode rnode;
+			rnode.spcNode = entry->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = entry->relid;
+			gtt_storage_checkout(rnode, false, isCommit);
+		}
+
+		for (i = 0; i < entry->natts; i++)
+		{
+			if (entry->att_stat_tups[i])
+			{
+				heap_freetuple(entry->att_stat_tups[i]);
+				entry->att_stat_tups[i] = NULL;
+			}
+		}
+
+		if (entry->attnum)
+			pfree(entry->attnum);
+
+		if (entry->att_stat_tups)
+			pfree(entry->att_stat_tups);
+	}
+
+	hash_search(gtt_storage_local_hash,
+			(void *) &(relid), HASH_REMOVE, NULL);
+
+	return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool found;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (gtt_storage_local_hash == NULL)
+		return false;
+
+	hash_search(gtt_storage_local_hash,
+			(void *) &(relid), HASH_FIND, &found);
+
+	return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS				status;
+	gtt_local_hash_entry		*entry;
+	int			nrels = 0,
+				maxrels = 0;
+	SMgrRelation	*srels = NULL;
+	RelFileNode		*rnodes = NULL;
+	char			*relkinds = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		SMgrRelation srel;
+		RelFileNode rnode;
+
+		rnode.spcNode = entry->spcnode;
+		rnode.dbNode = MyDatabaseId;
+		rnode.relNode = entry->relid;
+
+		srel = smgropen(rnode, MyBackendId);
+
+		/* allocate the initial array, or extend it, if needed */
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			srels = palloc(sizeof(SMgrRelation) * maxrels);
+			rnodes = palloc(sizeof(RelFileNode) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+			rnodes = repalloc(rnodes, sizeof(RelFileNode) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		srels[nrels] = srel;
+		rnodes[nrels] = rnode;
+		relkinds[nrels] = entry->relkind;
+		nrels++;
+	}
+
+	if (nrels > 0)
+	{
+		int i;
+
+		smgrdounlinkall(srels, nrels, false);
+
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+		for (i = 0; i < nrels; i++)
+		{
+			smgrclose(srels[i]);
+			if (relkinds[i] == RELKIND_RELATION ||
+				relkinds[i] == RELKIND_SEQUENCE)
+				gtt_storage_checkout(rnodes[i], true, false);
+		}
+		LWLockRelease(&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.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (gtt_storage_local_hash == NULL)
+		return false;
+
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_FIND, &found);
+
+	if (!found)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	if (relpages)
+		*relpages = entry->relpages;
+
+	if (reltuples)
+		*reltuples = entry->reltuples;
+
+	if (relallvisible)
+		*relallvisible = entry->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = entry->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = entry->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	MemoryContext			oldcontext;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+						(void *) &reloid, HASH_FIND, &found);
+
+	if (!found)
+		return;
+
+	/* todo */
+	if (entry->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	Assert(entry->relid == reloid);
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == 0)
+		{
+			entry->attnum[i] = attnum;
+			break;
+		}
+		else if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < entry->natts);
+	Assert(entry->att_stat_tups[i] == NULL);
+	entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+						(void *) &reloid, HASH_FIND, &found);
+
+	if (!found)
+		return NULL;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			return entry->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int			i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	HeapTuple	tuple;
+	Relation	rel = NULL;
+	int		attnum = PG_GETARG_INT32(1);
+	Oid		reloid = PG_GETARG_OID(0);
+	char		rel_persistence;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	  tupdesc;
+	MemoryContext oldcontext;
+	Tuplestorestate *tupstore;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = table_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+
+	table_close(rel, NoLock);
+	table_close(pg_tatistic, AccessShareLock);
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate	*tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Relation	rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	if (get_gtt_relstats(reloid,
+						&relpages, &reltuples, &relallvisible,
+						&relfrozenxid, &relminmxid))
+	{
+		Datum	values[5];
+		bool	isnull[5];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = Int32GetDatum(relpages);
+		values[1] = Float4GetDatum((float4)reltuples);
+		values[2] = Int32GetDatum(relallvisible);
+		values[3] = UInt32GetDatum(relfrozenxid);
+		values[4] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+
+	tuplestore_donestoring(tupstore);
+	table_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(rel->rd_node);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	table_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(indexOid));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid relOid = index->rd_index->indrelid;
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	if (gtt_storage_attached(relOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int			i;
+	Oid			toastrelid;
+
+	if (operation != CMD_INSERT)
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_attached(relation->rd_node.relNode))
+		return;
+
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+
+		index_build(relation, index, info, true, false);
+	}
+
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid 		indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo 	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index b8a3f46..491db16 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM 
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 924ef37..7efc5a9 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -184,6 +185,13 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(onerel->rd_node.relNode))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -586,14 +594,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 		 * pg_statistic for columns we didn't process, we leave them alone.)
 		 */
 		update_attstats(RelationGetRelid(onerel), inh,
-						attr_cnt, vacattrstats);
+						attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
 
 		for (ind = 0; ind < nindexes; ind++)
 		{
 			AnlIndexData *thisdata = &indexdata[ind];
 
 			update_attstats(RelationGetRelid(Irel[ind]), false,
-							thisdata->attr_cnt, thisdata->vacattrstats);
+							thisdata->attr_cnt, thisdata->vacattrstats,
+							RelationGetRelPersistence(Irel[ind]));
 		}
 
 		/*
@@ -1456,7 +1465,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
  *		by taking a self-exclusive lock on the relation in analyze_rel().
  */
 static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
 {
 	Relation	sd;
 	int			attno;
@@ -1558,31 +1567,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/* Update column statistic to localhash, not catalog */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 11ce1bb..552fd87 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -391,6 +391,12 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/* not support cluster global temp table yet */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("not support cluster global temporary tables yet")));
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index e79ede4..078694d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -27,6 +27,7 @@
 #include "catalog/dependency.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -1062,7 +1063,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, stmt->filename, stmt->is_program,
@@ -2840,6 +2841,8 @@ CopyFrom(CopyState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	estate->es_result_relations = resultRelInfo;
 	estate->es_num_result_relations = 1;
 	estate->es_result_relation_info = resultRelInfo;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 3f3a89f..928c01e 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2586,6 +2586,16 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 			!isTempNamespace(classtuple->relnamespace))
 			continue;
 
+		/* not support reindex on global temp table, so skip it */
+		if (classtuple->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			ereport(WARNING,
+				(errmsg("global temp table \"%s.%s\" skip reindexed",
+					get_namespace_name(get_rel_namespace(relid)),
+					get_rel_name(relid))));
+			continue;
+		}
+
 		/* Check user/system classification, and optionally skip */
 		if (objectKind == REINDEX_OBJECT_SYSTEM &&
 			!IsSystemClass(relid, classtuple))
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index d8cafc4..deecde8 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -96,7 +96,8 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 	 * transaction.
 	 */
 	relpersistence = get_rel_persistence(relid);
-	if (relpersistence == RELPERSISTENCE_TEMP)
+	if (relpersistence == RELPERSISTENCE_TEMP ||
+		relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
 
 	/* Check permissions. */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..fada1ac 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -94,7 +96,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -222,7 +225,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* now initialize the sequence's data */
 	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -327,7 +330,7 @@ ResetSequence(Oid seq_relid)
 	/*
 	 * Insert the modified tuple into the new storage file.
 	 */
-	fill_seq_with_data(seq_rel, tuple);
+	fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -340,17 +343,21 @@ ResetSequence(Oid seq_relid)
  * Initialize a sequence's relation with the specified tuple as content
  */
 static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 	sequence_magic *sm;
 	OffsetNumber offnum;
+	bool lockBuffer = false;
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ReadBuffer(rel, P_NEW);
-	Assert(BufferGetBlockNumber(buf) == 0);
+	if (buf == InvalidBuffer)
+	{
+		buf = ReadBuffer(rel, P_NEW);
+		Assert(BufferGetBlockNumber(buf) == 0);
+		lockBuffer = true;
+	}
 
 	page = BufferGetPage(buf);
 
@@ -360,7 +367,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* Now insert sequence tuple */
 
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+	if (lockBuffer)
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +418,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	END_CRIT_SECTION();
 
-	UnlockReleaseBuffer(buf);
+	if (lockBuffer)
+		UnlockReleaseBuffer(buf);
 }
 
 /*
@@ -502,7 +511,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * Insert the modified tuple into the new storage file.
 		 */
-		fill_seq_with_data(seqrel, newdatatuple);
+		fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
 	}
 
 	/* process OWNED BY if given */
@@ -611,7 +620,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +945,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1154,6 +1163,13 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(seqrel->rd_node.relNode))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1954,3 +1970,46 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+void
+gtt_init_seq(Relation rel)
+{
+	/* Initialize sequence for global temporary tables */
+	Datum		value[SEQ_COL_LASTCOL] = {0};
+	bool		null[SEQ_COL_LASTCOL] = {false};
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 3eb861b..c046734 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -553,6 +554,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 								  Relation partitionTbl);
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static OnCommitAction gtt_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -598,6 +600,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -608,8 +611,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Check consistency of arguments
 	 */
+	/* global temp table same as local temp table */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !(stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+			 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -639,7 +644,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	/* global temp table same as local temp table */
+	if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+		 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -740,6 +747,55 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* global temp table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* check parent table*/
+		if (inheritOids)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			table_close(relation, NoLock);
+		}
+
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "can not defeine global temp table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temp table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1824,7 +1880,8 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
 		 * table or the current physical file to be thrown away anyway.
 		 */
 		if (rel->rd_createSubid == mySubid ||
-			rel->rd_newRelfilenodeSubid == mySubid)
+			rel->rd_newRelfilenodeSubid == mySubid ||
+			RELATION_IS_GLOBAL_TEMP(rel))
 		{
 			/* Immediate, non-rollbackable truncation is OK */
 			heap_truncate_one_rel(rel);
@@ -3567,6 +3624,14 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(rel->rd_node))
+			elog(ERROR, "can not alter table %s when other backend attached this global temp table",
+						RelationGetRelationName(rel));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -8179,6 +8244,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -12645,6 +12716,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -12847,6 +12921,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temp table");
+
 	/*
 	 * No work if no change in tablespace.
 	 */
@@ -13219,7 +13296,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,
@@ -14626,7 +14703,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",
@@ -17218,3 +17297,28 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
 		table_close(rel, NoLock);
 	}
 }
+
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			if (defGetBoolean(def))
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index d625d17..f0860cc 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/vacuum.h"
@@ -1217,6 +1218,17 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	 /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		is_gtt = true;
+		up_gtt_relstats(relation,
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1230,17 +1242,26 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relpages = (int32) num_pages;
+	else if (pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (is_gtt)
+		relation->rd_rel->reltuples = (float4) num_tuples;
+	else if (pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	else if (pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1285,7 +1306,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1296,7 +1318,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1394,6 +1417,10 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/* global temp table relstats not in pg_class */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1451,6 +1478,25 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/*
+	 * Global temp table get frozenxid from MyProc
+	 * to avoid the vacuum truncate clog that gtt need.
+	 */
+	if (max_active_gtt > 0)
+	{
+		TransactionId oldest_gtt_frozenxid =
+			list_all_session_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid) &&
+			TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+		{
+			ereport(WARNING,
+				(errmsg("global temp table oldest FrozenXid is far in the past"),
+				 errhint("please truncate them or kill those sessions that use them.")));
+			newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1778,6 +1824,18 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(onerel->rd_node.relNode))
+	{
+		ereport(WARNING,
+				(errmsg("skipping vacuum global temp table \"%s\" because storage is not initialized for current session",
+						RelationGetRelationName(onerel))));
+		relation_close(onerel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6e65103..04706ee 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -530,6 +530,11 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 28130fb..dcb4a0b 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index c13b1d3..253054d 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
@@ -543,6 +544,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	init_gtt_storage(mtstate->operation, leaf_part_rri);
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d71c0a4..0900907 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,6 +42,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -2372,6 +2373,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			ExecOpenIndices(resultRelInfo,
 							node->onConflictAction != ONCONFLICT_NONE);
 
+		init_gtt_storage(operation, resultRelInfo);
+
 		/*
 		 * If this is an UPDATE and a BEFORE UPDATE trigger is present, the
 		 * trigger itself might modify the partition-key values. So arrange
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 905bbe7..170963d 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -591,6 +591,8 @@ static void
 set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 						  RangeTblEntry *rte)
 {
+	char relpersistence;
+
 	/*
 	 * The flag has previously been initialized to false, so we can just
 	 * return if it becomes clear that we can't safely set it.
@@ -618,7 +620,11 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			/* global temp table is same as local temp table */
+			relpersistence = get_rel_persistence(rte->relid);
+
+			if (relpersistence == RELPERSISTENCE_TEMP ||
+				relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b44efd6..14dbaaf 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6307,7 +6307,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
+	/* global temp table is same as local temp table */
 	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index d82fc5a..93c6d97 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -28,6 +28,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temp table */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(indexRelation->rd_node.relNode))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6676412..0685c1c 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2585,6 +2585,11 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("materialized views must not use temporary tables or views")));
 
+		if (is_query_using_gtt(query))
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("materialized views must not use global temporary tables or views")));
+
 		/*
 		 * A materialized view would either need to save parameters for use in
 		 * maintaining/loading the data or prohibit them entirely.  The latter
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7e384f9..6ac4aa0 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3291,17 +3291,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; }
@@ -11613,19 +11607,13 @@ OptTempTableName:
 				}
 			| GLOBAL TEMPORARY opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED opt_table qualified_name
 				{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b875a50..123945b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index af77f18..67d34f0 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index e3a43d3..ca460ca 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2087,6 +2087,11 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* autovacuum skip vacuum global temp table */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2153,7 +2158,9 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		/* autovacuum skip vacuum global temp table */
+		if (classForm->relpersistence == RELPERSISTENCE_TEMP || 
+			classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 5880054..98f7b43 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -52,6 +53,7 @@
 #include "utils/rel.h"
 #include "utils/resowner_private.h"
 #include "utils/timestamp.h"
+#include "utils/guc.h"
 
 
 /* Note: these two macros only work on shared buffers, not local ones! */
@@ -2809,6 +2811,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(relation->rd_node.relNode))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d5..7adb969 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -21,6 +21,7 @@
 #include "access/nbtree.h"
 #include "access/subtrans.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, active_gtt_shared_hash_size());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* global temporary table */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 4a5b26c..9754168 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -62,6 +62,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -4087,3 +4088,77 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temp table.
+ */
+int
+list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct *arrayP = procArray;
+	TransactionId result = InvalidTransactionId;
+	int			index;
+	int			flags = 0;
+	int			i = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+		*n = 0;
+	}
+
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		volatile PGXACT *pgxact = &allPgXact[pgprocno];
+
+		if (pgxact->vacuumFlags & flags)
+			continue;
+
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->session_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->session_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->session_gtt_frozenxid, result))
+				result = proc->session_gtt_frozenxid;
+
+			if (max_size > 0)
+			{
+				pids[i] = proc->pid;
+				xids[i] = proc->session_gtt_frozenxid;
+				i++;
+			}
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (max_size > 0)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index eb321f7..3893cef 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 8406644..0d99321 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -1008,6 +1008,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 0be26fe..c169c99 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -112,6 +112,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -4570,12 +4571,25 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -4700,15 +4714,27 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6090,6 +6116,7 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	{
 		/* Simple variable --- look to stats for the underlying table */
 		RangeTblEntry *rte = planner_rt_fetch(index->rel->relid, root);
+		char	rel_persistence = get_rel_persistence(rte->relid);
 
 		Assert(rte->rtekind == RTE_RELATION);
 		relid = rte->relid;
@@ -6107,6 +6134,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6118,6 +6152,8 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	}
 	else
 	{
+		char	rel_persistence = get_rel_persistence(index->indexoid);
+
 		/* Expression --- maybe there are stats for the index itself */
 		relid = index->indexoid;
 		colnum = 1;
@@ -6133,6 +6169,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7046,6 +7089,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		/* attempt to lookup stats in relation for this index column */
 		if (attnum != 0)
 		{
+			char	rel_persistence = get_rel_persistence(rte->relid);
+
 			/* Simple variable -- look to stats for the underlying table */
 			if (get_relation_stats_hook &&
 				(*get_relation_stats_hook) (root, rte, attnum, &vardata))
@@ -7058,6 +7103,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7070,6 +7123,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		}
 		else
 		{
+			char	rel_persistence = get_rel_persistence(index->indexoid);
+
 			/*
 			 * Looks like we've found an expression column in the index. Let's
 			 * see if there's any stats for it.
@@ -7089,6 +7144,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 3da90cb..97240ff 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -2878,6 +2879,18 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index ff70326..1cf7063 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -63,6 +63,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1127,6 +1128,24 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1181,6 +1200,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			gtt_fix_index_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -2220,6 +2240,8 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		gtt_fix_index_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3316,6 +3338,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3430,6 +3456,9 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
 
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+		elog(ERROR, "global temp table does not allow setting new relfilenode");
+
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
@@ -3470,7 +3499,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 				/* handle these directly, at least for now */
 				SMgrRelation srel;
 
-				srel = RelationCreateStorage(newrnode, persistence);
+				srel = RelationCreateStorage(newrnode, persistence, relation);
 				smgrclose(srel);
 			}
 			break;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index c1fad3b..c60fa31 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -140,6 +140,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temp table feature.
+ * global temp table define can still storage in catalog
+ * just can not use.
+ * num > 0 means database can management num active global temp table.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2047,6 +2059,15 @@ static struct config_bool ConfigureNamesBool[] =
 static struct config_int ConfigureNamesInt[] =
 {
 	{
+		{"max_active_global_temporary_table", PGC_POSTMASTER, UNGROUPED,
+			gettext_noop("max active global temporary table."),
+			NULL
+		},
+		&max_active_gtt,
+		DEFAULT_NUM_ACTIVE_GTT, MIN_NUM_ACTIVE_GTT, MAX_NUM_ACTIVE_GTT,
+		NULL, NULL, NULL
+	},
+	{
 		{"archive_timeout", PGC_SIGHUP, WAL_ARCHIVING,
 			gettext_noop("Forces a switch to the next WAL file if a "
 						 "new file has not been started within N seconds."),
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a12c8d0..58b22ea 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15595,6 +15595,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	{
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
+		char		*table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15646,9 +15647,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 			binary_upgrade_set_pg_class_oids(fout, q,
 											 tbinfo->dobj.catId.oid, false);
 
+		if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED)
+			table_type = "UNLOGGED ";
+		else if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			table_type = "GLOBAL TEMPORARY ";
+		else
+			table_type = "";
+
 		appendPQExpBuffer(q, "CREATE %s%s %s",
-						  tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ?
-						  "UNLOGGED " : "",
+						  table_type,
 						  reltypename,
 						  qualrelname);
 
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 5f9a102..3e40297 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -84,7 +84,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -157,7 +157,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 7e524ea..371168f 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -441,22 +443,46 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 	 * output, so we have to copy that system table.  It's easiest to do that
 	 * by treating it as a user table.
 	 */
-	snprintf(query + strlen(query), sizeof(query) - strlen(query),
-			 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
-			 "  SELECT c.oid, 0::oid, 0::oid "
-			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
-			 "         ON c.relnamespace = n.oid "
-			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
-	/* exclude possible orphaned temp tables */
-			 "    ((n.nspname !~ '^pg_temp_' AND "
-			 "      n.nspname !~ '^pg_toast_temp_' AND "
-			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
-			 "                        'binary_upgrade', 'pg_toast') AND "
-			 "      c.oid >= %u::pg_catalog.oid) OR "
-			 "     (n.nspname = 'pg_catalog' AND "
-			 "      relname IN ('pg_largeobject') ))), ",
-			 FirstNormalObjectId);
+	if (skip_gtt)
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude global temp tables */
+				 "    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
+	else
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
 
 	/*
 	 * Add a CTE that collects OIDs of toast tables belonging to the tables
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 70194eb..8dd9e66 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -411,7 +411,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -642,7 +642,9 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -653,7 +655,9 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 8b90cef..d155205 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -389,7 +389,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 109245f..557cce1 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3724,7 +3724,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 174c3db..80471d0 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1023,6 +1023,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2386,6 +2388,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2594,6 +2599,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 78b33b2..4be89f6 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -168,6 +168,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 7fb574f..6f8591c 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5507,6 +5507,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4191',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4192',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{relid,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4193',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4194',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 048003c..af48cdf 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -19,7 +19,7 @@
 #include "storage/smgr.h"
 #include "utils/relcache.h"
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationTruncate(Relation rel, BlockNumber nblocks);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..63f7241
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,43 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, bool isCommit);
+extern bool is_other_backend_use_gtt(RelFileNode node);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(RelFileNode node);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Relation relation,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void gtt_force_enable_index(Relation index);
+extern void gtt_fix_index_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index e2638ab..89a5ce4 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 3f88683..f870e9a 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 8fda8e4..172eae9 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_TBM,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_SXACT,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index d217801..8adde87 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -117,6 +117,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId session_gtt_frozenxid;	/* session level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ce93ace..0f7262e 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -281,6 +281,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 44ed04d..6b6d2da 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	struct SMgrRelationData *rd_smgr;	/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -277,6 +277,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -535,11 +536,13 @@ typedef struct ViewOptions
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -547,6 +550,7 @@ typedef struct ViewOptions
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -559,6 +563,14 @@ typedef struct ViewOptions
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is either local temp relation of this session
+ * 		or global temp relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -602,6 +614,17 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..73d3d7b
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,8 @@
+reset search_path;
+drop schema gtt cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
+drop cascades to table gtt.gtt_with_seq
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..220ec04
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,330 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+ n 
+---
+ 9
+(1 row)
+
+commit;
+-- 0 row
+select * from gtt6;
+ n 
+---
+(0 rows)
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+ERROR:  cannot create indexes on global temporary tables using concurrent mode
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+ERROR:  not support cluster global temporary tables yet
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temp table
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  can not defeine global temp table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test;
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR:  insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count 
+-------
+     1
+(1 row)
+
+select count(*) from orders;
+ count 
+-------
+     0
+(1 row)
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 1)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 300)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 20 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.gtt6
+drop cascades to table gtt_function.foo
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to table gtt_function.gt1
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..0646aae
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,90 @@
+set search_path=gtt,sys;
+select nextval('gtt_with_seq_c2_seq');
+ nextval 
+---------
+       1
+(1 row)
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..b2ef23b
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,168 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          114688 |                 614400
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |            49152 |         49152 |                  49152
+(2 rows)
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+ c1 | c2 
+----+----
+  1 |  1
+(1 row)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..8c0c376
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,10 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..0e21b30
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,76 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+(1 row)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+(1 row)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index c730461..8a39594 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1349,6 +1349,93 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d2b17dd..80e577f 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..f3cf710
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,6 @@
+
+
+reset search_path;
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..f599a10
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,224 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+commit;
+-- 0 row
+select * from gtt6;
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test;
+
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d05745e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,44 @@
+
+
+set search_path=gtt,sys;
+
+select nextval('gtt_with_seq_c2_seq');
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..bf0b922
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,79 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..dbe84d1
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,19 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..f041892
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,42 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
#180曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: tushar (#177)
1 attachment(s)
Re: [Proposal] Global temporary tables

2020年3月9日 下午9:34,tushar <tushar.ahuja@enterprisedb.com> 写道:

On 3/6/20 12:35 PM, 曾文旌(义从) wrote:

Fixed in global_temporary_table_v17-pg13.patch

Thanks Wenjing.

Please refer this scenario , where i am able to set 'on_commit_delete_rows=true' on regular table using 'alter' Syntax which is not allowed using 'Create' Syntax

--Expected -

postgres=# CREATE TABLE foo () WITH (on_commit_delete_rows='true');
ERROR: The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table

--But user can do this with 'alter' command -
postgres=# create table foo();
CREATE TABLE
postgres=# alter table foo set (on_commit_delete_rows='true');
ALTER TABLE

This is a bug ,I fixed.

Wenjing

Attachments:

global_temporary_table_v18-pg13.patchapplication/octet-stream; name=global_temporary_table_v18-pg13.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index c3d45c7..3f9e875 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -168,6 +168,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * For global temp table only
+	 * use ShareUpdateExclusiveLock for ensure safety
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temp table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},	
 	/* list terminator */
 	{{NULL}}
 };
@@ -1496,6 +1509,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1596,13 +1611,18 @@ build_reloptions(Datum reloptions, bool validate,
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	/*
 	 * There are no options for partitioned tables yet, but this is able to do
 	 * some validation.
 	 */
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index dd975b1..1610e7d 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1013,7 +1013,9 @@ gistGetFakeLSN(Relation rel)
 {
 	static XLogRecPtr counter = FirstNormalUnloggedLSN;
 
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	/* global temp is same as local temp table */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 4871b7f..16b00c9 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -149,7 +149,9 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	/* global temp table is same as local temp table */
+	if (!(index->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		index->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ca52846..f0153cf 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -598,7 +598,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -651,7 +651,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 03c43ef..2674132 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -62,6 +62,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -399,9 +400,9 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
 	Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
-	/* not every AM requires these to be valid, but heap does */
-	Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
-	Assert(MultiXactIdIsValid(onerel->rd_rel->relminmxid));
+	/* not every AM requires these to be valid, but regular heap does */
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ MultiXactIdIsValid(onerel->rd_rel->relminmxid));
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 39b8f17..38b46d0 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -601,6 +602,10 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(rel->rd_node.relNode))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index b0e953f..e366507 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6348,6 +6348,10 @@ StartupXLOG(void)
 	else
 		recoveryTargetTLI = ControlFile->checkPointCopy.ThisTimeLineID;
 
+	/* clean temp relation files */
+	if (max_active_gtt > 0)
+		RemovePgTempFiles();
+
 	/*
 	 * Check for signal files, and if so set up state for offline recovery
 	 */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index f8f0b48..dc8bbb1 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -42,6 +42,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 7d6acae..3be8d63 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -393,7 +393,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/* global temp table is same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 9d9e915..1c50263 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -427,7 +429,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -957,6 +959,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -995,8 +998,18 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/* global temp table not remember transaction info in catalog */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1358,6 +1371,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1938,6 +1952,14 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/* We allow to drop global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(rel->rd_node))
+			elog(ERROR, "can not drop relation %s when other backend attached this global temp table",
+						RelationGetRelationName(rel));
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3165,7 +3187,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3177,7 +3199,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3223,8 +3245,13 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		/* truncate global temp table only need RowExclusiveLock */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3257,6 +3284,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3265,23 +3293,37 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (!gtt_storage_attached(rel->rd_node.relNode))
+			return;
+
+		/*
+		 * Truncate global temp table only need RowExclusiveLock
+		 */
+		lockmode = RowExclusiveLock;
+	}
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(rel, 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 7223679..2a0144e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -52,6 +52,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -877,6 +878,19 @@ index_create(Relation heapRelation,
 						indexRelationName, RelationGetRelationName(heapRelation))));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temp table use concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot create indexes on global temporary tables using concurrent mode")));
+
+		/* if global temp table not init storage, then skip build index */
+		if (!gtt_storage_attached(heapRelation->rd_node.relNode))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -2061,6 +2075,14 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/* We allow to drop index on global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation))
+	{
+		if (is_other_backend_use_gtt(userHeapRelation->rd_node))
+			elog(ERROR, "can not drop index %s when other backend attached this global temp table.",
+						RelationGetRelationName(userHeapRelation));
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2667,6 +2689,11 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	/* update index stats into localhash and rel_rd_rel for global temp table */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		is_gtt = true;
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2752,21 +2779,35 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
+		if (is_gtt)
+			rel->rd_rel->relpages = (int32) relpages;
+		else if (rd_rel->relpages != (int32) relpages)
 		{
 			rd_rel->relpages = (int32) relpages;
 			dirty = true;
 		}
-		if (rd_rel->reltuples != (float4) reltuples)
+
+		if (is_gtt)
+			rel->rd_rel->reltuples = (float4) reltuples;
+		else if (rd_rel->reltuples != (float4) reltuples)
 		{
 			rd_rel->reltuples = (float4) reltuples;
 			dirty = true;
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+
+		if (is_gtt)
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+		else if (rd_rel->relallvisible != (int32) relallvisible)
 		{
 			rd_rel->relallvisible = (int32) relallvisible;
 			dirty = true;
 		}
+
+		if (is_gtt)
+		{
+			up_gtt_relstats(rel, relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
+		}
 	}
 
 	/*
@@ -2880,6 +2921,15 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, index, val);
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		if (!gtt_storage_attached(indexRelation->rd_node.relNode))
+		{
+			gtt_force_enable_index(indexRelation);
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3475,6 +3525,15 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 				 errmsg("cannot reindex temporary tables of other sessions")));
 
 	/*
+	 * Because global temp table cannot change relfilenode
+	 * no support reindex on global temp table yet.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(iRel))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot reindex global temporary tables")));
+
+	/*
 	 * Also check for active uses of the index in the current transaction; we
 	 * don't want to reindex underneath an open indexscan.
 	 */
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 5ff7824..9b58f2a 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -647,6 +647,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		/* global temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp relations in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index fddfbf1..2982bc7 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -26,6 +26,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -75,7 +76,7 @@ static PendingRelDelete *pendingDeletes = NULL; /* head of linked list */
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -85,6 +86,8 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		/* global temp table use same storage strategy as local temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -117,6 +120,10 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/* remember global temp table storage info to localhash */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel)
+		remember_gtt_storage_info(rnode, rel);
+
 	return srel;
 }
 
@@ -486,8 +493,15 @@ smgrDoPendingDeletes(bool isCommit)
 		smgrdounlinkall(srels, nrels, false);
 
 		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* clean global temp table flags when transaction commit or rollback */
+			if (SmgrIsTemp(srels[i]) && 
+				gtt_storage_attached(srels[i]->smgr_rnode.node.relNode))
+				forget_gtt_storage_info(srels[i]->smgr_rnode.node.relNode, isCommit);
+		}
+
 		pfree(srels);
 	}
 }
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..04f7fcc
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1261 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  code to create and destroy physical storage for global temparary table
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_relstats_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+typedef struct gtt_ctl_data
+{
+	LWLock			lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct
+{
+	RelFileNode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+	Oid			relid;
+
+	Oid			spcnode;
+	/* pg_class stat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+	char		relkind;
+	bool		on_commit_delete;
+
+	/* pg_statistic */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(RelFileNode rnode);
+static void gtt_storage_checkout(RelFileNode rnode, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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_global_temporary_table.")));
+	}
+
+	if (found == false)
+	{
+		int			wordnum;
+
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(RelFileNode rnode, bool skiplock, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (!skiplock)
+		LWLockAcquire(&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);
+
+		if (isCommit)
+		{
+			elog(WARNING, "relfilenode %u/%u/%u not exist in gtt shared hash when forget",
+						rnode.dbNode, rnode.spcNode, rnode.relNode);
+		}
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+	bms_del_member(entry->map, MyBackendId);
+
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &rnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+
+	if (!skiplock)
+		LWLockRelease(&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;
+	int			natts = 0;
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		/* First time through: initialize the hash table */
+		HASHCTL		ctl;
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_relstats_context =
+			AllocSetContextCreate(CacheMemoryContext,
+							"gtt relstats context",
+							ALLOCSET_DEFAULT_SIZES);
+	}
+
+	/* Look up or create an entry */
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_ENTER, &found);
+
+	if (found)
+	{
+		elog(ERROR, "backend %d relid %u already exists in global temporary table local hash",
+					MyBackendId, relid);
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+	entry->spcnode = rnode.spcNode;
+	entry->relpages = 0;
+	entry->reltuples = 0;
+	entry->relallvisible = 0;
+	entry->relfrozenxid = InvalidTransactionId;
+	entry->relminmxid = InvalidMultiXactId;
+	entry->relkind = rel->rd_rel->relkind;
+	entry->on_commit_delete = false;
+	entry->natts = 0;
+	entry->attnum = NULL;
+	entry->att_stat_tups = NULL;
+
+	natts = RelationGetNumberOfAttributes(rel);
+	entry->attnum = palloc0(sizeof(int) * natts);
+	entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+	entry->natts = natts;
+	/* only heap contain transaction information */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+		{
+			entry->on_commit_delete = true;
+			register_on_commit_action(rel->rd_node.relNode, ONCOMMIT_DELETE_ROWS);
+		}
+
+		entry->relfrozenxid = RecentXmin;
+		entry->relminmxid = GetOldestMultiXactId();
+		insert_gtt_relfrozenxid_to_ordered_list(entry->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	if (entry->relkind == RELKIND_RELATION ||
+		entry->relkind == RELKIND_SEQUENCE)
+	{
+		gtt_storage_checkin(rnode);
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry)
+	{
+		int		i;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			Assert(TransactionIdIsNormal(entry->relfrozenxid) || !isCommit);
+			if (TransactionIdIsValid(entry->relfrozenxid))
+			{
+				remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+				set_gtt_session_relfrozenxid();
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			RelFileNode rnode;
+			rnode.spcNode = entry->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = entry->relid;
+			gtt_storage_checkout(rnode, false, isCommit);
+		}
+
+		for (i = 0; i < entry->natts; i++)
+		{
+			if (entry->att_stat_tups[i])
+			{
+				heap_freetuple(entry->att_stat_tups[i]);
+				entry->att_stat_tups[i] = NULL;
+			}
+		}
+
+		if (entry->attnum)
+			pfree(entry->attnum);
+
+		if (entry->att_stat_tups)
+			pfree(entry->att_stat_tups);
+	}
+
+	hash_search(gtt_storage_local_hash,
+			(void *) &(relid), HASH_REMOVE, NULL);
+
+	return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool found;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (gtt_storage_local_hash == NULL)
+		return false;
+
+	hash_search(gtt_storage_local_hash,
+			(void *) &(relid), HASH_FIND, &found);
+
+	return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS				status;
+	gtt_local_hash_entry		*entry;
+	int			nrels = 0,
+				maxrels = 0;
+	SMgrRelation	*srels = NULL;
+	RelFileNode		*rnodes = NULL;
+	char			*relkinds = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		SMgrRelation srel;
+		RelFileNode rnode;
+
+		rnode.spcNode = entry->spcnode;
+		rnode.dbNode = MyDatabaseId;
+		rnode.relNode = entry->relid;
+
+		srel = smgropen(rnode, MyBackendId);
+
+		/* allocate the initial array, or extend it, if needed */
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			srels = palloc(sizeof(SMgrRelation) * maxrels);
+			rnodes = palloc(sizeof(RelFileNode) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+			rnodes = repalloc(rnodes, sizeof(RelFileNode) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		srels[nrels] = srel;
+		rnodes[nrels] = rnode;
+		relkinds[nrels] = entry->relkind;
+		nrels++;
+	}
+
+	if (nrels > 0)
+	{
+		int i;
+
+		smgrdounlinkall(srels, nrels, false);
+
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+		for (i = 0; i < nrels; i++)
+		{
+			smgrclose(srels[i]);
+			if (relkinds[i] == RELKIND_RELATION ||
+				relkinds[i] == RELKIND_SEQUENCE)
+				gtt_storage_checkout(rnodes[i], true, false);
+		}
+		LWLockRelease(&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.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (gtt_storage_local_hash == NULL)
+		return false;
+
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_FIND, &found);
+
+	if (!found)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	if (relpages)
+		*relpages = entry->relpages;
+
+	if (reltuples)
+		*reltuples = entry->reltuples;
+
+	if (relallvisible)
+		*relallvisible = entry->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = entry->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = entry->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	MemoryContext			oldcontext;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+						(void *) &reloid, HASH_FIND, &found);
+
+	if (!found)
+		return;
+
+	/* todo */
+	if (entry->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	Assert(entry->relid == reloid);
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == 0)
+		{
+			entry->attnum[i] = attnum;
+			break;
+		}
+		else if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < entry->natts);
+	Assert(entry->att_stat_tups[i] == NULL);
+	entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+						(void *) &reloid, HASH_FIND, &found);
+
+	if (!found)
+		return NULL;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			return entry->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int			i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	HeapTuple	tuple;
+	Relation	rel = NULL;
+	int		attnum = PG_GETARG_INT32(1);
+	Oid		reloid = PG_GETARG_OID(0);
+	char		rel_persistence;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	  tupdesc;
+	MemoryContext oldcontext;
+	Tuplestorestate *tupstore;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = table_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+
+	table_close(rel, NoLock);
+	table_close(pg_tatistic, AccessShareLock);
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate	*tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Relation	rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	if (get_gtt_relstats(reloid,
+						&relpages, &reltuples, &relallvisible,
+						&relfrozenxid, &relminmxid))
+	{
+		Datum	values[5];
+		bool	isnull[5];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = Int32GetDatum(relpages);
+		values[1] = Float4GetDatum((float4)reltuples);
+		values[2] = Int32GetDatum(relallvisible);
+		values[3] = UInt32GetDatum(relfrozenxid);
+		values[4] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+
+	tuplestore_donestoring(tupstore);
+	table_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(rel->rd_node);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	table_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(indexOid));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid relOid = index->rd_index->indrelid;
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	if (gtt_storage_attached(relOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int			i;
+	Oid			toastrelid;
+
+	if (operation != CMD_INSERT)
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_attached(relation->rd_node.relNode))
+		return;
+
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+
+		index_build(relation, index, info, true, false);
+	}
+
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid 		indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo 	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index b8a3f46..491db16 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM 
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 924ef37..7efc5a9 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -184,6 +185,13 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(onerel->rd_node.relNode))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -586,14 +594,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 		 * pg_statistic for columns we didn't process, we leave them alone.)
 		 */
 		update_attstats(RelationGetRelid(onerel), inh,
-						attr_cnt, vacattrstats);
+						attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
 
 		for (ind = 0; ind < nindexes; ind++)
 		{
 			AnlIndexData *thisdata = &indexdata[ind];
 
 			update_attstats(RelationGetRelid(Irel[ind]), false,
-							thisdata->attr_cnt, thisdata->vacattrstats);
+							thisdata->attr_cnt, thisdata->vacattrstats,
+							RelationGetRelPersistence(Irel[ind]));
 		}
 
 		/*
@@ -1456,7 +1465,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
  *		by taking a self-exclusive lock on the relation in analyze_rel().
  */
 static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
 {
 	Relation	sd;
 	int			attno;
@@ -1558,31 +1567,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/* Update column statistic to localhash, not catalog */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 11ce1bb..552fd87 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -391,6 +391,12 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/* not support cluster global temp table yet */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("not support cluster global temporary tables yet")));
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index e79ede4..078694d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -27,6 +27,7 @@
 #include "catalog/dependency.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -1062,7 +1063,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, stmt->filename, stmt->is_program,
@@ -2840,6 +2841,8 @@ CopyFrom(CopyState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	estate->es_result_relations = resultRelInfo;
 	estate->es_num_result_relations = 1;
 	estate->es_result_relation_info = resultRelInfo;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 3f3a89f..928c01e 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2586,6 +2586,16 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 			!isTempNamespace(classtuple->relnamespace))
 			continue;
 
+		/* not support reindex on global temp table, so skip it */
+		if (classtuple->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			ereport(WARNING,
+				(errmsg("global temp table \"%s.%s\" skip reindexed",
+					get_namespace_name(get_rel_namespace(relid)),
+					get_rel_name(relid))));
+			continue;
+		}
+
 		/* Check user/system classification, and optionally skip */
 		if (objectKind == REINDEX_OBJECT_SYSTEM &&
 			!IsSystemClass(relid, classtuple))
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index d8cafc4..deecde8 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -96,7 +96,8 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 	 * transaction.
 	 */
 	relpersistence = get_rel_persistence(relid);
-	if (relpersistence == RELPERSISTENCE_TEMP)
+	if (relpersistence == RELPERSISTENCE_TEMP ||
+		relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
 
 	/* Check permissions. */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..fada1ac 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -94,7 +96,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -222,7 +225,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* now initialize the sequence's data */
 	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -327,7 +330,7 @@ ResetSequence(Oid seq_relid)
 	/*
 	 * Insert the modified tuple into the new storage file.
 	 */
-	fill_seq_with_data(seq_rel, tuple);
+	fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -340,17 +343,21 @@ ResetSequence(Oid seq_relid)
  * Initialize a sequence's relation with the specified tuple as content
  */
 static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 	sequence_magic *sm;
 	OffsetNumber offnum;
+	bool lockBuffer = false;
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ReadBuffer(rel, P_NEW);
-	Assert(BufferGetBlockNumber(buf) == 0);
+	if (buf == InvalidBuffer)
+	{
+		buf = ReadBuffer(rel, P_NEW);
+		Assert(BufferGetBlockNumber(buf) == 0);
+		lockBuffer = true;
+	}
 
 	page = BufferGetPage(buf);
 
@@ -360,7 +367,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* Now insert sequence tuple */
 
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+	if (lockBuffer)
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +418,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	END_CRIT_SECTION();
 
-	UnlockReleaseBuffer(buf);
+	if (lockBuffer)
+		UnlockReleaseBuffer(buf);
 }
 
 /*
@@ -502,7 +511,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * Insert the modified tuple into the new storage file.
 		 */
-		fill_seq_with_data(seqrel, newdatatuple);
+		fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
 	}
 
 	/* process OWNED BY if given */
@@ -611,7 +620,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +945,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1154,6 +1163,13 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(seqrel->rd_node.relNode))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1954,3 +1970,46 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+void
+gtt_init_seq(Relation rel)
+{
+	/* Initialize sequence for global temporary tables */
+	Datum		value[SEQ_COL_LASTCOL] = {0};
+	bool		null[SEQ_COL_LASTCOL] = {false};
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 3eb861b..c046734 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -553,6 +554,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 								  Relation partitionTbl);
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static OnCommitAction gtt_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -598,6 +600,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -608,8 +611,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Check consistency of arguments
 	 */
+	/* global temp table same as local temp table */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !(stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+			 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -639,7 +644,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	/* global temp table same as local temp table */
+	if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+		 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -740,6 +747,55 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* global temp table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* check parent table*/
+		if (inheritOids)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			table_close(relation, NoLock);
+		}
+
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "can not defeine global temp table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temp table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1824,7 +1880,8 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
 		 * table or the current physical file to be thrown away anyway.
 		 */
 		if (rel->rd_createSubid == mySubid ||
-			rel->rd_newRelfilenodeSubid == mySubid)
+			rel->rd_newRelfilenodeSubid == mySubid ||
+			RELATION_IS_GLOBAL_TEMP(rel))
 		{
 			/* Immediate, non-rollbackable truncation is OK */
 			heap_truncate_one_rel(rel);
@@ -3567,6 +3624,14 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(rel->rd_node))
+			elog(ERROR, "can not alter table %s when other backend attached this global temp table",
+						RelationGetRelationName(rel));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -8179,6 +8244,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -12645,6 +12716,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -12847,6 +12921,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temp table");
+
 	/*
 	 * No work if no change in tablespace.
 	 */
@@ -13219,7 +13296,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,
@@ -14626,7 +14703,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",
@@ -17218,3 +17297,28 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
 		table_close(rel, NoLock);
 	}
 }
+
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			if (defGetBoolean(def))
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index d625d17..f0860cc 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/vacuum.h"
@@ -1217,6 +1218,17 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	 /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		is_gtt = true;
+		up_gtt_relstats(relation,
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1230,17 +1242,26 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relpages = (int32) num_pages;
+	else if (pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (is_gtt)
+		relation->rd_rel->reltuples = (float4) num_tuples;
+	else if (pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	else if (pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1285,7 +1306,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1296,7 +1318,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1394,6 +1417,10 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/* global temp table relstats not in pg_class */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1451,6 +1478,25 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/*
+	 * Global temp table get frozenxid from MyProc
+	 * to avoid the vacuum truncate clog that gtt need.
+	 */
+	if (max_active_gtt > 0)
+	{
+		TransactionId oldest_gtt_frozenxid =
+			list_all_session_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid) &&
+			TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+		{
+			ereport(WARNING,
+				(errmsg("global temp table oldest FrozenXid is far in the past"),
+				 errhint("please truncate them or kill those sessions that use them.")));
+			newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1778,6 +1824,18 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(onerel->rd_node.relNode))
+	{
+		ereport(WARNING,
+				(errmsg("skipping vacuum global temp table \"%s\" because storage is not initialized for current session",
+						RelationGetRelationName(onerel))));
+		relation_close(onerel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6e65103..04706ee 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -530,6 +530,11 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 28130fb..dcb4a0b 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index c13b1d3..253054d 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
@@ -543,6 +544,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	init_gtt_storage(mtstate->operation, leaf_part_rri);
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d71c0a4..0900907 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,6 +42,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -2372,6 +2373,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			ExecOpenIndices(resultRelInfo,
 							node->onConflictAction != ONCONFLICT_NONE);
 
+		init_gtt_storage(operation, resultRelInfo);
+
 		/*
 		 * If this is an UPDATE and a BEFORE UPDATE trigger is present, the
 		 * trigger itself might modify the partition-key values. So arrange
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 905bbe7..170963d 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -591,6 +591,8 @@ static void
 set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 						  RangeTblEntry *rte)
 {
+	char relpersistence;
+
 	/*
 	 * The flag has previously been initialized to false, so we can just
 	 * return if it becomes clear that we can't safely set it.
@@ -618,7 +620,11 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			/* global temp table is same as local temp table */
+			relpersistence = get_rel_persistence(rte->relid);
+
+			if (relpersistence == RELPERSISTENCE_TEMP ||
+				relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b44efd6..14dbaaf 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6307,7 +6307,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
+	/* global temp table is same as local temp table */
 	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index d82fc5a..93c6d97 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -28,6 +28,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temp table */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(indexRelation->rd_node.relNode))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6676412..0685c1c 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2585,6 +2585,11 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("materialized views must not use temporary tables or views")));
 
+		if (is_query_using_gtt(query))
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("materialized views must not use global temporary tables or views")));
+
 		/*
 		 * A materialized view would either need to save parameters for use in
 		 * maintaining/loading the data or prohibit them entirely.  The latter
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7e384f9..6ac4aa0 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3291,17 +3291,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; }
@@ -11613,19 +11607,13 @@ OptTempTableName:
 				}
 			| GLOBAL TEMPORARY opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED opt_table qualified_name
 				{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b875a50..123945b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index af77f18..67d34f0 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index e3a43d3..ca460ca 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2087,6 +2087,11 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* autovacuum skip vacuum global temp table */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2153,7 +2158,9 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		/* autovacuum skip vacuum global temp table */
+		if (classForm->relpersistence == RELPERSISTENCE_TEMP || 
+			classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 5880054..98f7b43 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -52,6 +53,7 @@
 #include "utils/rel.h"
 #include "utils/resowner_private.h"
 #include "utils/timestamp.h"
+#include "utils/guc.h"
 
 
 /* Note: these two macros only work on shared buffers, not local ones! */
@@ -2809,6 +2811,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(relation->rd_node.relNode))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d5..7adb969 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -21,6 +21,7 @@
 #include "access/nbtree.h"
 #include "access/subtrans.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, active_gtt_shared_hash_size());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* global temporary table */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 4a5b26c..9754168 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -62,6 +62,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -4087,3 +4088,77 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temp table.
+ */
+int
+list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct *arrayP = procArray;
+	TransactionId result = InvalidTransactionId;
+	int			index;
+	int			flags = 0;
+	int			i = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+		*n = 0;
+	}
+
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		volatile PGXACT *pgxact = &allPgXact[pgprocno];
+
+		if (pgxact->vacuumFlags & flags)
+			continue;
+
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->session_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->session_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->session_gtt_frozenxid, result))
+				result = proc->session_gtt_frozenxid;
+
+			if (max_size > 0)
+			{
+				pids[i] = proc->pid;
+				xids[i] = proc->session_gtt_frozenxid;
+				i++;
+			}
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (max_size > 0)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index eb321f7..3893cef 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 8406644..0d99321 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -1008,6 +1008,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 0be26fe..c169c99 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -112,6 +112,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -4570,12 +4571,25 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -4700,15 +4714,27 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6090,6 +6116,7 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	{
 		/* Simple variable --- look to stats for the underlying table */
 		RangeTblEntry *rte = planner_rt_fetch(index->rel->relid, root);
+		char	rel_persistence = get_rel_persistence(rte->relid);
 
 		Assert(rte->rtekind == RTE_RELATION);
 		relid = rte->relid;
@@ -6107,6 +6134,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6118,6 +6152,8 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	}
 	else
 	{
+		char	rel_persistence = get_rel_persistence(index->indexoid);
+
 		/* Expression --- maybe there are stats for the index itself */
 		relid = index->indexoid;
 		colnum = 1;
@@ -6133,6 +6169,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7046,6 +7089,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		/* attempt to lookup stats in relation for this index column */
 		if (attnum != 0)
 		{
+			char	rel_persistence = get_rel_persistence(rte->relid);
+
 			/* Simple variable -- look to stats for the underlying table */
 			if (get_relation_stats_hook &&
 				(*get_relation_stats_hook) (root, rte, attnum, &vardata))
@@ -7058,6 +7103,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7070,6 +7123,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		}
 		else
 		{
+			char	rel_persistence = get_rel_persistence(index->indexoid);
+
 			/*
 			 * Looks like we've found an expression column in the index. Let's
 			 * see if there's any stats for it.
@@ -7089,6 +7144,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 3da90cb..97240ff 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -2878,6 +2879,18 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index ff70326..1cf7063 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -63,6 +63,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1127,6 +1128,24 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1181,6 +1200,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			gtt_fix_index_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -2220,6 +2240,8 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		gtt_fix_index_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3316,6 +3338,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3430,6 +3456,9 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
 
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+		elog(ERROR, "global temp table does not allow setting new relfilenode");
+
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
@@ -3470,7 +3499,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 				/* handle these directly, at least for now */
 				SMgrRelation srel;
 
-				srel = RelationCreateStorage(newrnode, persistence);
+				srel = RelationCreateStorage(newrnode, persistence, relation);
 				smgrclose(srel);
 			}
 			break;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index c1fad3b..c60fa31 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -140,6 +140,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temp table feature.
+ * global temp table define can still storage in catalog
+ * just can not use.
+ * num > 0 means database can management num active global temp table.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2047,6 +2059,15 @@ static struct config_bool ConfigureNamesBool[] =
 static struct config_int ConfigureNamesInt[] =
 {
 	{
+		{"max_active_global_temporary_table", PGC_POSTMASTER, UNGROUPED,
+			gettext_noop("max active global temporary table."),
+			NULL
+		},
+		&max_active_gtt,
+		DEFAULT_NUM_ACTIVE_GTT, MIN_NUM_ACTIVE_GTT, MAX_NUM_ACTIVE_GTT,
+		NULL, NULL, NULL
+	},
+	{
 		{"archive_timeout", PGC_SIGHUP, WAL_ARCHIVING,
 			gettext_noop("Forces a switch to the next WAL file if a "
 						 "new file has not been started within N seconds."),
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a12c8d0..58b22ea 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15595,6 +15595,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	{
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
+		char		*table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15646,9 +15647,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 			binary_upgrade_set_pg_class_oids(fout, q,
 											 tbinfo->dobj.catId.oid, false);
 
+		if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED)
+			table_type = "UNLOGGED ";
+		else if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			table_type = "GLOBAL TEMPORARY ";
+		else
+			table_type = "";
+
 		appendPQExpBuffer(q, "CREATE %s%s %s",
-						  tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ?
-						  "UNLOGGED " : "",
+						  table_type,
 						  reltypename,
 						  qualrelname);
 
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 5f9a102..3e40297 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -84,7 +84,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -157,7 +157,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 7e524ea..371168f 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -441,22 +443,46 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 	 * output, so we have to copy that system table.  It's easiest to do that
 	 * by treating it as a user table.
 	 */
-	snprintf(query + strlen(query), sizeof(query) - strlen(query),
-			 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
-			 "  SELECT c.oid, 0::oid, 0::oid "
-			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
-			 "         ON c.relnamespace = n.oid "
-			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
-	/* exclude possible orphaned temp tables */
-			 "    ((n.nspname !~ '^pg_temp_' AND "
-			 "      n.nspname !~ '^pg_toast_temp_' AND "
-			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
-			 "                        'binary_upgrade', 'pg_toast') AND "
-			 "      c.oid >= %u::pg_catalog.oid) OR "
-			 "     (n.nspname = 'pg_catalog' AND "
-			 "      relname IN ('pg_largeobject') ))), ",
-			 FirstNormalObjectId);
+	if (skip_gtt)
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude global temp tables */
+				 "    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
+	else
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
 
 	/*
 	 * Add a CTE that collects OIDs of toast tables belonging to the tables
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 70194eb..8dd9e66 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -411,7 +411,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -642,7 +642,9 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -653,7 +655,9 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 8b90cef..d155205 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -389,7 +389,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 109245f..557cce1 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3724,7 +3724,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 174c3db..80471d0 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1023,6 +1023,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2386,6 +2388,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2594,6 +2599,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 78b33b2..4be89f6 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -168,6 +168,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 7fb574f..6f8591c 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5507,6 +5507,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4191',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4192',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{relid,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4193',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4194',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 048003c..af48cdf 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -19,7 +19,7 @@
 #include "storage/smgr.h"
 #include "utils/relcache.h"
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationTruncate(Relation rel, BlockNumber nblocks);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..63f7241
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,43 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, bool isCommit);
+extern bool is_other_backend_use_gtt(RelFileNode node);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(RelFileNode node);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Relation relation,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void gtt_force_enable_index(Relation index);
+extern void gtt_fix_index_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index e2638ab..89a5ce4 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 3f88683..f870e9a 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 8fda8e4..172eae9 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_TBM,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_SXACT,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index d217801..8adde87 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -117,6 +117,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId session_gtt_frozenxid;	/* session level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ce93ace..0f7262e 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -281,6 +281,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 44ed04d..6b6d2da 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	struct SMgrRelationData *rd_smgr;	/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -277,6 +277,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -535,11 +536,13 @@ typedef struct ViewOptions
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -547,6 +550,7 @@ typedef struct ViewOptions
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -559,6 +563,14 @@ typedef struct ViewOptions
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is either local temp relation of this session
+ * 		or global temp relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -602,6 +614,17 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..73d3d7b
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,8 @@
+reset search_path;
+drop schema gtt cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
+drop cascades to table gtt.gtt_with_seq
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..220ec04
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,330 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+ n 
+---
+ 9
+(1 row)
+
+commit;
+-- 0 row
+select * from gtt6;
+ n 
+---
+(0 rows)
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+ERROR:  cannot create indexes on global temporary tables using concurrent mode
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+ERROR:  not support cluster global temporary tables yet
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temp table
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  can not defeine global temp table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test;
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR:  insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count 
+-------
+     1
+(1 row)
+
+select count(*) from orders;
+ count 
+-------
+     0
+(1 row)
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 1)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 300)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 20 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.gtt6
+drop cascades to table gtt_function.foo
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to table gtt_function.gt1
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..0646aae
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,90 @@
+set search_path=gtt,sys;
+select nextval('gtt_with_seq_c2_seq');
+ nextval 
+---------
+       1
+(1 row)
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..b2ef23b
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,168 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          114688 |                 614400
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |            49152 |         49152 |                  49152
+(2 rows)
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+ c1 | c2 
+----+----
+  1 |  1
+(1 row)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..8c0c376
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,10 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..0e21b30
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,76 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+(1 row)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+(1 row)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index c730461..8a39594 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1349,6 +1349,93 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d2b17dd..80e577f 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..f3cf710
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,6 @@
+
+
+reset search_path;
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..f599a10
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,224 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+commit;
+-- 0 row
+select * from gtt6;
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test;
+
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d05745e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,44 @@
+
+
+set search_path=gtt,sys;
+
+select nextval('gtt_with_seq_c2_seq');
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..bf0b922
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,79 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..dbe84d1
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,19 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..f041892
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,42 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
#181曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: tushar (#178)
Re: [Proposal] Global temporary tables

2020年3月9日 下午10:37,tushar <tushar.ahuja@enterprisedb.com> 写道:

On 3/6/20 12:35 PM, 曾文旌(义从) wrote:

Fixed in global_temporary_table_v17-pg13.patch

I observed that , we do support 'global temp' keyword with views

postgres=# create or replace global temp view v1 as select 5;
CREATE VIEW

I think we should not support global temp view.
Fixed in global_temporary_table_v18-pg13.patch.

Wenjing

Show quoted text

but if we take the dump( using pg_dumpall) then it only display 'create view'

look like we are skipping it ?

--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company

#182Prabhat Sahu
prabhat.sahu@enterprisedb.com
In reply to: 曾文旌(义从) (#181)
Re: [Proposal] Global temporary tables

On Mon, Mar 9, 2020 at 10:02 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com> wrote:

Fixed in global_temporary_table_v18-pg13.patch.

Hi Wenjing,
Thanks for the patch. I have verified the previous issues with
"gtt_v18_pg13.patch" and those are resolved.
Please find below case:

postgres=# create sequence seq;
CREATE SEQUENCE

postgres=# CREATE GLOBAL TEMPORARY TABLE gtt1(c1 int PRIMARY KEY) ON COMMIT
DELETE ROWS;
CREATE TABLE

postgres=# CREATE GLOBAL TEMPORARY TABLE gtt2(c1 int PRIMARY KEY) ON COMMIT
PRESERVE ROWS;
CREATE TABLE

postgres=# alter table gtt1 add c2 int default nextval('seq');
ERROR: cannot reindex global temporary tables

postgres=# alter table gtt2 add c2 int default nextval('seq');
ERROR: cannot reindex global temporary tables

*Note*: We are getting this error if we have a key column(PK/UNIQUE) in a
GTT, and trying to add a column with a default sequence into it.

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

#183曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Prabhat Sahu (#182)
1 attachment(s)
Re: [Proposal] Global temporary tables

2020年3月11日 下午3:52,Prabhat Sahu <prabhat.sahu@enterprisedb.com> 写道:

On Mon, Mar 9, 2020 at 10:02 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> wrote:

Fixed in global_temporary_table_v18-pg13.patch.
Hi Wenjing,
Thanks for the patch. I have verified the previous issues with "gtt_v18_pg13.patch" and those are resolved.
Please find below case:

postgres=# create sequence seq;
CREATE SEQUENCE

postgres=# CREATE GLOBAL TEMPORARY TABLE gtt1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
CREATE TABLE

postgres=# CREATE GLOBAL TEMPORARY TABLE gtt2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
CREATE TABLE

postgres=# alter table gtt1 add c2 int default nextval('seq');
ERROR: cannot reindex global temporary tables

postgres=# alter table gtt2 add c2 int default nextval('seq');
ERROR: cannot reindex global temporary tables

Note: We are getting this error if we have a key column(PK/UNIQUE) in a GTT, and trying to add a column with a default sequence into it.

This is because alter table add column with default value need reindex pk,
reindex need change relfilenode, but GTT is not currently supported.
I make the error message more clearer in global_temporary_table_v19-pg13.patch

Wenjing

Show quoted text

--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

Attachments:

global_temporary_table_v19-pg13.patchapplication/octet-stream; name=global_temporary_table_v19-pg13.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index c3d45c7..3f9e875 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -168,6 +168,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * For global temp table only
+	 * use ShareUpdateExclusiveLock for ensure safety
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temp table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},	
 	/* list terminator */
 	{{NULL}}
 };
@@ -1496,6 +1509,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1596,13 +1611,18 @@ build_reloptions(Datum reloptions, bool validate,
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	/*
 	 * There are no options for partitioned tables yet, but this is able to do
 	 * some validation.
 	 */
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index dd975b1..1610e7d 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1013,7 +1013,9 @@ gistGetFakeLSN(Relation rel)
 {
 	static XLogRecPtr counter = FirstNormalUnloggedLSN;
 
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	/* global temp is same as local temp table */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 4871b7f..16b00c9 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -149,7 +149,9 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	/* global temp table is same as local temp table */
+	if (!(index->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		index->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ca52846..f0153cf 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -598,7 +598,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -651,7 +651,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 03c43ef..2674132 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -62,6 +62,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -399,9 +400,9 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
 	Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
-	/* not every AM requires these to be valid, but heap does */
-	Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
-	Assert(MultiXactIdIsValid(onerel->rd_rel->relminmxid));
+	/* not every AM requires these to be valid, but regular heap does */
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ MultiXactIdIsValid(onerel->rd_rel->relminmxid));
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 39b8f17..38b46d0 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -601,6 +602,10 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(rel->rd_node.relNode))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 614a252..5f8d9ca 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6348,6 +6348,10 @@ StartupXLOG(void)
 	else
 		recoveryTargetTLI = ControlFile->checkPointCopy.ThisTimeLineID;
 
+	/* clean temp relation files */
+	if (max_active_gtt > 0)
+		RemovePgTempFiles();
+
 	/*
 	 * Check for signal files, and if so set up state for offline recovery
 	 */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 9499bb3..ae47364 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -43,6 +43,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 7d6acae..3be8d63 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -393,7 +393,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/* global temp table is same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 9d9e915..1c50263 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -427,7 +429,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -957,6 +959,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -995,8 +998,18 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/* global temp table not remember transaction info in catalog */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1358,6 +1371,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1938,6 +1952,14 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/* We allow to drop global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(rel->rd_node))
+			elog(ERROR, "can not drop relation %s when other backend attached this global temp table",
+						RelationGetRelationName(rel));
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3165,7 +3187,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3177,7 +3199,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3223,8 +3245,13 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		/* truncate global temp table only need RowExclusiveLock */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3257,6 +3284,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3265,23 +3293,37 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (!gtt_storage_attached(rel->rd_node.relNode))
+			return;
+
+		/*
+		 * Truncate global temp table only need RowExclusiveLock
+		 */
+		lockmode = RowExclusiveLock;
+	}
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(rel, 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 76fd938..ca4709d 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -52,6 +52,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -877,6 +878,19 @@ index_create(Relation heapRelation,
 						indexRelationName, RelationGetRelationName(heapRelation))));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temp table use concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot create indexes on global temporary tables using concurrent mode")));
+
+		/* if global temp table not init storage, then skip build index */
+		if (!gtt_storage_attached(heapRelation->rd_node.relNode))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -2061,6 +2075,14 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/* We allow to drop index on global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation))
+	{
+		if (is_other_backend_use_gtt(userHeapRelation->rd_node))
+			elog(ERROR, "can not drop index %s when other backend attached this global temp table.",
+						RelationGetRelationName(userHeapRelation));
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2667,6 +2689,11 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	/* update index stats into localhash and rel_rd_rel for global temp table */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		is_gtt = true;
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2752,21 +2779,35 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
+		if (is_gtt)
+			rel->rd_rel->relpages = (int32) relpages;
+		else if (rd_rel->relpages != (int32) relpages)
 		{
 			rd_rel->relpages = (int32) relpages;
 			dirty = true;
 		}
-		if (rd_rel->reltuples != (float4) reltuples)
+
+		if (is_gtt)
+			rel->rd_rel->reltuples = (float4) reltuples;
+		else if (rd_rel->reltuples != (float4) reltuples)
 		{
 			rd_rel->reltuples = (float4) reltuples;
 			dirty = true;
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+
+		if (is_gtt)
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+		else if (rd_rel->relallvisible != (int32) relallvisible)
 		{
 			rd_rel->relallvisible = (int32) relallvisible;
 			dirty = true;
 		}
+
+		if (is_gtt)
+		{
+			up_gtt_relstats(rel, relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
+		}
 	}
 
 	/*
@@ -2880,6 +2921,15 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, index, val);
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		if (!gtt_storage_attached(indexRelation->rd_node.relNode))
+		{
+			gtt_force_enable_index(indexRelation);
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3486,6 +3536,17 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 				 errmsg("cannot reindex invalid index on TOAST table")));
 
 	/*
+	 * 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("unsupported reindex \"%s\" on global temporary table \"%s\"",
+				 RelationGetRelationName(iRel),
+				 RelationGetRelationName(heapRelation))));
+
+	/*
 	 * Also check for active uses of the index in the current transaction; we
 	 * don't want to reindex underneath an open indexscan.
 	 */
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 5ff7824..9b58f2a 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -647,6 +647,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		/* global temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp relations in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index fddfbf1..2982bc7 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -26,6 +26,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -75,7 +76,7 @@ static PendingRelDelete *pendingDeletes = NULL; /* head of linked list */
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -85,6 +86,8 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		/* global temp table use same storage strategy as local temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -117,6 +120,10 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/* remember global temp table storage info to localhash */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel)
+		remember_gtt_storage_info(rnode, rel);
+
 	return srel;
 }
 
@@ -486,8 +493,15 @@ smgrDoPendingDeletes(bool isCommit)
 		smgrdounlinkall(srels, nrels, false);
 
 		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* clean global temp table flags when transaction commit or rollback */
+			if (SmgrIsTemp(srels[i]) && 
+				gtt_storage_attached(srels[i]->smgr_rnode.node.relNode))
+				forget_gtt_storage_info(srels[i]->smgr_rnode.node.relNode, isCommit);
+		}
+
 		pfree(srels);
 	}
 }
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..04f7fcc
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1261 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  code to create and destroy physical storage for global temparary table
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_relstats_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+typedef struct gtt_ctl_data
+{
+	LWLock			lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct
+{
+	RelFileNode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+	Oid			relid;
+
+	Oid			spcnode;
+	/* pg_class stat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+	char		relkind;
+	bool		on_commit_delete;
+
+	/* pg_statistic */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(RelFileNode rnode);
+static void gtt_storage_checkout(RelFileNode rnode, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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_global_temporary_table.")));
+	}
+
+	if (found == false)
+	{
+		int			wordnum;
+
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(RelFileNode rnode, bool skiplock, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (!skiplock)
+		LWLockAcquire(&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);
+
+		if (isCommit)
+		{
+			elog(WARNING, "relfilenode %u/%u/%u not exist in gtt shared hash when forget",
+						rnode.dbNode, rnode.spcNode, rnode.relNode);
+		}
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+	bms_del_member(entry->map, MyBackendId);
+
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &rnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+
+	if (!skiplock)
+		LWLockRelease(&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;
+	int			natts = 0;
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		/* First time through: initialize the hash table */
+		HASHCTL		ctl;
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_relstats_context =
+			AllocSetContextCreate(CacheMemoryContext,
+							"gtt relstats context",
+							ALLOCSET_DEFAULT_SIZES);
+	}
+
+	/* Look up or create an entry */
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_ENTER, &found);
+
+	if (found)
+	{
+		elog(ERROR, "backend %d relid %u already exists in global temporary table local hash",
+					MyBackendId, relid);
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+	entry->spcnode = rnode.spcNode;
+	entry->relpages = 0;
+	entry->reltuples = 0;
+	entry->relallvisible = 0;
+	entry->relfrozenxid = InvalidTransactionId;
+	entry->relminmxid = InvalidMultiXactId;
+	entry->relkind = rel->rd_rel->relkind;
+	entry->on_commit_delete = false;
+	entry->natts = 0;
+	entry->attnum = NULL;
+	entry->att_stat_tups = NULL;
+
+	natts = RelationGetNumberOfAttributes(rel);
+	entry->attnum = palloc0(sizeof(int) * natts);
+	entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+	entry->natts = natts;
+	/* only heap contain transaction information */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+		{
+			entry->on_commit_delete = true;
+			register_on_commit_action(rel->rd_node.relNode, ONCOMMIT_DELETE_ROWS);
+		}
+
+		entry->relfrozenxid = RecentXmin;
+		entry->relminmxid = GetOldestMultiXactId();
+		insert_gtt_relfrozenxid_to_ordered_list(entry->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	if (entry->relkind == RELKIND_RELATION ||
+		entry->relkind == RELKIND_SEQUENCE)
+	{
+		gtt_storage_checkin(rnode);
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry)
+	{
+		int		i;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			Assert(TransactionIdIsNormal(entry->relfrozenxid) || !isCommit);
+			if (TransactionIdIsValid(entry->relfrozenxid))
+			{
+				remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+				set_gtt_session_relfrozenxid();
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			RelFileNode rnode;
+			rnode.spcNode = entry->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = entry->relid;
+			gtt_storage_checkout(rnode, false, isCommit);
+		}
+
+		for (i = 0; i < entry->natts; i++)
+		{
+			if (entry->att_stat_tups[i])
+			{
+				heap_freetuple(entry->att_stat_tups[i]);
+				entry->att_stat_tups[i] = NULL;
+			}
+		}
+
+		if (entry->attnum)
+			pfree(entry->attnum);
+
+		if (entry->att_stat_tups)
+			pfree(entry->att_stat_tups);
+	}
+
+	hash_search(gtt_storage_local_hash,
+			(void *) &(relid), HASH_REMOVE, NULL);
+
+	return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool found;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (gtt_storage_local_hash == NULL)
+		return false;
+
+	hash_search(gtt_storage_local_hash,
+			(void *) &(relid), HASH_FIND, &found);
+
+	return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS				status;
+	gtt_local_hash_entry		*entry;
+	int			nrels = 0,
+				maxrels = 0;
+	SMgrRelation	*srels = NULL;
+	RelFileNode		*rnodes = NULL;
+	char			*relkinds = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		SMgrRelation srel;
+		RelFileNode rnode;
+
+		rnode.spcNode = entry->spcnode;
+		rnode.dbNode = MyDatabaseId;
+		rnode.relNode = entry->relid;
+
+		srel = smgropen(rnode, MyBackendId);
+
+		/* allocate the initial array, or extend it, if needed */
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			srels = palloc(sizeof(SMgrRelation) * maxrels);
+			rnodes = palloc(sizeof(RelFileNode) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+			rnodes = repalloc(rnodes, sizeof(RelFileNode) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		srels[nrels] = srel;
+		rnodes[nrels] = rnode;
+		relkinds[nrels] = entry->relkind;
+		nrels++;
+	}
+
+	if (nrels > 0)
+	{
+		int i;
+
+		smgrdounlinkall(srels, nrels, false);
+
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+		for (i = 0; i < nrels; i++)
+		{
+			smgrclose(srels[i]);
+			if (relkinds[i] == RELKIND_RELATION ||
+				relkinds[i] == RELKIND_SEQUENCE)
+				gtt_storage_checkout(rnodes[i], true, false);
+		}
+		LWLockRelease(&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.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (gtt_storage_local_hash == NULL)
+		return false;
+
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_FIND, &found);
+
+	if (!found)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	if (relpages)
+		*relpages = entry->relpages;
+
+	if (reltuples)
+		*reltuples = entry->reltuples;
+
+	if (relallvisible)
+		*relallvisible = entry->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = entry->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = entry->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	MemoryContext			oldcontext;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+						(void *) &reloid, HASH_FIND, &found);
+
+	if (!found)
+		return;
+
+	/* todo */
+	if (entry->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	Assert(entry->relid == reloid);
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == 0)
+		{
+			entry->attnum[i] = attnum;
+			break;
+		}
+		else if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < entry->natts);
+	Assert(entry->att_stat_tups[i] == NULL);
+	entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+						(void *) &reloid, HASH_FIND, &found);
+
+	if (!found)
+		return NULL;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			return entry->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int			i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	HeapTuple	tuple;
+	Relation	rel = NULL;
+	int		attnum = PG_GETARG_INT32(1);
+	Oid		reloid = PG_GETARG_OID(0);
+	char		rel_persistence;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	  tupdesc;
+	MemoryContext oldcontext;
+	Tuplestorestate *tupstore;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = table_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+
+	table_close(rel, NoLock);
+	table_close(pg_tatistic, AccessShareLock);
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate	*tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Relation	rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	if (get_gtt_relstats(reloid,
+						&relpages, &reltuples, &relallvisible,
+						&relfrozenxid, &relminmxid))
+	{
+		Datum	values[5];
+		bool	isnull[5];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = Int32GetDatum(relpages);
+		values[1] = Float4GetDatum((float4)reltuples);
+		values[2] = Int32GetDatum(relallvisible);
+		values[3] = UInt32GetDatum(relfrozenxid);
+		values[4] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+
+	tuplestore_donestoring(tupstore);
+	table_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(rel->rd_node);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	table_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(indexOid));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid relOid = index->rd_index->indrelid;
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	if (gtt_storage_attached(relOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int			i;
+	Oid			toastrelid;
+
+	if (operation != CMD_INSERT)
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_attached(relation->rd_node.relNode))
+		return;
+
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+
+		index_build(relation, index, info, true, false);
+	}
+
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid 		indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo 	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index b8a3f46..491db16 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM 
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 924ef37..7efc5a9 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -184,6 +185,13 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(onerel->rd_node.relNode))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -586,14 +594,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 		 * pg_statistic for columns we didn't process, we leave them alone.)
 		 */
 		update_attstats(RelationGetRelid(onerel), inh,
-						attr_cnt, vacattrstats);
+						attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
 
 		for (ind = 0; ind < nindexes; ind++)
 		{
 			AnlIndexData *thisdata = &indexdata[ind];
 
 			update_attstats(RelationGetRelid(Irel[ind]), false,
-							thisdata->attr_cnt, thisdata->vacattrstats);
+							thisdata->attr_cnt, thisdata->vacattrstats,
+							RelationGetRelPersistence(Irel[ind]));
 		}
 
 		/*
@@ -1456,7 +1465,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
  *		by taking a self-exclusive lock on the relation in analyze_rel().
  */
 static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
 {
 	Relation	sd;
 	int			attno;
@@ -1558,31 +1567,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/* Update column statistic to localhash, not catalog */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 11ce1bb..552fd87 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -391,6 +391,12 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/* not support cluster global temp table yet */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("not support cluster global temporary tables yet")));
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index fbde9f8..7974ea4 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -27,6 +27,7 @@
 #include "catalog/dependency.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -1063,7 +1064,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, stmt->filename, stmt->is_program,
@@ -2841,6 +2842,8 @@ CopyFrom(CopyState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	estate->es_result_relations = resultRelInfo;
 	estate->es_num_result_relations = 1;
 	estate->es_result_relation_info = resultRelInfo;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 6d696dd..8e08596 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2586,6 +2586,16 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 			!isTempNamespace(classtuple->relnamespace))
 			continue;
 
+		/* not support reindex on global temp table, so skip it */
+		if (classtuple->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			ereport(WARNING,
+				(errmsg("global temp table \"%s.%s\" skip reindexed",
+					get_namespace_name(get_rel_namespace(relid)),
+					get_rel_name(relid))));
+			continue;
+		}
+
 		/* Check user/system classification, and optionally skip */
 		if (objectKind == REINDEX_OBJECT_SYSTEM &&
 			!IsSystemClass(relid, classtuple))
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index d8cafc4..deecde8 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -96,7 +96,8 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 	 * transaction.
 	 */
 	relpersistence = get_rel_persistence(relid);
-	if (relpersistence == RELPERSISTENCE_TEMP)
+	if (relpersistence == RELPERSISTENCE_TEMP ||
+		relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
 
 	/* Check permissions. */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..fada1ac 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -94,7 +96,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -222,7 +225,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* now initialize the sequence's data */
 	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -327,7 +330,7 @@ ResetSequence(Oid seq_relid)
 	/*
 	 * Insert the modified tuple into the new storage file.
 	 */
-	fill_seq_with_data(seq_rel, tuple);
+	fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -340,17 +343,21 @@ ResetSequence(Oid seq_relid)
  * Initialize a sequence's relation with the specified tuple as content
  */
 static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 	sequence_magic *sm;
 	OffsetNumber offnum;
+	bool lockBuffer = false;
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ReadBuffer(rel, P_NEW);
-	Assert(BufferGetBlockNumber(buf) == 0);
+	if (buf == InvalidBuffer)
+	{
+		buf = ReadBuffer(rel, P_NEW);
+		Assert(BufferGetBlockNumber(buf) == 0);
+		lockBuffer = true;
+	}
 
 	page = BufferGetPage(buf);
 
@@ -360,7 +367,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* Now insert sequence tuple */
 
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+	if (lockBuffer)
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +418,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	END_CRIT_SECTION();
 
-	UnlockReleaseBuffer(buf);
+	if (lockBuffer)
+		UnlockReleaseBuffer(buf);
 }
 
 /*
@@ -502,7 +511,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * Insert the modified tuple into the new storage file.
 		 */
-		fill_seq_with_data(seqrel, newdatatuple);
+		fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
 	}
 
 	/* process OWNED BY if given */
@@ -611,7 +620,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +945,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1154,6 +1163,13 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(seqrel->rd_node.relNode))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1954,3 +1970,46 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+void
+gtt_init_seq(Relation rel)
+{
+	/* Initialize sequence for global temporary tables */
+	Datum		value[SEQ_COL_LASTCOL] = {0};
+	bool		null[SEQ_COL_LASTCOL] = {false};
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 3eb861b..c046734 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -553,6 +554,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 								  Relation partitionTbl);
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static OnCommitAction gtt_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -598,6 +600,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -608,8 +611,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Check consistency of arguments
 	 */
+	/* global temp table same as local temp table */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !(stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+			 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -639,7 +644,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	/* global temp table same as local temp table */
+	if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+		 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -740,6 +747,55 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* global temp table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* check parent table*/
+		if (inheritOids)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			table_close(relation, NoLock);
+		}
+
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "can not defeine global temp table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temp table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1824,7 +1880,8 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
 		 * table or the current physical file to be thrown away anyway.
 		 */
 		if (rel->rd_createSubid == mySubid ||
-			rel->rd_newRelfilenodeSubid == mySubid)
+			rel->rd_newRelfilenodeSubid == mySubid ||
+			RELATION_IS_GLOBAL_TEMP(rel))
 		{
 			/* Immediate, non-rollbackable truncation is OK */
 			heap_truncate_one_rel(rel);
@@ -3567,6 +3624,14 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(rel->rd_node))
+			elog(ERROR, "can not alter table %s when other backend attached this global temp table",
+						RelationGetRelationName(rel));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -8179,6 +8244,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -12645,6 +12716,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -12847,6 +12921,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temp table");
+
 	/*
 	 * No work if no change in tablespace.
 	 */
@@ -13219,7 +13296,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,
@@ -14626,7 +14703,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",
@@ -17218,3 +17297,28 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
 		table_close(rel, NoLock);
 	}
 }
+
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			if (defGetBoolean(def))
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index d625d17..f0860cc 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/vacuum.h"
@@ -1217,6 +1218,17 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	 /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		is_gtt = true;
+		up_gtt_relstats(relation,
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1230,17 +1242,26 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relpages = (int32) num_pages;
+	else if (pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (is_gtt)
+		relation->rd_rel->reltuples = (float4) num_tuples;
+	else if (pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	else if (pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1285,7 +1306,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1296,7 +1318,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1394,6 +1417,10 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/* global temp table relstats not in pg_class */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1451,6 +1478,25 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/*
+	 * Global temp table get frozenxid from MyProc
+	 * to avoid the vacuum truncate clog that gtt need.
+	 */
+	if (max_active_gtt > 0)
+	{
+		TransactionId oldest_gtt_frozenxid =
+			list_all_session_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid) &&
+			TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+		{
+			ereport(WARNING,
+				(errmsg("global temp table oldest FrozenXid is far in the past"),
+				 errhint("please truncate them or kill those sessions that use them.")));
+			newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1778,6 +1824,18 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(onerel->rd_node.relNode))
+	{
+		ereport(WARNING,
+				(errmsg("skipping vacuum global temp table \"%s\" because storage is not initialized for current session",
+						RelationGetRelationName(onerel))));
+		relation_close(onerel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6e65103..04706ee 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -530,6 +530,11 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 28130fb..dcb4a0b 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index ef74ad8..b1cd1c0 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
@@ -544,6 +545,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	init_gtt_storage(mtstate->operation, leaf_part_rri);
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d71c0a4..0900907 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,6 +42,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -2372,6 +2373,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			ExecOpenIndices(resultRelInfo,
 							node->onConflictAction != ONCONFLICT_NONE);
 
+		init_gtt_storage(operation, resultRelInfo);
+
 		/*
 		 * If this is an UPDATE and a BEFORE UPDATE trigger is present, the
 		 * trigger itself might modify the partition-key values. So arrange
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 905bbe7..170963d 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -591,6 +591,8 @@ static void
 set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 						  RangeTblEntry *rte)
 {
+	char relpersistence;
+
 	/*
 	 * The flag has previously been initialized to false, so we can just
 	 * return if it becomes clear that we can't safely set it.
@@ -618,7 +620,11 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			/* global temp table is same as local temp table */
+			relpersistence = get_rel_persistence(rte->relid);
+
+			if (relpersistence == RELPERSISTENCE_TEMP ||
+				relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b44efd6..14dbaaf 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6307,7 +6307,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
+	/* global temp table is same as local temp table */
 	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index d82fc5a..93c6d97 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -28,6 +28,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temp table */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(indexRelation->rd_node.relNode))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6676412..0685c1c 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2585,6 +2585,11 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("materialized views must not use temporary tables or views")));
 
+		if (is_query_using_gtt(query))
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("materialized views must not use global temporary tables or views")));
+
 		/*
 		 * A materialized view would either need to save parameters for use in
 		 * maintaining/loading the data or prohibit them entirely.  The latter
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7e384f9..6ac4aa0 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3291,17 +3291,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; }
@@ -11613,19 +11607,13 @@ OptTempTableName:
 				}
 			| GLOBAL TEMPORARY opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED opt_table qualified_name
 				{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b875a50..123945b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index c191141..fe1c494 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index e3a43d3..ca460ca 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2087,6 +2087,11 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* autovacuum skip vacuum global temp table */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2153,7 +2158,9 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		/* autovacuum skip vacuum global temp table */
+		if (classForm->relpersistence == RELPERSISTENCE_TEMP || 
+			classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 5880054..98f7b43 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -52,6 +53,7 @@
 #include "utils/rel.h"
 #include "utils/resowner_private.h"
 #include "utils/timestamp.h"
+#include "utils/guc.h"
 
 
 /* Note: these two macros only work on shared buffers, not local ones! */
@@ -2809,6 +2811,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(relation->rd_node.relNode))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d5..7adb969 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -21,6 +21,7 @@
 #include "access/nbtree.h"
 #include "access/subtrans.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, active_gtt_shared_hash_size());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* global temporary table */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index f45a619..cf8b20a 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -63,6 +63,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -4088,3 +4089,77 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temp table.
+ */
+int
+list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct *arrayP = procArray;
+	TransactionId result = InvalidTransactionId;
+	int			index;
+	int			flags = 0;
+	int			i = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+		*n = 0;
+	}
+
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		volatile PGXACT *pgxact = &allPgXact[pgprocno];
+
+		if (pgxact->vacuumFlags & flags)
+			continue;
+
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->session_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->session_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->session_gtt_frozenxid, result))
+				result = proc->session_gtt_frozenxid;
+
+			if (max_size > 0)
+			{
+				pids[i] = proc->pid;
+				xids[i] = proc->session_gtt_frozenxid;
+				i++;
+			}
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (max_size > 0)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index eb321f7..3893cef 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 8406644..0d99321 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -1008,6 +1008,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 8339f4c..d41c05d 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -112,6 +112,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -4571,12 +4572,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))
 							{
@@ -4701,15 +4715,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))
 		{
@@ -6091,6 +6117,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;
@@ -6108,6 +6135,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,
@@ -6119,6 +6153,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;
@@ -6134,6 +6170,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,
@@ -7047,6 +7090,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))
@@ -7059,6 +7104,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 =
@@ -7071,6 +7124,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.
@@ -7090,6 +7145,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 6b24369..1bb437c 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -2904,6 +2905,18 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index ff70326..1cf7063 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -63,6 +63,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1127,6 +1128,24 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1181,6 +1200,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			gtt_fix_index_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -2220,6 +2240,8 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		gtt_fix_index_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3316,6 +3338,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3430,6 +3456,9 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
 
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+		elog(ERROR, "global temp table does not allow setting new relfilenode");
+
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
@@ -3470,7 +3499,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 				/* handle these directly, at least for now */
 				SMgrRelation srel;
 
-				srel = RelationCreateStorage(newrnode, persistence);
+				srel = RelationCreateStorage(newrnode, persistence, relation);
 				smgrclose(srel);
 			}
 			break;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 4c6d648..047045e 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -141,6 +141,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,
@@ -2048,6 +2060,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 ad039e9..b8a958c 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15599,6 +15599,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	{
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
+		char		*table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15650,9 +15651,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 			binary_upgrade_set_pg_class_oids(fout, q,
 											 tbinfo->dobj.catId.oid, false);
 
+		if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED)
+			table_type = "UNLOGGED ";
+		else if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			table_type = "GLOBAL TEMPORARY ";
+		else
+			table_type = "";
+
 		appendPQExpBuffer(q, "CREATE %s%s %s",
-						  tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ?
-						  "UNLOGGED " : "",
+						  table_type,
 						  reltypename,
 						  qualrelname);
 
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 5f9a102..3e40297 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -84,7 +84,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -157,7 +157,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 7e524ea..371168f 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -441,22 +443,46 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 	 * output, so we have to copy that system table.  It's easiest to do that
 	 * by treating it as a user table.
 	 */
-	snprintf(query + strlen(query), sizeof(query) - strlen(query),
-			 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
-			 "  SELECT c.oid, 0::oid, 0::oid "
-			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
-			 "         ON c.relnamespace = n.oid "
-			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
-	/* exclude possible orphaned temp tables */
-			 "    ((n.nspname !~ '^pg_temp_' AND "
-			 "      n.nspname !~ '^pg_toast_temp_' AND "
-			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
-			 "                        'binary_upgrade', 'pg_toast') AND "
-			 "      c.oid >= %u::pg_catalog.oid) OR "
-			 "     (n.nspname = 'pg_catalog' AND "
-			 "      relname IN ('pg_largeobject') ))), ",
-			 FirstNormalObjectId);
+	if (skip_gtt)
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude global temp tables */
+				 "    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
+	else
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
 
 	/*
 	 * Add a CTE that collects OIDs of toast tables belonging to the tables
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 70194eb..8dd9e66 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -411,7 +411,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -642,7 +642,9 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -653,7 +655,9 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 8b90cef..d155205 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -389,7 +389,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 109245f..557cce1 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3724,7 +3724,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 174c3db..80471d0 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1023,6 +1023,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2386,6 +2388,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2594,6 +2599,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 78b33b2..4be89f6 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -168,6 +168,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 7fb574f..6f8591c 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5507,6 +5507,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4191',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4192',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{relid,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4193',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4194',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 048003c..af48cdf 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -19,7 +19,7 @@
 #include "storage/smgr.h"
 #include "utils/relcache.h"
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationTruncate(Relation rel, BlockNumber nblocks);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..63f7241
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,43 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, bool isCommit);
+extern bool is_other_backend_use_gtt(RelFileNode node);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(RelFileNode node);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Relation relation,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void gtt_force_enable_index(Relation index);
+extern void gtt_fix_index_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index e2638ab..89a5ce4 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 3f88683..f870e9a 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 8fda8e4..172eae9 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_TBM,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_SXACT,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index d217801..8adde87 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -117,6 +117,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId session_gtt_frozenxid;	/* session level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ce93ace..0f7262e 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -281,6 +281,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 44ed04d..6b6d2da 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	struct SMgrRelationData *rd_smgr;	/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -277,6 +277,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -535,11 +536,13 @@ typedef struct ViewOptions
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -547,6 +550,7 @@ typedef struct ViewOptions
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -559,6 +563,14 @@ typedef struct ViewOptions
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is either local temp relation of this session
+ * 		or global temp relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -602,6 +614,17 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..73d3d7b
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,8 @@
+reset search_path;
+drop schema gtt cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
+drop cascades to table gtt.gtt_with_seq
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..220ec04
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,330 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+ n 
+---
+ 9
+(1 row)
+
+commit;
+-- 0 row
+select * from gtt6;
+ n 
+---
+(0 rows)
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+ERROR:  cannot create indexes on global temporary tables using concurrent mode
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+ERROR:  not support cluster global temporary tables yet
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temp table
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  can not defeine global temp table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test;
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR:  insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count 
+-------
+     1
+(1 row)
+
+select count(*) from orders;
+ count 
+-------
+     0
+(1 row)
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 1)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 300)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 20 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.gtt6
+drop cascades to table gtt_function.foo
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to table gtt_function.gt1
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..0646aae
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,90 @@
+set search_path=gtt,sys;
+select nextval('gtt_with_seq_c2_seq');
+ nextval 
+---------
+       1
+(1 row)
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..b2ef23b
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,168 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          114688 |                 614400
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |            49152 |         49152 |                  49152
+(2 rows)
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+ c1 | c2 
+----+----
+  1 |  1
+(1 row)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..8c0c376
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,10 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..0e21b30
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,76 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+(1 row)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+(1 row)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index c730461..8a39594 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1349,6 +1349,93 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d2b17dd..80e577f 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..f3cf710
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,6 @@
+
+
+reset search_path;
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..f599a10
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,224 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+commit;
+-- 0 row
+select * from gtt6;
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test;
+
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d05745e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,44 @@
+
+
+set search_path=gtt,sys;
+
+select nextval('gtt_with_seq_c2_seq');
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..bf0b922
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,79 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..dbe84d1
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,19 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..f041892
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,42 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 0
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
#184Robert Haas
robertmhaas@gmail.com
In reply to: 曾文旌(义从) (#183)
Re: [Proposal] Global temporary tables

On Wed, Mar 11, 2020 at 9:07 AM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com> wrote:

reindex need change relfilenode, but GTT is not currently supported.

In my view that'd have to be fixed somehow.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#185曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Robert Haas (#184)
Re: [Proposal] Global temporary tables

2020年3月12日 上午4:12,Robert Haas <robertmhaas@gmail.com> 写道:

On Wed, Mar 11, 2020 at 9:07 AM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com> wrote:

reindex need change relfilenode, but GTT is not currently supported.

In my view that'd have to be fixed somehow.

Ok , I am working on it.

Show quoted text

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#186Prabhat Sahu
prabhat.sahu@enterprisedb.com
In reply to: 曾文旌(义从) (#185)
Re: [Proposal] Global temporary tables

Hi Wenjing,

Please check the below findings:
After running "TRUNCATE" command, the "relfilenode" field is not changing
for GTT
whereas, for Simple table/Temp table "relfilenode" field is changing after
TRUNCATE.

*Case 1: Getting same "relfilenode" for GTT after and before "TRUNCATE"*
postgres=# create global temporary table gtt1(c1 int) on commit delete rows;
CREATE TABLE
postgres=# select relfilenode from pg_class where relname ='gtt1';
relfilenode
-------------
16384
(1 row)
postgres=# truncate gtt1;
TRUNCATE TABLE
postgres=# select relfilenode from pg_class where relname ='gtt1';
relfilenode
-------------
16384
(1 row)

postgres=# create global temporary table gtt2(c1 int) on commit preserve
rows;
CREATE TABLE
postgres=# select relfilenode from pg_class where relname ='gtt2';
relfilenode
-------------
16387
(1 row)
postgres=# truncate gtt2;
TRUNCATE TABLE
postgres=# select relfilenode from pg_class where relname ='gtt2';
relfilenode
-------------
16387
(1 row)

*Case 2: "relfilenode" changes after "TRUNCATE" for Simple table/Temp table*
postgres=# create temporary table temp3(c1 int) on commit preserve rows;
CREATE TABLE
postgres=# select relfilenode from pg_class where relname ='temp3';
relfilenode
-------------
16392
(1 row)
postgres=# truncate temp3;
TRUNCATE TABLE
postgres=# select relfilenode from pg_class where relname ='temp3';
relfilenode
-------------
16395
(1 row)

postgres=# create table tabl4(c1 int);
CREATE TABLE
postgres=# select relfilenode from pg_class where relname ='tabl4';
relfilenode
-------------
16396
(1 row)
postgres=# truncate tabl4;
TRUNCATE TABLE
postgres=# select relfilenode from pg_class where relname ='tabl4';
relfilenode
-------------
16399
(1 row)

On Thu, Mar 12, 2020 at 3:36 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com> wrote:

2020年3月12日 上午4:12,Robert Haas <robertmhaas@gmail.com> 写道:

On Wed, Mar 11, 2020 at 9:07 AM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>

wrote:

reindex need change relfilenode, but GTT is not currently supported.

In my view that'd have to be fixed somehow.

Ok , I am working on it.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

#187tushar
tushar.ahuja@enterprisedb.com
In reply to: 曾文旌(义从) (#181)
Re: [Proposal] Global temporary tables

On 3/9/20 10:01 PM, 曾文旌(义从) wrote:

Fixed in global_temporary_table_v18-pg13.patch.

Thanks Wenjing.

I am getting this error  "ERROR:  could not open file
"base/13589/t3_16440": No such file or directory" if
max_active_global_temporary_table set to 0

Please refer this scenario -

postgres=# create global temp table  tab1 (n int ) with (
on_commit_delete_rows='true');
CREATE TABLE
postgres=# insert into tab1 values (1);
INSERT 0 1
postgres=# select * from tab1;
 n
---
(0 rows)

postgres=# alter system set max_active_global_temporary_table=0;
ALTER SYSTEM
postgres=# \q
[tushar@localhost bin]$ ./pg_ctl -D data/ restart -c -l logs123

waiting for server to start.... done
server started

[tushar@localhost bin]$ ./psql postgres
psql (13devel)
Type "help" for help.

postgres=# insert into tab1 values (1);
ERROR:  could not open file "base/13589/t3_16440": No such file or directory
postgres=#

--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company

#188Prabhat Sahu
prabhat.sahu@enterprisedb.com
In reply to: tushar (#187)
Re: [Proposal] Global temporary tables

Hi Wenjing,

Please check the below combination of GTT with Primary and Foreign key
relations, with the ERROR message.

*Case1:*postgres=# CREATE GLOBAL TEMPORARY TABLE gtt1(c1 serial PRIMARY
KEY, c2 VARCHAR (50) UNIQUE NOT NULL) ON COMMIT *DELETE* ROWS;
CREATE TABLE

postgres=# CREATE GLOBAL TEMPORARY TABLE gtt2(c1 integer NOT NULL, c2
integer NOT NULL,
PRIMARY KEY (c1, c2),
FOREIGN KEY (c1) REFERENCES gtt1 (c1)) ON COMMIT *PRESERVE* ROWS;
ERROR: unsupported ON COMMIT and foreign key combination
DETAIL: Table "gtt2" references "gtt1", but *they do not have the same ON
COMMIT setting*.

*Case2:*
postgres=# CREATE GLOBAL TEMPORARY TABLE gtt1(c1 serial PRIMARY KEY, c2
VARCHAR (50) UNIQUE NOT NULL) ON COMMIT *PRESERVE* ROWS;
CREATE TABLE

postgres=# CREATE GLOBAL TEMPORARY TABLE gtt2(c1 integer NOT NULL, c2
integer NOT NULL,
PRIMARY KEY (c1, c2),
FOREIGN KEY (c1) REFERENCES gtt1 (c1)) ON COMMIT *DELETE* ROWS;
CREATE TABLE

In "case2" although both the primary table and foreign key GTT *do not have
the same ON COMMIT setting*, still we are able to create the PK-FK
relations with GTT.

So I hope the detail message(DETAIL: Table "gtt2" references "gtt1", but
they do not have the same ON COMMIT setting.) in "Case1" should be more
clear(something like "wrong combination of ON COMMIT setting").

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

#189Prabhat Sahu
prabhat.sahu@enterprisedb.com
In reply to: Prabhat Sahu (#188)
Re: [Proposal] Global temporary tables

Hi Wenjing,
Please check the below scenario, where the Foreign table on GTT not showing
records.

postgres=# create extension postgres_fdw;
CREATE EXTENSION
postgres=# do $d$
begin
execute $$create server fdw foreign data wrapper postgres_fdw
options (host 'localhost',dbname 'postgres',port
'$$||current_setting('port')||$$')$$;
end;
$d$;
DO
postgres=# create user mapping for public server fdw;
CREATE USER MAPPING

postgres=# create table lt1 (c1 integer, c2 varchar(50));
CREATE TABLE
postgres=# insert into lt1 values (1,'c21');
INSERT 0 1
postgres=# create foreign table ft1 (c1 integer, c2 varchar(50)) server fdw
options (table_name 'lt1');
CREATE FOREIGN TABLE
postgres=# select * from ft1;
c1 | c2
----+-----
1 | c21
(1 row)

postgres=# create global temporary table gtt1 (c1 integer, c2 varchar(50));
CREATE TABLE
postgres=# insert into gtt1 values (1,'gtt_c21');
INSERT 0 1
postgres=# create foreign table f_gtt1 (c1 integer, c2 varchar(50)) server
fdw options (table_name 'gtt1');
CREATE FOREIGN TABLE

postgres=# select * from gtt1;
c1 | c2
----+---------
1 | gtt_c21
(1 row)

postgres=# select * from f_gtt1;
c1 | c2
----+----
(0 rows)

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

#190Konstantin Knizhnik
k.knizhnik@postgrespro.ru
In reply to: Prabhat Sahu (#189)
Re: [Proposal] Global temporary tables

On 16.03.2020 9:23, Prabhat Sahu wrote:

Hi Wenjing,
Please check the below scenario, where the Foreign table on GTT not
showing records.

postgres=# create extension postgres_fdw;
CREATE EXTENSION
postgres=# do $d$
    begin
        execute $$create server fdw foreign data wrapper postgres_fdw
options (host 'localhost',dbname 'postgres',port
'$$||current_setting('port')||$$')$$;
    end;
$d$;
DO
postgres=# create user mapping for public server fdw;
CREATE USER MAPPING

postgres=# create table lt1 (c1 integer, c2 varchar(50));
CREATE TABLE
postgres=# insert into lt1 values (1,'c21');
INSERT 0 1
postgres=# create foreign table ft1 (c1 integer, c2 varchar(50))
server fdw options (table_name 'lt1');
CREATE FOREIGN TABLE
postgres=# select * from ft1;
 c1 | c2
----+-----
  1 | c21
(1 row)

postgres=# create global temporary table gtt1 (c1 integer, c2
varchar(50));
CREATE TABLE
postgres=# insert into gtt1 values (1,'gtt_c21');
INSERT 0 1
postgres=# create foreign table f_gtt1 (c1 integer, c2 varchar(50))
server fdw options (table_name 'gtt1');
CREATE FOREIGN TABLE

postgres=# select * from gtt1;
 c1 |   c2
----+---------
  1 | gtt_c21
(1 row)

postgres=# select * from f_gtt1;
 c1 | c2
----+----
(0 rows)

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

It seems to be expected behavior: GTT data is private to the session and
postgres_fdw establish its own session where content of the table is empty.
But if you insert some data in f_gtt1, then you will be able to select
this data from it because of connection cache in postgres_fdw.

--

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

#191曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Prabhat Sahu (#189)
Re: [Proposal] Global temporary tables

2020年3月16日 下午2:23,Prabhat Sahu <prabhat.sahu@enterprisedb.com> 写道:

Hi Wenjing,
Please check the below scenario, where the Foreign table on GTT not showing records.

postgres=# create extension postgres_fdw;
CREATE EXTENSION
postgres=# do $d$
begin
execute $$create server fdw foreign data wrapper postgres_fdw options (host 'localhost',dbname 'postgres',port '$$||current_setting('port')||$$')$$;
end;
$d$;
DO
postgres=# create user mapping for public server fdw;
CREATE USER MAPPING

postgres=# create table lt1 (c1 integer, c2 varchar(50));
CREATE TABLE
postgres=# insert into lt1 values (1,'c21');
INSERT 0 1
postgres=# create foreign table ft1 (c1 integer, c2 varchar(50)) server fdw options (table_name 'lt1');
CREATE FOREIGN TABLE
postgres=# select * from ft1;
c1 | c2
----+-----
1 | c21
(1 row)

postgres=# create global temporary table gtt1 (c1 integer, c2 varchar(50));
CREATE TABLE
postgres=# insert into gtt1 values (1,'gtt_c21');
INSERT 0 1
postgres=# create foreign table f_gtt1 (c1 integer, c2 varchar(50)) server fdw options (table_name 'gtt1');
CREATE FOREIGN TABLE

postgres=# select * from gtt1;
c1 | c2
----+---------
1 | gtt_c21
(1 row)

postgres=# select * from f_gtt1;
c1 | c2
----+----
(0 rows)

--

I understand that postgre_fdw works similar to dblink.
postgre_fdw access to the table requires a new connection.
The data in the GTT table is empty in the newly established connection.
Because GTT shares structure but not data between connections.

Try local temp table:
create temporary table ltt1 (c1 integer, c2 varchar(50));

insert into ltt1 values (1,'gtt_c21');

create foreign table f_ltt1 (c1 integer, c2 varchar(50)) server fdw options (table_name 'ltt1');

select * from ltt1;
c1 | c2
----+---------
1 | gtt_c21
(1 row)

select * from l_gtt1;
ERROR: relation "l_gtt1" does not exist
LINE 1: select * from l_gtt1;

Wenjing

Show quoted text

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

#192tushar
tushar.ahuja@enterprisedb.com
In reply to: 曾文旌(义从) (#191)
Re: [Proposal] Global temporary tables

Hi Wenjing,

I have created a global table on X session but i am not able to drop
from Y session ?

X session - ( connect to psql terminal )
postgres=# create global temp table foo(n int);
CREATE TABLE
postgres=# select * from foo;
 n
---
(0 rows)

Y session - ( connect to psql terminal )
postgres=# drop table foo;
ERROR:  can not drop relation foo when other backend attached this
global temp table

Table has been created  so i think - user should be able to drop from
another session as well without exit from X session.

regards,

On 3/16/20 1:35 PM, 曾文旌(义从) wrote:

2020年3月16日 下午2:23,Prabhat Sahu <prabhat.sahu@enterprisedb.com
<mailto:prabhat.sahu@enterprisedb.com>> 写道:

Hi Wenjing,
Please check the below scenario, where the Foreign table on GTT not
showing records.

postgres=# create extension postgres_fdw;
CREATE EXTENSION
postgres=# do $d$
    begin
        execute $$create server fdw foreign data wrapper postgres_fdw
options (host 'localhost',dbname 'postgres',port
'$$||current_setting('port')||$$')$$;
    end;
$d$;
DO
postgres=# create user mapping for public server fdw;
CREATE USER MAPPING

postgres=# create table lt1 (c1 integer, c2 varchar(50));
CREATE TABLE
postgres=# insert into lt1 values (1,'c21');
INSERT 0 1
postgres=# create foreign table ft1 (c1 integer, c2 varchar(50))
server fdw options (table_name 'lt1');
CREATE FOREIGN TABLE
postgres=# select * from ft1;
 c1 | c2
----+-----
  1 | c21
(1 row)

postgres=# create global temporary table gtt1 (c1 integer, c2
varchar(50));
CREATE TABLE
postgres=# insert into gtt1 values (1,'gtt_c21');
INSERT 0 1
postgres=# create foreign table f_gtt1 (c1 integer, c2 varchar(50))
server fdw options (table_name 'gtt1');
CREATE FOREIGN TABLE

postgres=# select * from gtt1;
 c1 |   c2
----+---------
  1 | gtt_c21
(1 row)

postgres=# select * from f_gtt1;
 c1 | c2
----+----
(0 rows)

--

I understand that postgre_fdw works similar to dblink.
postgre_fdw access to the table requires a new connection.
The data in the GTT table is empty in the newly established connection.
Because GTT shares structure but not data between connections.

Try local temp table:
create temporary table ltt1 (c1 integer, c2 varchar(50));

insert into ltt1 values (1,'gtt_c21');

create foreign table f_ltt1 (c1 integer, c2 varchar(50)) server fdw
options (table_name 'ltt1');

select * from ltt1;
 c1 |   c2
----+---------
  1 | gtt_c21
(1 row)

select * from l_gtt1;
ERROR:  relation "l_gtt1" does not exist
LINE 1: select * from l_gtt1;

Wenjing

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company

#193Pavel Stehule
pavel.stehule@gmail.com
In reply to: tushar (#192)
Re: [Proposal] Global temporary tables

po 16. 3. 2020 v 9:58 odesílatel tushar <tushar.ahuja@enterprisedb.com>
napsal:

Hi Wenjing,

I have created a global table on X session but i am not able to drop from
Y session ?

X session - ( connect to psql terminal )
postgres=# create global temp table foo(n int);
CREATE TABLE
postgres=# select * from foo;
n
---
(0 rows)

Y session - ( connect to psql terminal )
postgres=# drop table foo;
ERROR: can not drop relation foo when other backend attached this global
temp table

Table has been created so i think - user should be able to drop from
another session as well without exit from X session.

By the original design GTT was not modifiable until is used by any session.
Now, you cannot to drop normal table when this table is used.

It is hard to say what is most correct behave and design, but for this
moment, I think so protecting table against drop while it is used by other
session is the best behave.

Maybe for next release we can introduce DROP TABLE x (FORCE) - like we have
for DROP DATABASE. This behave is very similar.

Pavel

Show quoted text

regards,

On 3/16/20 1:35 PM, 曾文旌(义从) wrote:

2020年3月16日 下午2:23,Prabhat Sahu <prabhat.sahu@enterprisedb.com> 写道:

Hi Wenjing,
Please check the below scenario, where the Foreign table on GTT not
showing records.

postgres=# create extension postgres_fdw;
CREATE EXTENSION
postgres=# do $d$
begin
execute $$create server fdw foreign data wrapper postgres_fdw
options (host 'localhost',dbname 'postgres',port
'$$||current_setting('port')||$$')$$;
end;
$d$;
DO
postgres=# create user mapping for public server fdw;
CREATE USER MAPPING

postgres=# create table lt1 (c1 integer, c2 varchar(50));
CREATE TABLE
postgres=# insert into lt1 values (1,'c21');
INSERT 0 1
postgres=# create foreign table ft1 (c1 integer, c2 varchar(50)) server
fdw options (table_name 'lt1');
CREATE FOREIGN TABLE
postgres=# select * from ft1;
c1 | c2
----+-----
1 | c21
(1 row)

postgres=# create global temporary table gtt1 (c1 integer, c2 varchar(50));
CREATE TABLE
postgres=# insert into gtt1 values (1,'gtt_c21');
INSERT 0 1
postgres=# create foreign table f_gtt1 (c1 integer, c2 varchar(50)) server
fdw options (table_name 'gtt1');
CREATE FOREIGN TABLE

postgres=# select * from gtt1;
c1 | c2
----+---------
1 | gtt_c21
(1 row)

postgres=# select * from f_gtt1;
c1 | c2
----+----
(0 rows)

--

I understand that postgre_fdw works similar to dblink.
postgre_fdw access to the table requires a new connection.
The data in the GTT table is empty in the newly established connection.
Because GTT shares structure but not data between connections.

Try local temp table:
create temporary table ltt1 (c1 integer, c2 varchar(50));

insert into ltt1 values (1,'gtt_c21');

create foreign table f_ltt1 (c1 integer, c2 varchar(50)) server fdw
options (table_name 'ltt1');

select * from ltt1;
c1 | c2
----+---------
1 | gtt_c21
(1 row)

select * from l_gtt1;
ERROR: relation "l_gtt1" does not exist
LINE 1: select * from l_gtt1;

Wenjing

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company

#194曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: tushar (#192)
Re: [Proposal] Global temporary tables

2020年3月16日 下午4:58,tushar <tushar.ahuja@enterprisedb.com> 写道:

Hi Wenjing,

I have created a global table on X session but i am not able to drop from Y session ?

X session - ( connect to psql terminal )
postgres=# create global temp table foo(n int);
CREATE TABLE
postgres=# select * from foo;
n
---
(0 rows)

Y session - ( connect to psql terminal )
postgres=# drop table foo;
ERROR: can not drop relation foo when other backend attached this global temp table

For now, If one dba wants to drop one GTT,
he can use the view pg_gtt_attached_pids to see which backends are using this GTT.
then kill these sessions with pg_terminate_backend, and he can drop this GTT.

Show quoted text

Table has been created so i think - user should be able to drop from another session as well without exit from X session.

regards,

On 3/16/20 1:35 PM, 曾文旌(义从) wrote:

2020年3月16日 下午2:23,Prabhat Sahu <prabhat.sahu@enterprisedb.com <mailto:prabhat.sahu@enterprisedb.com>> 写道:

Hi Wenjing,
Please check the below scenario, where the Foreign table on GTT not showing records.

postgres=# create extension postgres_fdw;
CREATE EXTENSION
postgres=# do $d$
begin
execute $$create server fdw foreign data wrapper postgres_fdw options (host 'localhost',dbname 'postgres',port '$$||current_setting('port')||$$')$$;
end;
$d$;
DO
postgres=# create user mapping for public server fdw;
CREATE USER MAPPING

postgres=# create table lt1 (c1 integer, c2 varchar(50));
CREATE TABLE
postgres=# insert into lt1 values (1,'c21');
INSERT 0 1
postgres=# create foreign table ft1 (c1 integer, c2 varchar(50)) server fdw options (table_name 'lt1');
CREATE FOREIGN TABLE
postgres=# select * from ft1;
c1 | c2
----+-----
1 | c21
(1 row)

postgres=# create global temporary table gtt1 (c1 integer, c2 varchar(50));
CREATE TABLE
postgres=# insert into gtt1 values (1,'gtt_c21');
INSERT 0 1
postgres=# create foreign table f_gtt1 (c1 integer, c2 varchar(50)) server fdw options (table_name 'gtt1');
CREATE FOREIGN TABLE

postgres=# select * from gtt1;
c1 | c2
----+---------
1 | gtt_c21
(1 row)

postgres=# select * from f_gtt1;
c1 | c2
----+----
(0 rows)

--

I understand that postgre_fdw works similar to dblink.
postgre_fdw access to the table requires a new connection.
The data in the GTT table is empty in the newly established connection.
Because GTT shares structure but not data between connections.

Try local temp table:
create temporary table ltt1 (c1 integer, c2 varchar(50));

insert into ltt1 values (1,'gtt_c21');

create foreign table f_ltt1 (c1 integer, c2 varchar(50)) server fdw options (table_name 'ltt1');

select * from ltt1;
c1 | c2
----+---------
1 | gtt_c21
(1 row)

select * from l_gtt1;
ERROR: relation "l_gtt1" does not exist
LINE 1: select * from l_gtt1;

Wenjing

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/ <https://www.enterprisedb.com/&gt;
The Enterprise PostgreSQL Company

#195曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Pavel Stehule (#193)
Re: [Proposal] Global temporary tables

2020年3月16日 下午5:04,Pavel Stehule <pavel.stehule@gmail.com> 写道:

po 16. 3. 2020 v 9:58 odesílatel tushar <tushar.ahuja@enterprisedb.com <mailto:tushar.ahuja@enterprisedb.com>> napsal:
Hi Wenjing,

I have created a global table on X session but i am not able to drop from Y session ?

X session - ( connect to psql terminal )
postgres=# create global temp table foo(n int);
CREATE TABLE
postgres=# select * from foo;
n
---
(0 rows)

Y session - ( connect to psql terminal )
postgres=# drop table foo;
ERROR: can not drop relation foo when other backend attached this global temp table

Table has been created so i think - user should be able to drop from another session as well without exit from X session.

By the original design GTT was not modifiable until is used by any session. Now, you cannot to drop normal table when this table is used.

It is hard to say what is most correct behave and design, but for this moment, I think so protecting table against drop while it is used by other session is the best behave.

Maybe for next release we can introduce DROP TABLE x (FORCE) - like we have for DROP DATABASE. This behave is very similar.

I agree with that.

Wenjing

Show quoted text

Pavel

regards,

On 3/16/20 1:35 PM, 曾文旌(义从) wrote:

2020年3月16日 下午2:23,Prabhat Sahu <prabhat.sahu@enterprisedb.com <mailto:prabhat.sahu@enterprisedb.com>> 写道:

Hi Wenjing,
Please check the below scenario, where the Foreign table on GTT not showing records.

postgres=# create extension postgres_fdw;
CREATE EXTENSION
postgres=# do $d$
begin
execute $$create server fdw foreign data wrapper postgres_fdw options (host 'localhost',dbname 'postgres',port '$$||current_setting('port')||$$')$$;
end;
$d$;
DO
postgres=# create user mapping for public server fdw;
CREATE USER MAPPING

postgres=# create table lt1 (c1 integer, c2 varchar(50));
CREATE TABLE
postgres=# insert into lt1 values (1,'c21');
INSERT 0 1
postgres=# create foreign table ft1 (c1 integer, c2 varchar(50)) server fdw options (table_name 'lt1');
CREATE FOREIGN TABLE
postgres=# select * from ft1;
c1 | c2
----+-----
1 | c21
(1 row)

postgres=# create global temporary table gtt1 (c1 integer, c2 varchar(50));
CREATE TABLE
postgres=# insert into gtt1 values (1,'gtt_c21');
INSERT 0 1
postgres=# create foreign table f_gtt1 (c1 integer, c2 varchar(50)) server fdw options (table_name 'gtt1');
CREATE FOREIGN TABLE

postgres=# select * from gtt1;
c1 | c2
----+---------
1 | gtt_c21
(1 row)

postgres=# select * from f_gtt1;
c1 | c2
----+----
(0 rows)

--

I understand that postgre_fdw works similar to dblink.
postgre_fdw access to the table requires a new connection.
The data in the GTT table is empty in the newly established connection.
Because GTT shares structure but not data between connections.

Try local temp table:
create temporary table ltt1 (c1 integer, c2 varchar(50));

insert into ltt1 values (1,'gtt_c21');

create foreign table f_ltt1 (c1 integer, c2 varchar(50)) server fdw options (table_name 'ltt1');

select * from ltt1;
c1 | c2
----+---------
1 | gtt_c21
(1 row)

select * from l_gtt1;
ERROR: relation "l_gtt1" does not exist
LINE 1: select * from l_gtt1;

Wenjing

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/ <https://www.enterprisedb.com/&gt;
The Enterprise PostgreSQL Company

#196Prabhat Sahu
prabhat.sahu@enterprisedb.com
In reply to: Konstantin Knizhnik (#190)
Re: [Proposal] Global temporary tables

On Mon, Mar 16, 2020 at 1:30 PM Konstantin Knizhnik <
k.knizhnik@postgrespro.ru> wrote:

It seems to be expected behavior: GTT data is private to the session and
postgres_fdw establish its own session where content of the table is empty.
But if you insert some data in f_gtt1, then you will be able to select
this data from it because of connection cache in postgres_fdw.

Thanks for the explanation.
I am able to insert and select the value from f_gtt1.

postgres=# insert into f_gtt1 values (1,'gtt_c21');
INSERT 0 1
postgres=# select * from f_gtt1;
c1 | c2
----+---------
1 | gtt_c21
(1 row)

I have one more doubt,
As you told above "GTT data is private to the session and postgres_fdw
establish its own session where content of the table is empty."
Please check the below scenario,
we can select data from the "root GTT" and "foreign GTT partitioned table"
but we are unable to select data from "GTT partitioned table"

postgres=# create global temporary table gtt2 (c1 integer, c2 integer)
partition by range(c1);
CREATE TABLE
postgres=# create global temporary table gtt2_p1 (c1 integer, c2 integer);
CREATE TABLE
postgres=# create foreign table f_gtt2_p1 (c1 integer, c2 integer) server
fdw options (table_name 'gtt2_p1');
CREATE FOREIGN TABLE
postgres=# alter table gtt2 attach partition f_gtt2_p1 for values from
(minvalue) to (10);
ALTER TABLE
postgres=# insert into gtt2 select i,i from generate_series(1,5,2)i;
INSERT 0 3
postgres=# select * from gtt2;
c1 | c2
----+----
1 | 1
3 | 3
5 | 5
(3 rows)

postgres=# select * from gtt2_p1;
c1 | c2
----+----
(0 rows)

postgres=# select * from f_gtt2_p1;
c1 | c2
----+----
1 | 1
3 | 3
5 | 5
(3 rows)

Is this an expected behavior?

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

#197曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Prabhat Sahu (#196)
Re: [Proposal] Global temporary tables

2020年3月16日 下午5:31,Prabhat Sahu <prabhat.sahu@enterprisedb.com> 写道:

On Mon, Mar 16, 2020 at 1:30 PM Konstantin Knizhnik <k.knizhnik@postgrespro.ru <mailto:k.knizhnik@postgrespro.ru>> wrote:

It seems to be expected behavior: GTT data is private to the session and postgres_fdw establish its own session where content of the table is empty.
But if you insert some data in f_gtt1, then you will be able to select this data from it because of connection cache in postgres_fdw.

Thanks for the explanation.
I am able to insert and select the value from f_gtt1.

postgres=# insert into f_gtt1 values (1,'gtt_c21');
INSERT 0 1
postgres=# select * from f_gtt1;
c1 | c2
----+---------
1 | gtt_c21
(1 row)

I have one more doubt,
As you told above "GTT data is private to the session and postgres_fdw establish its own session where content of the table is empty."
Please check the below scenario,
we can select data from the "root GTT" and "foreign GTT partitioned table" but we are unable to select data from "GTT partitioned table"

postgres=# select pg_backend_pid();
pg_backend_pid
----------------
119135
(1 row)

postgres=# select * from pg_gtt_attached_pids;
schemaname | tablename | relid | pid
------------+-----------+-------+--------
public | gtt2_p1 | 73845 | 119135
public | gtt2_p1 | 73845 | 51482
(2 rows)

postgres=# select datid,datname,pid,application_name,query from pg_stat_activity where usename = ‘wenjing';
datid | datname | pid | application_name | query
-------+----------+--------+------------------+------------------------------------------------------------------------------------------------------
13589 | postgres | 119135 | psql | select datid,datname,pid,application_name,query from pg_stat_activity where usename = 'wenjing';
13589 | postgres | 51482 | postgres_fdw | COMMIT TRANSACTION
(2 rows)

This can be explained
The postgre_fdw connection has not been disconnected, and it produced data in another session.
In other words, gtt2_p1 is empty in session 119135, but not in session 51482.

Show quoted text

postgres=# create global temporary table gtt2 (c1 integer, c2 integer) partition by range(c1);
CREATE TABLE
postgres=# create global temporary table gtt2_p1 (c1 integer, c2 integer);
CREATE TABLE
postgres=# create foreign table f_gtt2_p1 (c1 integer, c2 integer) server fdw options (table_name 'gtt2_p1');
CREATE FOREIGN TABLE
postgres=# alter table gtt2 attach partition f_gtt2_p1 for values from (minvalue) to (10);
ALTER TABLE
postgres=# insert into gtt2 select i,i from generate_series(1,5,2)i;
INSERT 0 3
postgres=# select * from gtt2;
c1 | c2
----+----
1 | 1
3 | 3
5 | 5
(3 rows)

postgres=# select * from gtt2_p1;
c1 | c2
----+----
(0 rows)

postgres=# select * from f_gtt2_p1;
c1 | c2
----+----
1 | 1
3 | 3
5 | 5
(3 rows)

Is this an expected behavior?

--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

#198曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Prabhat Sahu (#186)
1 attachment(s)
Re: [Proposal] Global temporary tables

2020年3月12日 下午8:22,Prabhat Sahu <prabhat.sahu@enterprisedb.com> 写道:

Hi Wenjing,

Please check the below findings:
After running "TRUNCATE" command, the "relfilenode" field is not changing for GTT
whereas, for Simple table/Temp table "relfilenode" field is changing after TRUNCATE.

Case 1: Getting same "relfilenode" for GTT after and before "TRUNCATE"
postgres=# create global temporary table gtt1(c1 int) on commit delete rows;
CREATE TABLE
postgres=# select relfilenode from pg_class where relname ='gtt1';
relfilenode
-------------
16384
(1 row)
postgres=# truncate gtt1;
TRUNCATE TABLE
postgres=# select relfilenode from pg_class where relname ='gtt1';
relfilenode
-------------
16384
(1 row)

postgres=# create global temporary table gtt2(c1 int) on commit preserve rows;
CREATE TABLE
postgres=# select relfilenode from pg_class where relname ='gtt2';
relfilenode
-------------
16387
(1 row)
postgres=# truncate gtt2;
TRUNCATE TABLE
postgres=# select relfilenode from pg_class where relname ='gtt2';
relfilenode
-------------
16387
(1 row)

Case 2: "relfilenode" changes after "TRUNCATE" for Simple table/Temp table
postgres=# create temporary table temp3(c1 int) on commit preserve rows;
CREATE TABLE
postgres=# select relfilenode from pg_class where relname ='temp3';
relfilenode
-------------
16392
(1 row)
postgres=# truncate temp3;
TRUNCATE TABLE
postgres=# select relfilenode from pg_class where relname ='temp3';
relfilenode
-------------
16395
(1 row)

postgres=# create table tabl4(c1 int);
CREATE TABLE
postgres=# select relfilenode from pg_class where relname ='tabl4';
relfilenode
-------------
16396
(1 row)
postgres=# truncate tabl4;
TRUNCATE TABLE
postgres=# select relfilenode from pg_class where relname ='tabl4';
relfilenode
-------------
16399
(1 row)

Truncated GTT has been supported.
Now it clears the data in the table by switching relfilenode and can support rollback.
Note that the latest relfilenode in GTT is not stored in pg_class, you can view them in the view pg_gtt_stats.

postgres=# create global temp table gtt1(a int primary key);
CREATE TABLE
postgres=# insert into gtt1 select generate_series(1,10000);
INSERT 0 10000
postgres=# select tablename,relfilenode from pg_gtt_relstats;
tablename | relfilenode
-----------+-------------
gtt1 | 16406
gtt1_pkey | 16409
(2 rows)
postgres=# truncate gtt1;
TRUNCATE TABLE
postgres=#
postgres=# select tablename,relfilenode from pg_gtt_relstats;
tablename | relfilenode
-----------+-------------
gtt1 | 16411
gtt1_pkey | 16412
(2 rows)

Wenjing

Show quoted text

On Thu, Mar 12, 2020 at 3:36 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> wrote:

2020年3月12日 上午4:12,Robert Haas <robertmhaas@gmail.com <mailto:robertmhaas@gmail.com>> 写道:

On Wed, Mar 11, 2020 at 9:07 AM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> wrote:

reindex need change relfilenode, but GTT is not currently supported.

In my view that'd have to be fixed somehow.

Ok , I am working on it.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;
The Enterprise PostgreSQL Company

--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

Attachments:

global_temporary_table_v20-pg13.patchapplication/octet-stream; name=global_temporary_table_v20-pg13.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index ec207d3..eb5d8cf 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -168,6 +168,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * For global temp table only
+	 * use ShareUpdateExclusiveLock for ensure safety
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temp table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},	
 	/* list terminator */
 	{{NULL}}
 };
@@ -1509,6 +1522,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)},
@@ -1609,13 +1624,18 @@ build_reloptions(Datum reloptions, bool validate,
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	/*
 	 * There are no options for partitioned tables yet, but this is able to do
 	 * some validation.
 	 */
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index dd975b1..1610e7d 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1013,7 +1013,9 @@ gistGetFakeLSN(Relation rel)
 {
 	static XLogRecPtr counter = FirstNormalUnloggedLSN;
 
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	/* global temp is same as local temp table */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 4871b7f..16b00c9 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -149,7 +149,9 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	/* global temp table is same as local temp table */
+	if (!(index->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		index->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ca52846..f0153cf 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -598,7 +598,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -651,7 +651,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 03c43ef..2674132 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -62,6 +62,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -399,9 +400,9 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
 	Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
-	/* not every AM requires these to be valid, but heap does */
-	Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
-	Assert(MultiXactIdIsValid(onerel->rd_rel->relminmxid));
+	/* not every AM requires these to be valid, but regular heap does */
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ MultiXactIdIsValid(onerel->rd_rel->relminmxid));
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 39b8f17..abb76dc 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -601,6 +602,10 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index de2d4ee..7e120b5 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6348,6 +6348,10 @@ StartupXLOG(void)
 	else
 		recoveryTargetTLI = ControlFile->checkPointCopy.ThisTimeLineID;
 
+	/* clean temp relation files */
+	if (max_active_gtt > 0)
+		RemovePgTempFiles();
+
 	/*
 	 * Check for signal files, and if so set up state for offline recovery
 	 */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 9499bb3..ae47364 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -43,6 +43,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 7d6acae..3be8d63 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -393,7 +393,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/* global temp table is same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 9d9e915..785a5cd 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -427,7 +429,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -957,6 +959,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -995,8 +998,18 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/* global temp table not remember transaction info in catalog */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1358,6 +1371,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1938,6 +1952,14 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/* We allow to drop global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			elog(ERROR, "can not drop relation %s when other backend attached this global temp table",
+						RelationGetRelationName(rel));
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3165,7 +3187,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3177,7 +3199,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3223,8 +3245,13 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		/* truncate global temp table only need RowExclusiveLock */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3257,6 +3284,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3265,23 +3293,37 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (!gtt_storage_attached(RelationGetRelid(rel)))
+			return;
+
+		/*
+		 * Truncate global temp table only need RowExclusiveLock
+		 */
+		lockmode = RowExclusiveLock;
+	}
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(rel, 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 76fd938..5203ed2 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -52,6 +52,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -877,6 +878,19 @@ index_create(Relation heapRelation,
 						indexRelationName, RelationGetRelationName(heapRelation))));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temp table use concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot create indexes on global temporary tables using concurrent mode")));
+
+		/* if global temp table not init storage, then skip build index */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -2061,6 +2075,14 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/* We allow to drop index on global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+			elog(ERROR, "can not drop index %s when other backend attached this global temp table.",
+						RelationGetRelationName(userHeapRelation));
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2667,6 +2689,11 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	/* update index stats into localhash and rel_rd_rel for global temp table */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		is_gtt = true;
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2752,21 +2779,35 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
+		if (is_gtt)
+			rel->rd_rel->relpages = (int32) relpages;
+		else if (rd_rel->relpages != (int32) relpages)
 		{
 			rd_rel->relpages = (int32) relpages;
 			dirty = true;
 		}
-		if (rd_rel->reltuples != (float4) reltuples)
+
+		if (is_gtt)
+			rel->rd_rel->reltuples = (float4) reltuples;
+		else if (rd_rel->reltuples != (float4) reltuples)
 		{
 			rd_rel->reltuples = (float4) reltuples;
 			dirty = true;
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+
+		if (is_gtt)
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+		else if (rd_rel->relallvisible != (int32) relallvisible)
 		{
 			rd_rel->relallvisible = (int32) relallvisible;
 			dirty = true;
 		}
+
+		if (is_gtt)
+		{
+			up_gtt_relstats(rel, relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
+		}
 	}
 
 	/*
@@ -2880,6 +2921,15 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, index, val);
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			gtt_force_enable_index(indexRelation);
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3428,6 +3478,10 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	PGRUsage	ru0;
 	bool		progress = (options & REINDEXOPT_REPORT_PROGRESS) != 0;
 
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		!gtt_storage_attached(indexId))
+		return;
+
 	pg_rusage_init(&ru0);
 
 	/*
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 5ff7824..9b58f2a 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -647,6 +647,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		/* global temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp relations in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index fddfbf1..c3661e3 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -26,6 +26,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -56,6 +57,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			relOid;			/* InvalidOid if not a global temp rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -75,7 +77,7 @@ static PendingRelDelete *pendingDeletes = NULL; /* head of linked list */
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -85,6 +87,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;
@@ -112,11 +116,19 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->relOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/* remember global temp table storage info to localhash */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel)
+	{
+		pending->relOid = RelationGetRelid(rel);
+		remember_gtt_storage_info(rnode, rel);
+	}
+
 	return srel;
 }
 
@@ -153,11 +165,15 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->relOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->relOid = RelationGetRelid(rel);
+
 	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
@@ -437,6 +453,7 @@ smgrDoPendingDeletes(bool isCommit)
 				i = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -466,14 +483,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->relOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -486,9 +507,18 @@ smgrDoPendingDeletes(bool isCommit)
 		smgrdounlinkall(srels, nrels, false);
 
 		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* clean global temp table flags when transaction commit or rollback */
+			if (SmgrIsTemp(srels[i]) &&
+				reloids[i] != InvalidOid &&
+				gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..42cbeb6
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1440 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  code to create and destroy physical storage for global temparary table
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_relstats_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+typedef struct gtt_ctl_data
+{
+	LWLock			lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+	Oid			relfilenode;
+	Oid			spcnode;
+
+	/* pg_class stat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+
+	/* pg_statistic */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(RelFileNode rnode);
+static void gtt_storage_checkout(RelFileNode rnode, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_reset_statistics(gtt_local_hash_entry *entry);
+static void gtt_free_statistics(gtt_local_hash_entry *entry);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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(gtt_fnode);
+	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;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = rnode.dbNode;
+	fnode.relNode = rnode.relNode;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *)&(fnode), 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_global_temporary_table.")));
+	}
+
+	if (found == false)
+	{
+		int			wordnum;
+
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(RelFileNode rnode, bool skiplock, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode				fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (!skiplock)
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+
+	fnode.dbNode = rnode.dbNode;
+	fnode.relNode = rnode.relNode;
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		if (!skiplock)
+			LWLockRelease(&gtt_shared_ctl->lock);
+
+		if (isCommit)
+		{
+			elog(WARNING, "relfilenode %u/%u/%u not exist in gtt shared hash when forget",
+						rnode.dbNode, rnode.spcNode, rnode.relNode);
+		}
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+	bms_del_member(entry->map, MyBackendId);
+
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, 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;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	fnode.dbNode = node.dbNode;
+	fnode.relNode = node.relNode;
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool		in_use = false;
+	int			num_use = 0;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	fnode.dbNode = MyBackendId;
+	fnode.relNode = relid;
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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;
+	gtt_relfilenode			*new_node = NULL;
+	Oid						relid = RelationGetRelid(rel);
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		/* First time through: initialize the hash table */
+		HASHCTL		ctl;
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_relstats_context =
+			AllocSetContextCreate(CacheMemoryContext,
+							"gtt relstats context",
+							ALLOCSET_DEFAULT_SIZES);
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+	if (!entry)
+	{
+		bool		found = false;
+		int 		natts = 0;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash,
+						(void *) &relid, HASH_ENTER, &found);
+
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+		entry->natts = 0;
+		entry->attnum = NULL;
+		entry->att_stat_tups = NULL;
+
+		natts = RelationGetNumberOfAttributes(rel);
+		entry->attnum = palloc0(sizeof(int) * natts);
+		entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+		entry->natts = natts;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			RelFileNode	gtt_rnode;
+			gtt_rnode.spcNode = InvalidOid;
+			gtt_rnode.dbNode = rnode.dbNode;
+			gtt_rnode.relNode = relid;
+			gtt_storage_checkin(gtt_rnode);
+		}
+	}
+
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* only heap contain transaction information */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	gtt_reset_statistics(entry);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	ListCell		*lc;
+	gtt_relfilenode *d_rnode = NULL;
+	RelFileNode		relnode;
+
+	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)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+		return;
+	}
+
+	relnode.spcNode = InvalidOid;
+	relnode.dbNode = MyDatabaseId;
+	relnode.relNode = entry->relid;
+
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == rnode.relNode &&
+			gtt_rnode->spcnode == rnode.spcNode)
+		{
+			d_rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+
+		if (entry->relfilenode_list == NIL)
+		{
+			if (entry->relkind == RELKIND_RELATION ||
+				entry->relkind == RELKIND_SEQUENCE)
+				gtt_storage_checkout(relnode, false, isCommit);
+
+			gtt_free_statistics(entry);
+			hash_search(gtt_storage_local_hash,
+					(void *) &(relid), HASH_REMOVE, NULL);
+		}
+
+		return;
+	}
+
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	pfree(d_rnode);
+	if (entry->relfilenode_list == NIL)
+	{
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(relnode, false, isCommit);
+
+		gtt_free_statistics(entry);
+		hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_REMOVE, NULL);
+	}
+	else
+		gtt_reset_statistics(entry);
+
+	return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool found;
+
+	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;
+	SMgrRelation	*srels = NULL;
+	RelFileNode		*rnodes = NULL;
+	char			*relkinds = NULL;
+	int			nrels = 0,
+				nfiles = 0,
+				maxrels = 0,
+				maxfiles = 0,
+				i = 0;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel;
+			RelFileNode 	rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel = smgropen(rnode, MyBackendId);
+
+			if (maxfiles == 0)
+			{
+				maxfiles = 32;
+				srels = palloc(sizeof(SMgrRelation) * maxfiles);
+			}
+			else if (maxfiles <= nfiles)
+			{
+				maxfiles *= 2;
+				srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+			}
+
+			srels[nfiles++] = srel;
+		}
+
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			rnodes = palloc(sizeof(RelFileNode) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			rnodes = repalloc(rnodes, sizeof(RelFileNode) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		relkinds[nrels] = entry->relkind;
+		rnodes[nrels].relNode = entry->relid;
+		rnodes[nrels].dbNode = MyDatabaseId;
+		rnodes[nrels].spcNode = InvalidOid;
+		nrels++;
+	}
+
+	if (nfiles > 0)
+	{
+		smgrdounlinkall(srels, nfiles, false);
+		for (i = 0; i < nfiles; i++)
+			smgrclose(srels[i]);
+
+		pfree(srels);
+	}
+
+	if (nrels)
+	{
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+		for (i = 0; i < nrels; i++)
+		{
+			if (relkinds[i] == RELKIND_RELATION ||
+				relkinds[i] == RELKIND_SEQUENCE)
+				gtt_storage_checkout(rnodes[i], true, false);
+		}
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+		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;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_FIND, &found);
+
+	if (!found)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (num_pages >= 0 &&
+		gtt_rnode->relpages != (int32)num_pages)
+		gtt_rnode->relpages = (int32)num_pages;
+
+	if (num_tuples >= 0 &&
+		gtt_rnode->reltuples != (float4)num_tuples)
+		gtt_rnode->reltuples = (float4)num_tuples;
+
+	/* only heap contain transaction information and relallvisible */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (gtt_rnode->relallvisible >= 0 &&
+			gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+		{
+			gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+		}
+
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNewTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible) 
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (gtt_storage_local_hash == NULL)
+		return false;
+
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_FIND, &found);
+
+	if (!found)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	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->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	Assert(entry->relid == reloid);
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == 0)
+		{
+			entry->attnum[i] = attnum;
+			break;
+		}
+		else if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < entry->natts);
+	Assert(entry->att_stat_tups[i] == NULL);
+	entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+						(void *) &reloid, HASH_FIND, &found);
+
+	if (!found)
+		return NULL;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			return entry->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int			i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	HeapTuple	tuple;
+	Relation	rel = NULL;
+	int		attnum = PG_GETARG_INT32(1);
+	Oid		reloid = PG_GETARG_OID(0);
+	char		rel_persistence;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	  tupdesc;
+	MemoryContext oldcontext;
+	Tuplestorestate *tupstore;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+
+	relation_close(rel, NoLock);
+	relation_close(pg_tatistic, AccessShareLock);
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate	*tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Oid			relnode = 0;
+	Relation	rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[6];
+		bool	isnull[6];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(rel->rd_node);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(indexOid));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid relOid = index->rd_index->indrelid;
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	if (gtt_storage_attached(relOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int			i;
+	Oid			toastrelid;
+
+	if (operation != CMD_INSERT)
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+
+		index_build(relation, index, info, true, false);
+	}
+
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid 		indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo 	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+static void
+gtt_reset_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+
+		entry->attnum[i] = 0;
+	}
+
+	return;
+}
+
+static void
+gtt_free_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (entry->attnum)
+		pfree(entry->attnum);
+
+	if (entry->att_stat_tups)
+		pfree(entry->att_stat_tups);
+
+	return;
+}
+
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	if (gtt_storage_local_hash == NULL)
+		return InvalidOid;
+
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_FIND, &found);
+
+	if (!found)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index b8a3f46..4b46d77 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM 
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 924ef37..db12eef 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -184,6 +185,13 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -586,14 +594,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 		 * pg_statistic for columns we didn't process, we leave them alone.)
 		 */
 		update_attstats(RelationGetRelid(onerel), inh,
-						attr_cnt, vacattrstats);
+						attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
 
 		for (ind = 0; ind < nindexes; ind++)
 		{
 			AnlIndexData *thisdata = &indexdata[ind];
 
 			update_attstats(RelationGetRelid(Irel[ind]), false,
-							thisdata->attr_cnt, thisdata->vacattrstats);
+							thisdata->attr_cnt, thisdata->vacattrstats,
+							RelationGetRelPersistence(Irel[ind]));
 		}
 
 		/*
@@ -1456,7 +1465,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
  *		by taking a self-exclusive lock on the relation in analyze_rel().
  */
 static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
 {
 	Relation	sd;
 	int			attno;
@@ -1558,31 +1567,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/* Update column statistic to localhash, not catalog */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 11ce1bb..552fd87 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -391,6 +391,12 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/* not support cluster global temp table yet */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("not support cluster global temporary tables yet")));
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index fbde9f8..7974ea4 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -27,6 +27,7 @@
 #include "catalog/dependency.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -1063,7 +1064,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, stmt->filename, stmt->is_program,
@@ -2841,6 +2842,8 @@ CopyFrom(CopyState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	estate->es_result_relations = resultRelInfo;
 	estate->es_num_result_relations = 1;
 	estate->es_result_relation_info = resultRelInfo;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 6d696dd..e2e96fe 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2343,7 +2343,9 @@ ReindexIndex(RangeVar *indexRelation, int options, bool concurrent)
 	persistence = irel->rd_rel->relpersistence;
 	index_close(irel, NoLock);
 
-	if (concurrent && persistence != RELPERSISTENCE_TEMP)
+	if (concurrent &&
+		persistence != RELPERSISTENCE_TEMP &&
+		persistence != RELPERSISTENCE_GLOBAL_TEMP)
 		ReindexRelationConcurrently(indOid, options);
 	else
 		reindex_index(indOid, false, persistence,
@@ -2586,6 +2588,16 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 			!isTempNamespace(classtuple->relnamespace))
 			continue;
 
+		/* not support reindex on global temp table, so skip it */
+		if (classtuple->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			ereport(WARNING,
+				(errmsg("global temp table \"%s.%s\" skip reindexed",
+					get_namespace_name(get_rel_namespace(relid)),
+					get_rel_name(relid))));
+			continue;
+		}
+
 		/* Check user/system classification, and optionally skip */
 		if (objectKind == REINDEX_OBJECT_SYSTEM &&
 			!IsSystemClass(relid, classtuple))
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index d8cafc4..deecde8 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -96,7 +96,8 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 	 * transaction.
 	 */
 	relpersistence = get_rel_persistence(relid);
-	if (relpersistence == RELPERSISTENCE_TEMP)
+	if (relpersistence == RELPERSISTENCE_TEMP ||
+		relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
 
 	/* Check permissions. */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..d7119b7 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -94,7 +96,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -222,7 +225,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* now initialize the sequence's data */
 	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -327,7 +330,7 @@ ResetSequence(Oid seq_relid)
 	/*
 	 * Insert the modified tuple into the new storage file.
 	 */
-	fill_seq_with_data(seq_rel, tuple);
+	fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -340,17 +343,21 @@ ResetSequence(Oid seq_relid)
  * Initialize a sequence's relation with the specified tuple as content
  */
 static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 	sequence_magic *sm;
 	OffsetNumber offnum;
+	bool lockBuffer = false;
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ReadBuffer(rel, P_NEW);
-	Assert(BufferGetBlockNumber(buf) == 0);
+	if (buf == InvalidBuffer)
+	{
+		buf = ReadBuffer(rel, P_NEW);
+		Assert(BufferGetBlockNumber(buf) == 0);
+		lockBuffer = true;
+	}
 
 	page = BufferGetPage(buf);
 
@@ -360,7 +367,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* Now insert sequence tuple */
 
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+	if (lockBuffer)
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +418,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	END_CRIT_SECTION();
 
-	UnlockReleaseBuffer(buf);
+	if (lockBuffer)
+		UnlockReleaseBuffer(buf);
 }
 
 /*
@@ -502,7 +511,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * Insert the modified tuple into the new storage file.
 		 */
-		fill_seq_with_data(seqrel, newdatatuple);
+		fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
 	}
 
 	/* process OWNED BY if given */
@@ -611,7 +620,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +945,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1154,6 +1163,13 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1954,3 +1970,46 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+void
+gtt_init_seq(Relation rel)
+{
+	/* Initialize sequence for global temporary tables */
+	Datum		value[SEQ_COL_LASTCOL] = {0};
+	bool		null[SEQ_COL_LASTCOL] = {false};
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8c33b67..4715e91 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -554,6 +555,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 								  Relation partitionTbl);
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static OnCommitAction gtt_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -599,6 +601,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -609,8 +612,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")));
@@ -640,7 +645,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	/* global temp table same as local temp table */
+	if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+		 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -741,6 +748,55 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* global temp table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* check parent table*/
+		if (inheritOids)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			table_close(relation, NoLock);
+		}
+
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "can not defeine global temp table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temp table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1817,6 +1873,10 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
 		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 			continue;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			continue;
+
 		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
@@ -3568,6 +3628,14 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			elog(ERROR, "can not alter table %s when other backend attached this global temp table",
+						RelationGetRelationName(rel));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -8180,6 +8248,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -12687,6 +12761,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -12889,6 +12966,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temp table");
+
 	/*
 	 * No work if no change in tablespace.
 	 */
@@ -13261,7 +13341,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,
@@ -14668,7 +14748,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",
@@ -17260,3 +17342,28 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
 		table_close(rel, NoLock);
 	}
 }
+
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			if (defGetBoolean(def))
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index d625d17..d780a1c 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/vacuum.h"
@@ -1217,6 +1218,17 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	 /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		is_gtt = true;
+		up_gtt_relstats(relation,
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1230,17 +1242,26 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relpages = (int32) num_pages;
+	else if (pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (is_gtt)
+		relation->rd_rel->reltuples = (float4) num_tuples;
+	else if (pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	else if (pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1285,7 +1306,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1296,7 +1318,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1394,6 +1417,10 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/* global temp table relstats not in pg_class */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1451,6 +1478,25 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/*
+	 * Global temp table get frozenxid from MyProc
+	 * to avoid the vacuum truncate clog that gtt need.
+	 */
+	if (max_active_gtt > 0)
+	{
+		TransactionId oldest_gtt_frozenxid =
+			list_all_session_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid) &&
+			TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+		{
+			ereport(WARNING,
+				(errmsg("global temp table oldest FrozenXid is far in the past"),
+				 errhint("please truncate them or kill those sessions that use them.")));
+			newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1778,6 +1824,18 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		ereport(WARNING,
+				(errmsg("skipping vacuum global temp table \"%s\" because storage is not initialized for current session",
+						RelationGetRelationName(onerel))));
+		relation_close(onerel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6e65103..04706ee 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -530,6 +530,11 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 28130fb..dcb4a0b 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index ef74ad8..b1cd1c0 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
@@ -544,6 +545,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	init_gtt_storage(mtstate->operation, leaf_part_rri);
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d71c0a4..0900907 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,6 +42,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -2372,6 +2373,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			ExecOpenIndices(resultRelInfo,
 							node->onConflictAction != ONCONFLICT_NONE);
 
+		init_gtt_storage(operation, resultRelInfo);
+
 		/*
 		 * If this is an UPDATE and a BEFORE UPDATE trigger is present, the
 		 * trigger itself might modify the partition-key values. So arrange
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 905bbe7..170963d 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -591,6 +591,8 @@ static void
 set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 						  RangeTblEntry *rte)
 {
+	char relpersistence;
+
 	/*
 	 * The flag has previously been initialized to false, so we can just
 	 * return if it becomes clear that we can't safely set it.
@@ -618,7 +620,11 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			/* global temp table is same as local temp table */
+			relpersistence = get_rel_persistence(rte->relid);
+
+			if (relpersistence == RELPERSISTENCE_TEMP ||
+				relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b44efd6..14dbaaf 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6307,7 +6307,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
+	/* global temp table is same as local temp table */
 	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index d82fc5a..c8f06eb 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -28,6 +28,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temp table */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6676412..0685c1c 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2585,6 +2585,11 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("materialized views must not use temporary tables or views")));
 
+		if (is_query_using_gtt(query))
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("materialized views must not use global temporary tables or views")));
+
 		/*
 		 * A materialized view would either need to save parameters for use in
 		 * maintaining/loading the data or prohibit them entirely.  The latter
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7e384f9..6ac4aa0 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3291,17 +3291,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; }
@@ -11613,19 +11607,13 @@ OptTempTableName:
 				}
 			| GLOBAL TEMPORARY opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED opt_table qualified_name
 				{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b875a50..123945b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index c191141..fe1c494 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index da75e75..f4b97e8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2087,6 +2087,11 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* autovacuum skip vacuum global temp table */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2153,7 +2158,9 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		/* autovacuum skip vacuum global temp table */
+		if (classForm->relpersistence == RELPERSISTENCE_TEMP || 
+			classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index e05e2b3..b45904c 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -52,6 +53,7 @@
 #include "utils/rel.h"
 #include "utils/resowner_private.h"
 #include "utils/timestamp.h"
+#include "utils/guc.h"
 
 
 /* Note: these two macros only work on shared buffers, not local ones! */
@@ -2756,6 +2758,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d5..7adb969 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -21,6 +21,7 @@
 #include "access/nbtree.h"
 #include "access/subtrans.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, active_gtt_shared_hash_size());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* global temporary table */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index f45a619..cf8b20a 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -63,6 +63,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -4088,3 +4089,77 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temp table.
+ */
+int
+list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct *arrayP = procArray;
+	TransactionId result = InvalidTransactionId;
+	int			index;
+	int			flags = 0;
+	int			i = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+		*n = 0;
+	}
+
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		volatile PGXACT *pgxact = &allPgXact[pgprocno];
+
+		if (pgxact->vacuumFlags & flags)
+			continue;
+
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->session_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->session_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->session_gtt_frozenxid, result))
+				result = proc->session_gtt_frozenxid;
+
+			if (max_size > 0)
+			{
+				pids[i] = proc->pid;
+				xids[i] = proc->session_gtt_frozenxid;
+				i++;
+			}
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (max_size > 0)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index eb321f7..3893cef 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 8406644..0d99321 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -1008,6 +1008,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 8339f4c..d41c05d 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -112,6 +112,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -4571,12 +4572,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))
 							{
@@ -4701,15 +4715,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))
 		{
@@ -6091,6 +6117,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;
@@ -6108,6 +6135,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,
@@ -6119,6 +6153,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;
@@ -6134,6 +6170,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,
@@ -7047,6 +7090,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))
@@ -7059,6 +7104,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 =
@@ -7071,6 +7124,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.
@@ -7090,6 +7145,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 27bbb58..9b5e719 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -2904,6 +2905,18 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index ff70326..5aa5b38 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -63,6 +63,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1127,6 +1128,24 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1181,6 +1200,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			gtt_fix_index_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -1303,7 +1323,17 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+			if (newrelnode != InvalidOid &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2220,6 +2250,8 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		gtt_fix_index_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3316,6 +3348,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3429,22 +3465,28 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	MultiXactId minmulti = InvalidMultiXactId;
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
+	bool		modify_pg_class = !RELATION_IS_GLOBAL_TEMP(relation);
 
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
 
-	/*
-	 * Get a writable copy of the pg_class tuple for the given relation.
-	 */
-	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+	if (modify_pg_class)
+	{
+		/*
+		 * Get a writable copy of the pg_class tuple for the given relation.
+		 */
+		pg_class = table_open(RelationRelationId, RowExclusiveLock);
 
-	tuple = SearchSysCacheCopy1(RELOID,
-								ObjectIdGetDatum(RelationGetRelid(relation)));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "could not find tuple for relation %u",
-			 RelationGetRelid(relation));
-	classform = (Form_pg_class) GETSTRUCT(tuple);
+		tuple = SearchSysCacheCopy1(RELOID,
+									ObjectIdGetDatum(RelationGetRelid(relation)));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u",
+				 RelationGetRelid(relation));
+		classform = (Form_pg_class) GETSTRUCT(tuple);
+	}
+	else
+		memset(&classform, 0, sizeof(classform));
 
 	/*
 	 * Schedule unlinking of the old storage at transaction commit.
@@ -3470,7 +3512,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;
@@ -3490,6 +3532,15 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 			break;
 	}
 
+	if (!modify_pg_class)
+	{
+		Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+		Assert(RELATION_IS_GLOBAL_TEMP(relation));
+		Assert(!RelationIsMapped(relation));
+		relation->rd_node.relNode = relnode;
+		CacheInvalidateRelcache(relation);
+	}
 	/*
 	 * If we're dealing with a mapped index, pg_class.relfilenode doesn't
 	 * change; instead we have to send the update to the relation mapper.
@@ -3499,7 +3550,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	 * possibly-inaccurate values of relpages etc, but those will be fixed up
 	 * later.
 	 */
-	if (RelationIsMapped(relation))
+	else if (RelationIsMapped(relation))
 	{
 		/* This case is only supported for indexes */
 		Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3545,9 +3596,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 		CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 	}
 
-	heap_freetuple(tuple);
+	if (modify_pg_class)
+	{
+		heap_freetuple(tuple);
 
-	table_close(pg_class, RowExclusiveLock);
+		table_close(pg_class, RowExclusiveLock);
+	}
 
 	/*
 	 * Make the pg_class row change or relation map change visible.  This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 6808231..20ef76f 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -141,6 +141,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,
@@ -2048,6 +2060,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 ced0681..aa9c4a2 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15655,6 +15655,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	{
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
+		char		*table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15706,9 +15707,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 			binary_upgrade_set_pg_class_oids(fout, q,
 											 tbinfo->dobj.catId.oid, false);
 
+		if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED)
+			table_type = "UNLOGGED ";
+		else if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			table_type = "GLOBAL TEMPORARY ";
+		else
+			table_type = "";
+
 		appendPQExpBuffer(q, "CREATE %s%s %s",
-						  tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ?
-						  "UNLOGGED " : "",
+						  table_type,
 						  reltypename,
 						  qualrelname);
 
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 5f9a102..3e40297 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -84,7 +84,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -157,7 +157,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 7e524ea..371168f 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -441,22 +443,46 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 	 * output, so we have to copy that system table.  It's easiest to do that
 	 * by treating it as a user table.
 	 */
-	snprintf(query + strlen(query), sizeof(query) - strlen(query),
-			 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
-			 "  SELECT c.oid, 0::oid, 0::oid "
-			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
-			 "         ON c.relnamespace = n.oid "
-			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
-	/* exclude possible orphaned temp tables */
-			 "    ((n.nspname !~ '^pg_temp_' AND "
-			 "      n.nspname !~ '^pg_toast_temp_' AND "
-			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
-			 "                        'binary_upgrade', 'pg_toast') AND "
-			 "      c.oid >= %u::pg_catalog.oid) OR "
-			 "     (n.nspname = 'pg_catalog' AND "
-			 "      relname IN ('pg_largeobject') ))), ",
-			 FirstNormalObjectId);
+	if (skip_gtt)
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude global temp tables */
+				 "    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
+	else
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
 
 	/*
 	 * Add a CTE that collects OIDs of toast tables belonging to the tables
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 70194eb..8dd9e66 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -411,7 +411,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -642,7 +642,9 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -653,7 +655,9 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 8b90cef..d155205 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -389,7 +389,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 109245f..557cce1 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3724,7 +3724,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ae35fa4..4486f89 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1023,6 +1023,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2386,6 +2388,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2594,6 +2599,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 78b33b2..4be89f6 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -168,6 +168,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 7fb574f..8ce1614 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5507,6 +5507,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4191',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4192',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4193',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4194',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 048003c..af48cdf 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -19,7 +19,7 @@
 #include "storage/smgr.h"
 #include "utils/relcache.h"
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationTruncate(Relation rel, BlockNumber nblocks);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..19f24b9
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,44 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(RelFileNode node);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Relation relation,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void gtt_force_enable_index(Relation index);
+extern void gtt_fix_index_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index e2638ab..89a5ce4 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 3f88683..f870e9a 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 8fda8e4..172eae9 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_TBM,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_SXACT,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index d217801..8adde87 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -117,6 +117,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId session_gtt_frozenxid;	/* session level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ce93ace..0f7262e 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -281,6 +281,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 44ed04d..6b6d2da 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	struct SMgrRelationData *rd_smgr;	/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -277,6 +277,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -535,11 +536,13 @@ typedef struct ViewOptions
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -547,6 +550,7 @@ typedef struct ViewOptions
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -559,6 +563,14 @@ typedef struct ViewOptions
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is either local temp relation of this session
+ * 		or global temp relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -602,6 +614,17 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..ca2d135
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,14 @@
+reset search_path;
+select pg_sleep(5);
+ pg_sleep 
+----------
+ 
+(1 row)
+
+drop schema gtt cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
+drop cascades to table gtt.gtt_with_seq
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..603f57a
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,363 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+ n 
+---
+ 9
+(1 row)
+
+commit;
+-- 0 row
+select * from gtt6;
+ n 
+---
+(0 rows)
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+ERROR:  cannot create indexes on global temporary tables using concurrent mode
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+ERROR:  not support cluster global temporary tables yet
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temp table
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  can not defeine global temp table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test;
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR:  insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count 
+-------
+     1
+(1 row)
+
+select count(*) from orders;
+ count 
+-------
+     0
+(1 row)
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  2 |  3
+(2 rows)
+
+commit;
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+select * from gtt_s_2 order by c1;
+ c1 | c2 
+----+----
+  1 |  2
+  2 |  4
+(2 rows)
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 1)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 300)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 23 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.gtt6
+drop cascades to table gtt_function.foo
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to sequence gtt_function.seq_1
+drop cascades to table gtt_function.gtt_s_1
+drop cascades to table gtt_function.gtt_s_2
+drop cascades to table gtt_function.gt1
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..0646aae
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,90 @@
+set search_path=gtt,sys;
+select nextval('gtt_with_seq_c2_seq');
+ nextval 
+---------
+       1
+(1 row)
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..de2373c
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,287 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+rollback;
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+commit;
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          114688 |                 614400
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |            49152 |         49152 |                  49152
+(2 rows)
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+ c1 | c2 
+----+----
+  1 |  1
+(1 row)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..8c0c376
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,10 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..4420fdb
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,80 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+ gtt_stats  | gtt_pkey  |        1 |         0 |             0
+(2 rows)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+reindex table gtt;
+reindex index gtt_pkey;
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+ gtt_stats  | gtt_pkey  |       30 |     10000 |             0
+(2 rows)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index c730461..fa08e6d 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1349,6 +1349,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d2b17dd..80e577f 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..2c8e586
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,8 @@
+
+
+reset search_path;
+
+select pg_sleep(5);
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..af2bb20
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,239 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+commit;
+-- 0 row
+select * from gtt6;
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test;
+
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+commit;
+select * from gtt_s_1 order by c1;
+select * from gtt_s_2 order by c1;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d05745e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,44 @@
+
+
+set search_path=gtt,sys;
+
+select nextval('gtt_with_seq_c2_seq');
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..3ac81a1
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,123 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+rollback;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+commit;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..dbe84d1
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,19 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..d61b0ff
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,46 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+reindex table gtt;
+
+reindex index gtt_pkey;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
#199曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: tushar (#187)
1 attachment(s)

2020年3月13日 下午8:40,tushar <tushar.ahuja@enterprisedb.com> 写道:

On 3/9/20 10:01 PM, 曾文旌(义从) wrote:

Fixed in global_temporary_table_v18-pg13.patch.

Thanks Wenjing.

I am getting this error "ERROR: could not open file "base/13589/t3_16440": No such file or directory" if max_active_global_temporary_table set to 0

Please refer this scenario -

postgres=# create global temp table tab1 (n int ) with ( on_commit_delete_rows='true');
CREATE TABLE
postgres=# insert into tab1 values (1);
INSERT 0 1
postgres=# select * from tab1;
n
---
(0 rows)

postgres=# alter system set max_active_global_temporary_table=0;
ALTER SYSTEM
postgres=# \q
[tushar@localhost bin]$ ./pg_ctl -D data/ restart -c -l logs123

waiting for server to start.... done
server started

[tushar@localhost bin]$ ./psql postgres
psql (13devel)
Type "help" for help.

postgres=# insert into tab1 values (1);
ERROR: could not open file "base/13589/t3_16440": No such file or directory
postgres=#

Thanks for review
It is a bug, I fixed in global_temporary_table_v20-pg13.patch

Wenjing

Attachments:

global_temporary_table_v20-pg13.patchapplication/octet-stream; name=global_temporary_table_v20-pg13.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index ec207d3..eb5d8cf 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -168,6 +168,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * For global temp table only
+	 * use ShareUpdateExclusiveLock for ensure safety
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temp table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},	
 	/* list terminator */
 	{{NULL}}
 };
@@ -1509,6 +1522,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)},
@@ -1609,13 +1624,18 @@ build_reloptions(Datum reloptions, bool validate,
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	/*
 	 * There are no options for partitioned tables yet, but this is able to do
 	 * some validation.
 	 */
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index dd975b1..1610e7d 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1013,7 +1013,9 @@ gistGetFakeLSN(Relation rel)
 {
 	static XLogRecPtr counter = FirstNormalUnloggedLSN;
 
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	/* global temp is same as local temp table */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 4871b7f..16b00c9 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -149,7 +149,9 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	/* global temp table is same as local temp table */
+	if (!(index->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		index->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ca52846..f0153cf 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -598,7 +598,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -651,7 +651,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 03c43ef..2674132 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -62,6 +62,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -399,9 +400,9 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
 	Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
-	/* not every AM requires these to be valid, but heap does */
-	Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
-	Assert(MultiXactIdIsValid(onerel->rd_rel->relminmxid));
+	/* not every AM requires these to be valid, but regular heap does */
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ MultiXactIdIsValid(onerel->rd_rel->relminmxid));
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 39b8f17..abb76dc 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -601,6 +602,10 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index de2d4ee..7e120b5 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6348,6 +6348,10 @@ StartupXLOG(void)
 	else
 		recoveryTargetTLI = ControlFile->checkPointCopy.ThisTimeLineID;
 
+	/* clean temp relation files */
+	if (max_active_gtt > 0)
+		RemovePgTempFiles();
+
 	/*
 	 * Check for signal files, and if so set up state for offline recovery
 	 */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 9499bb3..ae47364 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -43,6 +43,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 7d6acae..3be8d63 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -393,7 +393,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/* global temp table is same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 9d9e915..785a5cd 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -427,7 +429,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -957,6 +959,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -995,8 +998,18 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/* global temp table not remember transaction info in catalog */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1358,6 +1371,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1938,6 +1952,14 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/* We allow to drop global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			elog(ERROR, "can not drop relation %s when other backend attached this global temp table",
+						RelationGetRelationName(rel));
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3165,7 +3187,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3177,7 +3199,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3223,8 +3245,13 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		/* truncate global temp table only need RowExclusiveLock */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3257,6 +3284,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3265,23 +3293,37 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (!gtt_storage_attached(RelationGetRelid(rel)))
+			return;
+
+		/*
+		 * Truncate global temp table only need RowExclusiveLock
+		 */
+		lockmode = RowExclusiveLock;
+	}
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(rel, 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 76fd938..5203ed2 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -52,6 +52,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -877,6 +878,19 @@ index_create(Relation heapRelation,
 						indexRelationName, RelationGetRelationName(heapRelation))));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temp table use concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot create indexes on global temporary tables using concurrent mode")));
+
+		/* if global temp table not init storage, then skip build index */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -2061,6 +2075,14 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/* We allow to drop index on global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+			elog(ERROR, "can not drop index %s when other backend attached this global temp table.",
+						RelationGetRelationName(userHeapRelation));
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2667,6 +2689,11 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	/* update index stats into localhash and rel_rd_rel for global temp table */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		is_gtt = true;
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2752,21 +2779,35 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
+		if (is_gtt)
+			rel->rd_rel->relpages = (int32) relpages;
+		else if (rd_rel->relpages != (int32) relpages)
 		{
 			rd_rel->relpages = (int32) relpages;
 			dirty = true;
 		}
-		if (rd_rel->reltuples != (float4) reltuples)
+
+		if (is_gtt)
+			rel->rd_rel->reltuples = (float4) reltuples;
+		else if (rd_rel->reltuples != (float4) reltuples)
 		{
 			rd_rel->reltuples = (float4) reltuples;
 			dirty = true;
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+
+		if (is_gtt)
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+		else if (rd_rel->relallvisible != (int32) relallvisible)
 		{
 			rd_rel->relallvisible = (int32) relallvisible;
 			dirty = true;
 		}
+
+		if (is_gtt)
+		{
+			up_gtt_relstats(rel, relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
+		}
 	}
 
 	/*
@@ -2880,6 +2921,15 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, index, val);
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			gtt_force_enable_index(indexRelation);
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3428,6 +3478,10 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	PGRUsage	ru0;
 	bool		progress = (options & REINDEXOPT_REPORT_PROGRESS) != 0;
 
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		!gtt_storage_attached(indexId))
+		return;
+
 	pg_rusage_init(&ru0);
 
 	/*
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 5ff7824..9b58f2a 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -647,6 +647,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		/* global temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp relations in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index fddfbf1..c3661e3 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -26,6 +26,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -56,6 +57,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			relOid;			/* InvalidOid if not a global temp rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -75,7 +77,7 @@ static PendingRelDelete *pendingDeletes = NULL; /* head of linked list */
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -85,6 +87,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;
@@ -112,11 +116,19 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->relOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/* remember global temp table storage info to localhash */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel)
+	{
+		pending->relOid = RelationGetRelid(rel);
+		remember_gtt_storage_info(rnode, rel);
+	}
+
 	return srel;
 }
 
@@ -153,11 +165,15 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->relOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->relOid = RelationGetRelid(rel);
+
 	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
@@ -437,6 +453,7 @@ smgrDoPendingDeletes(bool isCommit)
 				i = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -466,14 +483,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->relOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -486,9 +507,18 @@ smgrDoPendingDeletes(bool isCommit)
 		smgrdounlinkall(srels, nrels, false);
 
 		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* clean global temp table flags when transaction commit or rollback */
+			if (SmgrIsTemp(srels[i]) &&
+				reloids[i] != InvalidOid &&
+				gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..42cbeb6
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1440 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  code to create and destroy physical storage for global temparary table
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_relstats_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+typedef struct gtt_ctl_data
+{
+	LWLock			lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+	Oid			relfilenode;
+	Oid			spcnode;
+
+	/* pg_class stat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+
+	/* pg_statistic */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(RelFileNode rnode);
+static void gtt_storage_checkout(RelFileNode rnode, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_reset_statistics(gtt_local_hash_entry *entry);
+static void gtt_free_statistics(gtt_local_hash_entry *entry);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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(gtt_fnode);
+	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;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = rnode.dbNode;
+	fnode.relNode = rnode.relNode;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *)&(fnode), 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_global_temporary_table.")));
+	}
+
+	if (found == false)
+	{
+		int			wordnum;
+
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(RelFileNode rnode, bool skiplock, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode				fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (!skiplock)
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+
+	fnode.dbNode = rnode.dbNode;
+	fnode.relNode = rnode.relNode;
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		if (!skiplock)
+			LWLockRelease(&gtt_shared_ctl->lock);
+
+		if (isCommit)
+		{
+			elog(WARNING, "relfilenode %u/%u/%u not exist in gtt shared hash when forget",
+						rnode.dbNode, rnode.spcNode, rnode.relNode);
+		}
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+	bms_del_member(entry->map, MyBackendId);
+
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, 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;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	fnode.dbNode = node.dbNode;
+	fnode.relNode = node.relNode;
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool		in_use = false;
+	int			num_use = 0;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	fnode.dbNode = MyBackendId;
+	fnode.relNode = relid;
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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;
+	gtt_relfilenode			*new_node = NULL;
+	Oid						relid = RelationGetRelid(rel);
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		/* First time through: initialize the hash table */
+		HASHCTL		ctl;
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_relstats_context =
+			AllocSetContextCreate(CacheMemoryContext,
+							"gtt relstats context",
+							ALLOCSET_DEFAULT_SIZES);
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+	if (!entry)
+	{
+		bool		found = false;
+		int 		natts = 0;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash,
+						(void *) &relid, HASH_ENTER, &found);
+
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+		entry->natts = 0;
+		entry->attnum = NULL;
+		entry->att_stat_tups = NULL;
+
+		natts = RelationGetNumberOfAttributes(rel);
+		entry->attnum = palloc0(sizeof(int) * natts);
+		entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+		entry->natts = natts;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			RelFileNode	gtt_rnode;
+			gtt_rnode.spcNode = InvalidOid;
+			gtt_rnode.dbNode = rnode.dbNode;
+			gtt_rnode.relNode = relid;
+			gtt_storage_checkin(gtt_rnode);
+		}
+	}
+
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* only heap contain transaction information */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	gtt_reset_statistics(entry);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	ListCell		*lc;
+	gtt_relfilenode *d_rnode = NULL;
+	RelFileNode		relnode;
+
+	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)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+		return;
+	}
+
+	relnode.spcNode = InvalidOid;
+	relnode.dbNode = MyDatabaseId;
+	relnode.relNode = entry->relid;
+
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == rnode.relNode &&
+			gtt_rnode->spcnode == rnode.spcNode)
+		{
+			d_rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+
+		if (entry->relfilenode_list == NIL)
+		{
+			if (entry->relkind == RELKIND_RELATION ||
+				entry->relkind == RELKIND_SEQUENCE)
+				gtt_storage_checkout(relnode, false, isCommit);
+
+			gtt_free_statistics(entry);
+			hash_search(gtt_storage_local_hash,
+					(void *) &(relid), HASH_REMOVE, NULL);
+		}
+
+		return;
+	}
+
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	pfree(d_rnode);
+	if (entry->relfilenode_list == NIL)
+	{
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(relnode, false, isCommit);
+
+		gtt_free_statistics(entry);
+		hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_REMOVE, NULL);
+	}
+	else
+		gtt_reset_statistics(entry);
+
+	return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool found;
+
+	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;
+	SMgrRelation	*srels = NULL;
+	RelFileNode		*rnodes = NULL;
+	char			*relkinds = NULL;
+	int			nrels = 0,
+				nfiles = 0,
+				maxrels = 0,
+				maxfiles = 0,
+				i = 0;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel;
+			RelFileNode 	rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel = smgropen(rnode, MyBackendId);
+
+			if (maxfiles == 0)
+			{
+				maxfiles = 32;
+				srels = palloc(sizeof(SMgrRelation) * maxfiles);
+			}
+			else if (maxfiles <= nfiles)
+			{
+				maxfiles *= 2;
+				srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+			}
+
+			srels[nfiles++] = srel;
+		}
+
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			rnodes = palloc(sizeof(RelFileNode) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			rnodes = repalloc(rnodes, sizeof(RelFileNode) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		relkinds[nrels] = entry->relkind;
+		rnodes[nrels].relNode = entry->relid;
+		rnodes[nrels].dbNode = MyDatabaseId;
+		rnodes[nrels].spcNode = InvalidOid;
+		nrels++;
+	}
+
+	if (nfiles > 0)
+	{
+		smgrdounlinkall(srels, nfiles, false);
+		for (i = 0; i < nfiles; i++)
+			smgrclose(srels[i]);
+
+		pfree(srels);
+	}
+
+	if (nrels)
+	{
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+		for (i = 0; i < nrels; i++)
+		{
+			if (relkinds[i] == RELKIND_RELATION ||
+				relkinds[i] == RELKIND_SEQUENCE)
+				gtt_storage_checkout(rnodes[i], true, false);
+		}
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+		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;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_FIND, &found);
+
+	if (!found)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (num_pages >= 0 &&
+		gtt_rnode->relpages != (int32)num_pages)
+		gtt_rnode->relpages = (int32)num_pages;
+
+	if (num_tuples >= 0 &&
+		gtt_rnode->reltuples != (float4)num_tuples)
+		gtt_rnode->reltuples = (float4)num_tuples;
+
+	/* only heap contain transaction information and relallvisible */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (gtt_rnode->relallvisible >= 0 &&
+			gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+		{
+			gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+		}
+
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNewTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible) 
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (gtt_storage_local_hash == NULL)
+		return false;
+
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_FIND, &found);
+
+	if (!found)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	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->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	Assert(entry->relid == reloid);
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == 0)
+		{
+			entry->attnum[i] = attnum;
+			break;
+		}
+		else if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < entry->natts);
+	Assert(entry->att_stat_tups[i] == NULL);
+	entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+						(void *) &reloid, HASH_FIND, &found);
+
+	if (!found)
+		return NULL;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			return entry->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int			i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	HeapTuple	tuple;
+	Relation	rel = NULL;
+	int		attnum = PG_GETARG_INT32(1);
+	Oid		reloid = PG_GETARG_OID(0);
+	char		rel_persistence;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	  tupdesc;
+	MemoryContext oldcontext;
+	Tuplestorestate *tupstore;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+
+	relation_close(rel, NoLock);
+	relation_close(pg_tatistic, AccessShareLock);
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate	*tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Oid			relnode = 0;
+	Relation	rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[6];
+		bool	isnull[6];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(rel->rd_node);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(indexOid));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid relOid = index->rd_index->indrelid;
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	if (gtt_storage_attached(relOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int			i;
+	Oid			toastrelid;
+
+	if (operation != CMD_INSERT)
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+
+		index_build(relation, index, info, true, false);
+	}
+
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid 		indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo 	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+static void
+gtt_reset_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+
+		entry->attnum[i] = 0;
+	}
+
+	return;
+}
+
+static void
+gtt_free_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (entry->attnum)
+		pfree(entry->attnum);
+
+	if (entry->att_stat_tups)
+		pfree(entry->att_stat_tups);
+
+	return;
+}
+
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	if (gtt_storage_local_hash == NULL)
+		return InvalidOid;
+
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_FIND, &found);
+
+	if (!found)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index b8a3f46..4b46d77 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM 
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 924ef37..db12eef 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -184,6 +185,13 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -586,14 +594,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 		 * pg_statistic for columns we didn't process, we leave them alone.)
 		 */
 		update_attstats(RelationGetRelid(onerel), inh,
-						attr_cnt, vacattrstats);
+						attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
 
 		for (ind = 0; ind < nindexes; ind++)
 		{
 			AnlIndexData *thisdata = &indexdata[ind];
 
 			update_attstats(RelationGetRelid(Irel[ind]), false,
-							thisdata->attr_cnt, thisdata->vacattrstats);
+							thisdata->attr_cnt, thisdata->vacattrstats,
+							RelationGetRelPersistence(Irel[ind]));
 		}
 
 		/*
@@ -1456,7 +1465,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
  *		by taking a self-exclusive lock on the relation in analyze_rel().
  */
 static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
 {
 	Relation	sd;
 	int			attno;
@@ -1558,31 +1567,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/* Update column statistic to localhash, not catalog */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 11ce1bb..552fd87 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -391,6 +391,12 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/* not support cluster global temp table yet */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("not support cluster global temporary tables yet")));
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index fbde9f8..7974ea4 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -27,6 +27,7 @@
 #include "catalog/dependency.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -1063,7 +1064,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, stmt->filename, stmt->is_program,
@@ -2841,6 +2842,8 @@ CopyFrom(CopyState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	estate->es_result_relations = resultRelInfo;
 	estate->es_num_result_relations = 1;
 	estate->es_result_relation_info = resultRelInfo;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 6d696dd..e2e96fe 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2343,7 +2343,9 @@ ReindexIndex(RangeVar *indexRelation, int options, bool concurrent)
 	persistence = irel->rd_rel->relpersistence;
 	index_close(irel, NoLock);
 
-	if (concurrent && persistence != RELPERSISTENCE_TEMP)
+	if (concurrent &&
+		persistence != RELPERSISTENCE_TEMP &&
+		persistence != RELPERSISTENCE_GLOBAL_TEMP)
 		ReindexRelationConcurrently(indOid, options);
 	else
 		reindex_index(indOid, false, persistence,
@@ -2586,6 +2588,16 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 			!isTempNamespace(classtuple->relnamespace))
 			continue;
 
+		/* not support reindex on global temp table, so skip it */
+		if (classtuple->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			ereport(WARNING,
+				(errmsg("global temp table \"%s.%s\" skip reindexed",
+					get_namespace_name(get_rel_namespace(relid)),
+					get_rel_name(relid))));
+			continue;
+		}
+
 		/* Check user/system classification, and optionally skip */
 		if (objectKind == REINDEX_OBJECT_SYSTEM &&
 			!IsSystemClass(relid, classtuple))
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index d8cafc4..deecde8 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -96,7 +96,8 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 	 * transaction.
 	 */
 	relpersistence = get_rel_persistence(relid);
-	if (relpersistence == RELPERSISTENCE_TEMP)
+	if (relpersistence == RELPERSISTENCE_TEMP ||
+		relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
 
 	/* Check permissions. */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..d7119b7 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -94,7 +96,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -222,7 +225,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* now initialize the sequence's data */
 	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -327,7 +330,7 @@ ResetSequence(Oid seq_relid)
 	/*
 	 * Insert the modified tuple into the new storage file.
 	 */
-	fill_seq_with_data(seq_rel, tuple);
+	fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -340,17 +343,21 @@ ResetSequence(Oid seq_relid)
  * Initialize a sequence's relation with the specified tuple as content
  */
 static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 	sequence_magic *sm;
 	OffsetNumber offnum;
+	bool lockBuffer = false;
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ReadBuffer(rel, P_NEW);
-	Assert(BufferGetBlockNumber(buf) == 0);
+	if (buf == InvalidBuffer)
+	{
+		buf = ReadBuffer(rel, P_NEW);
+		Assert(BufferGetBlockNumber(buf) == 0);
+		lockBuffer = true;
+	}
 
 	page = BufferGetPage(buf);
 
@@ -360,7 +367,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* Now insert sequence tuple */
 
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+	if (lockBuffer)
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +418,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	END_CRIT_SECTION();
 
-	UnlockReleaseBuffer(buf);
+	if (lockBuffer)
+		UnlockReleaseBuffer(buf);
 }
 
 /*
@@ -502,7 +511,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * Insert the modified tuple into the new storage file.
 		 */
-		fill_seq_with_data(seqrel, newdatatuple);
+		fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
 	}
 
 	/* process OWNED BY if given */
@@ -611,7 +620,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +945,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1154,6 +1163,13 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1954,3 +1970,46 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+void
+gtt_init_seq(Relation rel)
+{
+	/* Initialize sequence for global temporary tables */
+	Datum		value[SEQ_COL_LASTCOL] = {0};
+	bool		null[SEQ_COL_LASTCOL] = {false};
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8c33b67..4715e91 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -554,6 +555,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 								  Relation partitionTbl);
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static OnCommitAction gtt_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -599,6 +601,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -609,8 +612,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")));
@@ -640,7 +645,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	/* global temp table same as local temp table */
+	if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+		 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -741,6 +748,55 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* global temp table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* check parent table*/
+		if (inheritOids)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			table_close(relation, NoLock);
+		}
+
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "can not defeine global temp table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temp table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1817,6 +1873,10 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
 		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 			continue;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			continue;
+
 		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
@@ -3568,6 +3628,14 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			elog(ERROR, "can not alter table %s when other backend attached this global temp table",
+						RelationGetRelationName(rel));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -8180,6 +8248,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -12687,6 +12761,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -12889,6 +12966,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temp table");
+
 	/*
 	 * No work if no change in tablespace.
 	 */
@@ -13261,7 +13341,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,
@@ -14668,7 +14748,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",
@@ -17260,3 +17342,28 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
 		table_close(rel, NoLock);
 	}
 }
+
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			if (defGetBoolean(def))
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index d625d17..d780a1c 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/vacuum.h"
@@ -1217,6 +1218,17 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	 /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		is_gtt = true;
+		up_gtt_relstats(relation,
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1230,17 +1242,26 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relpages = (int32) num_pages;
+	else if (pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (is_gtt)
+		relation->rd_rel->reltuples = (float4) num_tuples;
+	else if (pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	else if (pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1285,7 +1306,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1296,7 +1318,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1394,6 +1417,10 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/* global temp table relstats not in pg_class */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1451,6 +1478,25 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/*
+	 * Global temp table get frozenxid from MyProc
+	 * to avoid the vacuum truncate clog that gtt need.
+	 */
+	if (max_active_gtt > 0)
+	{
+		TransactionId oldest_gtt_frozenxid =
+			list_all_session_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid) &&
+			TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+		{
+			ereport(WARNING,
+				(errmsg("global temp table oldest FrozenXid is far in the past"),
+				 errhint("please truncate them or kill those sessions that use them.")));
+			newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1778,6 +1824,18 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		ereport(WARNING,
+				(errmsg("skipping vacuum global temp table \"%s\" because storage is not initialized for current session",
+						RelationGetRelationName(onerel))));
+		relation_close(onerel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6e65103..04706ee 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -530,6 +530,11 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 28130fb..dcb4a0b 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index ef74ad8..b1cd1c0 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
@@ -544,6 +545,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	init_gtt_storage(mtstate->operation, leaf_part_rri);
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d71c0a4..0900907 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,6 +42,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -2372,6 +2373,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			ExecOpenIndices(resultRelInfo,
 							node->onConflictAction != ONCONFLICT_NONE);
 
+		init_gtt_storage(operation, resultRelInfo);
+
 		/*
 		 * If this is an UPDATE and a BEFORE UPDATE trigger is present, the
 		 * trigger itself might modify the partition-key values. So arrange
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 905bbe7..170963d 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -591,6 +591,8 @@ static void
 set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 						  RangeTblEntry *rte)
 {
+	char relpersistence;
+
 	/*
 	 * The flag has previously been initialized to false, so we can just
 	 * return if it becomes clear that we can't safely set it.
@@ -618,7 +620,11 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			/* global temp table is same as local temp table */
+			relpersistence = get_rel_persistence(rte->relid);
+
+			if (relpersistence == RELPERSISTENCE_TEMP ||
+				relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b44efd6..14dbaaf 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6307,7 +6307,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
+	/* global temp table is same as local temp table */
 	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index d82fc5a..c8f06eb 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -28,6 +28,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temp table */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6676412..0685c1c 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2585,6 +2585,11 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("materialized views must not use temporary tables or views")));
 
+		if (is_query_using_gtt(query))
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("materialized views must not use global temporary tables or views")));
+
 		/*
 		 * A materialized view would either need to save parameters for use in
 		 * maintaining/loading the data or prohibit them entirely.  The latter
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7e384f9..6ac4aa0 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3291,17 +3291,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; }
@@ -11613,19 +11607,13 @@ OptTempTableName:
 				}
 			| GLOBAL TEMPORARY opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED opt_table qualified_name
 				{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b875a50..123945b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index c191141..fe1c494 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index da75e75..f4b97e8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2087,6 +2087,11 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* autovacuum skip vacuum global temp table */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2153,7 +2158,9 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		/* autovacuum skip vacuum global temp table */
+		if (classForm->relpersistence == RELPERSISTENCE_TEMP || 
+			classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index e05e2b3..b45904c 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -52,6 +53,7 @@
 #include "utils/rel.h"
 #include "utils/resowner_private.h"
 #include "utils/timestamp.h"
+#include "utils/guc.h"
 
 
 /* Note: these two macros only work on shared buffers, not local ones! */
@@ -2756,6 +2758,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d5..7adb969 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -21,6 +21,7 @@
 #include "access/nbtree.h"
 #include "access/subtrans.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, active_gtt_shared_hash_size());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* global temporary table */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index f45a619..cf8b20a 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -63,6 +63,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -4088,3 +4089,77 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temp table.
+ */
+int
+list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct *arrayP = procArray;
+	TransactionId result = InvalidTransactionId;
+	int			index;
+	int			flags = 0;
+	int			i = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+		*n = 0;
+	}
+
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		volatile PGXACT *pgxact = &allPgXact[pgprocno];
+
+		if (pgxact->vacuumFlags & flags)
+			continue;
+
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->session_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->session_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->session_gtt_frozenxid, result))
+				result = proc->session_gtt_frozenxid;
+
+			if (max_size > 0)
+			{
+				pids[i] = proc->pid;
+				xids[i] = proc->session_gtt_frozenxid;
+				i++;
+			}
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (max_size > 0)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index eb321f7..3893cef 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 8406644..0d99321 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -1008,6 +1008,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 8339f4c..d41c05d 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -112,6 +112,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -4571,12 +4572,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))
 							{
@@ -4701,15 +4715,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))
 		{
@@ -6091,6 +6117,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;
@@ -6108,6 +6135,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,
@@ -6119,6 +6153,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;
@@ -6134,6 +6170,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,
@@ -7047,6 +7090,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))
@@ -7059,6 +7104,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 =
@@ -7071,6 +7124,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.
@@ -7090,6 +7145,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 27bbb58..9b5e719 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -2904,6 +2905,18 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index ff70326..5aa5b38 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -63,6 +63,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1127,6 +1128,24 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1181,6 +1200,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			gtt_fix_index_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -1303,7 +1323,17 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+			if (newrelnode != InvalidOid &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2220,6 +2250,8 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		gtt_fix_index_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3316,6 +3348,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3429,22 +3465,28 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	MultiXactId minmulti = InvalidMultiXactId;
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
+	bool		modify_pg_class = !RELATION_IS_GLOBAL_TEMP(relation);
 
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
 
-	/*
-	 * Get a writable copy of the pg_class tuple for the given relation.
-	 */
-	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+	if (modify_pg_class)
+	{
+		/*
+		 * Get a writable copy of the pg_class tuple for the given relation.
+		 */
+		pg_class = table_open(RelationRelationId, RowExclusiveLock);
 
-	tuple = SearchSysCacheCopy1(RELOID,
-								ObjectIdGetDatum(RelationGetRelid(relation)));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "could not find tuple for relation %u",
-			 RelationGetRelid(relation));
-	classform = (Form_pg_class) GETSTRUCT(tuple);
+		tuple = SearchSysCacheCopy1(RELOID,
+									ObjectIdGetDatum(RelationGetRelid(relation)));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u",
+				 RelationGetRelid(relation));
+		classform = (Form_pg_class) GETSTRUCT(tuple);
+	}
+	else
+		memset(&classform, 0, sizeof(classform));
 
 	/*
 	 * Schedule unlinking of the old storage at transaction commit.
@@ -3470,7 +3512,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;
@@ -3490,6 +3532,15 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 			break;
 	}
 
+	if (!modify_pg_class)
+	{
+		Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+		Assert(RELATION_IS_GLOBAL_TEMP(relation));
+		Assert(!RelationIsMapped(relation));
+		relation->rd_node.relNode = relnode;
+		CacheInvalidateRelcache(relation);
+	}
 	/*
 	 * If we're dealing with a mapped index, pg_class.relfilenode doesn't
 	 * change; instead we have to send the update to the relation mapper.
@@ -3499,7 +3550,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	 * possibly-inaccurate values of relpages etc, but those will be fixed up
 	 * later.
 	 */
-	if (RelationIsMapped(relation))
+	else if (RelationIsMapped(relation))
 	{
 		/* This case is only supported for indexes */
 		Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3545,9 +3596,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 		CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 	}
 
-	heap_freetuple(tuple);
+	if (modify_pg_class)
+	{
+		heap_freetuple(tuple);
 
-	table_close(pg_class, RowExclusiveLock);
+		table_close(pg_class, RowExclusiveLock);
+	}
 
 	/*
 	 * Make the pg_class row change or relation map change visible.  This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 6808231..20ef76f 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -141,6 +141,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,
@@ -2048,6 +2060,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 ced0681..aa9c4a2 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15655,6 +15655,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	{
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
+		char		*table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15706,9 +15707,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 			binary_upgrade_set_pg_class_oids(fout, q,
 											 tbinfo->dobj.catId.oid, false);
 
+		if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED)
+			table_type = "UNLOGGED ";
+		else if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			table_type = "GLOBAL TEMPORARY ";
+		else
+			table_type = "";
+
 		appendPQExpBuffer(q, "CREATE %s%s %s",
-						  tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ?
-						  "UNLOGGED " : "",
+						  table_type,
 						  reltypename,
 						  qualrelname);
 
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 5f9a102..3e40297 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -84,7 +84,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -157,7 +157,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 7e524ea..371168f 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -441,22 +443,46 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 	 * output, so we have to copy that system table.  It's easiest to do that
 	 * by treating it as a user table.
 	 */
-	snprintf(query + strlen(query), sizeof(query) - strlen(query),
-			 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
-			 "  SELECT c.oid, 0::oid, 0::oid "
-			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
-			 "         ON c.relnamespace = n.oid "
-			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
-	/* exclude possible orphaned temp tables */
-			 "    ((n.nspname !~ '^pg_temp_' AND "
-			 "      n.nspname !~ '^pg_toast_temp_' AND "
-			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
-			 "                        'binary_upgrade', 'pg_toast') AND "
-			 "      c.oid >= %u::pg_catalog.oid) OR "
-			 "     (n.nspname = 'pg_catalog' AND "
-			 "      relname IN ('pg_largeobject') ))), ",
-			 FirstNormalObjectId);
+	if (skip_gtt)
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude global temp tables */
+				 "    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
+	else
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
 
 	/*
 	 * Add a CTE that collects OIDs of toast tables belonging to the tables
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 70194eb..8dd9e66 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -411,7 +411,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -642,7 +642,9 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -653,7 +655,9 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 8b90cef..d155205 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -389,7 +389,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 109245f..557cce1 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3724,7 +3724,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ae35fa4..4486f89 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1023,6 +1023,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2386,6 +2388,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2594,6 +2599,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 78b33b2..4be89f6 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -168,6 +168,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 7fb574f..8ce1614 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5507,6 +5507,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4191',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4192',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4193',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4194',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 048003c..af48cdf 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -19,7 +19,7 @@
 #include "storage/smgr.h"
 #include "utils/relcache.h"
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationTruncate(Relation rel, BlockNumber nblocks);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..19f24b9
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,44 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(RelFileNode node);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Relation relation,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void gtt_force_enable_index(Relation index);
+extern void gtt_fix_index_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index e2638ab..89a5ce4 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 3f88683..f870e9a 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 8fda8e4..172eae9 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_TBM,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_SXACT,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index d217801..8adde87 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -117,6 +117,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId session_gtt_frozenxid;	/* session level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ce93ace..0f7262e 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -281,6 +281,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 44ed04d..6b6d2da 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	struct SMgrRelationData *rd_smgr;	/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -277,6 +277,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -535,11 +536,13 @@ typedef struct ViewOptions
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -547,6 +550,7 @@ typedef struct ViewOptions
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -559,6 +563,14 @@ typedef struct ViewOptions
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is either local temp relation of this session
+ * 		or global temp relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -602,6 +614,17 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..ca2d135
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,14 @@
+reset search_path;
+select pg_sleep(5);
+ pg_sleep 
+----------
+ 
+(1 row)
+
+drop schema gtt cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
+drop cascades to table gtt.gtt_with_seq
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..603f57a
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,363 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+ n 
+---
+ 9
+(1 row)
+
+commit;
+-- 0 row
+select * from gtt6;
+ n 
+---
+(0 rows)
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+ERROR:  cannot create indexes on global temporary tables using concurrent mode
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+ERROR:  not support cluster global temporary tables yet
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temp table
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  can not defeine global temp table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test;
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR:  insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count 
+-------
+     1
+(1 row)
+
+select count(*) from orders;
+ count 
+-------
+     0
+(1 row)
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  2 |  3
+(2 rows)
+
+commit;
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+select * from gtt_s_2 order by c1;
+ c1 | c2 
+----+----
+  1 |  2
+  2 |  4
+(2 rows)
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 1)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 300)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 23 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.gtt6
+drop cascades to table gtt_function.foo
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to sequence gtt_function.seq_1
+drop cascades to table gtt_function.gtt_s_1
+drop cascades to table gtt_function.gtt_s_2
+drop cascades to table gtt_function.gt1
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..0646aae
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,90 @@
+set search_path=gtt,sys;
+select nextval('gtt_with_seq_c2_seq');
+ nextval 
+---------
+       1
+(1 row)
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..de2373c
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,287 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+rollback;
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+commit;
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          114688 |                 614400
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |            49152 |         49152 |                  49152
+(2 rows)
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+ c1 | c2 
+----+----
+  1 |  1
+(1 row)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..8c0c376
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,10 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..4420fdb
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,80 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+ gtt_stats  | gtt_pkey  |        1 |         0 |             0
+(2 rows)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+reindex table gtt;
+reindex index gtt_pkey;
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+ gtt_stats  | gtt_pkey  |       30 |     10000 |             0
+(2 rows)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index c730461..fa08e6d 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1349,6 +1349,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d2b17dd..80e577f 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..2c8e586
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,8 @@
+
+
+reset search_path;
+
+select pg_sleep(5);
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..af2bb20
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,239 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+commit;
+-- 0 row
+select * from gtt6;
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test;
+
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+commit;
+select * from gtt_s_1 order by c1;
+select * from gtt_s_2 order by c1;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d05745e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,44 @@
+
+
+set search_path=gtt,sys;
+
+select nextval('gtt_with_seq_c2_seq');
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..3ac81a1
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,123 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+rollback;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+commit;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..dbe84d1
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,19 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..d61b0ff
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,46 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+reindex table gtt;
+
+reindex index gtt_pkey;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
#200曾文旌(义从)
wenjing.zwj@alibaba-inc.com
In reply to: Prabhat Sahu (#182)
Re: [Proposal] Global temporary tables

2020年3月11日 下午3:52,Prabhat Sahu <prabhat.sahu@enterprisedb.com> 写道:

On Mon, Mar 9, 2020 at 10:02 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> wrote:

Fixed in global_temporary_table_v18-pg13.patch.
Hi Wenjing,
Thanks for the patch. I have verified the previous issues with "gtt_v18_pg13.patch" and those are resolved.
Please find below case:

postgres=# create sequence seq;
CREATE SEQUENCE

postgres=# CREATE GLOBAL TEMPORARY TABLE gtt1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
CREATE TABLE

postgres=# CREATE GLOBAL TEMPORARY TABLE gtt2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
CREATE TABLE

postgres=# alter table gtt1 add c2 int default nextval('seq');
ERROR: cannot reindex global temporary tables

postgres=# alter table gtt2 add c2 int default nextval('seq');
ERROR: cannot reindex global temporary tables

reindex GTT is already supported

Please check global_temporary_table_v20-pg13.patch

Wenjing

Show quoted text

Note: We are getting this error if we have a key column(PK/UNIQUE) in a GTT, and trying to add a column with a default sequence into it.

--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

#201wenjing.zwj
wenjing.zwj@alibaba-inc.com
In reply to: Prabhat Sahu (#188)
1 attachment(s)
Re: [Proposal] Global temporary tables

postgres=# CREATE LOCAL TEMPORARY TABLE gtt1(c1 serial PRIMARY KEY, c2 VARCHAR (50) UNIQUE NOT NULL) ON COMMIT DELETE ROWS;
CREATE TABLE
postgres=# CREATE LOCAL TEMPORARY TABLE gtt2(c1 integer NOT NULL, c2 integer NOT NULL,
postgres(# PRIMARY KEY (c1, c2),
postgres(# FOREIGN KEY (c1) REFERENCES gtt1 (c1)) ON COMMIT PRESERVE ROWS;
ERROR: unsupported ON COMMIT and foreign key combination
DETAIL: Table "gtt2" references "gtt1", but they do not have the same ON COMMIT setting.

postgres=# CREATE LOCAL TEMPORARY TABLE gtt3(c1 serial PRIMARY KEY, c2 VARCHAR (50) UNIQUE NOT NULL) ON COMMIT PRESERVE ROWS;
CREATE TABLE
postgres=#
postgres=# CREATE LOCAL TEMPORARY TABLE gtt4(c1 integer NOT NULL, c2 integer NOT NULL,
postgres(# PRIMARY KEY (c1, c2),
postgres(# FOREIGN KEY (c1) REFERENCES gtt3 (c1)) ON COMMIT DELETE ROWS;
CREATE TABLE

The same behavior applies to the local temp table.
I think, Cause of the problem is temp table with on commit delete rows is not good for reference tables.
So, it the error message ”cannot reference an on commit delete rows temporary table.“ ?

Show quoted text

2020年3月13日 下午10:16,Prabhat Sahu <prabhat.sahu@enterprisedb.com> 写道:

Hi Wenjing,

Please check the below combination of GTT with Primary and Foreign key relations, with the ERROR message.

Case1:
postgres=# CREATE GLOBAL TEMPORARY TABLE gtt1(c1 serial PRIMARY KEY, c2 VARCHAR (50) UNIQUE NOT NULL) ON COMMIT DELETE ROWS;
CREATE TABLE

postgres=# CREATE GLOBAL TEMPORARY TABLE gtt2(c1 integer NOT NULL, c2 integer NOT NULL,
PRIMARY KEY (c1, c2),
FOREIGN KEY (c1) REFERENCES gtt1 (c1)) ON COMMIT PRESERVE ROWS;
ERROR: unsupported ON COMMIT and foreign key combination
DETAIL: Table "gtt2" references "gtt1", but they do not have the same ON COMMIT setting.

Case2:
postgres=# CREATE GLOBAL TEMPORARY TABLE gtt1(c1 serial PRIMARY KEY, c2 VARCHAR (50) UNIQUE NOT NULL) ON COMMIT PRESERVE ROWS;
CREATE TABLE

postgres=# CREATE GLOBAL TEMPORARY TABLE gtt2(c1 integer NOT NULL, c2 integer NOT NULL,
PRIMARY KEY (c1, c2),
FOREIGN KEY (c1) REFERENCES gtt1 (c1)) ON COMMIT DELETE ROWS;
CREATE TABLE

In "case2" although both the primary table and foreign key GTT do not have the same ON COMMIT setting, still we are able to create the PK-FK relations with GTT.

So I hope the detail message(DETAIL: Table "gtt2" references "gtt1", but they do not have the same ON COMMIT setting.) in "Case1" should be more clear(something like "wrong combination of ON COMMIT setting").

--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

Attachments:

smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#202Prabhat Sahu
prabhat.sahu@enterprisedb.com
In reply to: wenjing.zwj (#201)
Re: [Proposal] Global temporary tables

On Thu, Mar 19, 2020 at 3:51 PM wenjing.zwj <wenjing.zwj@alibaba-inc.com>
wrote:

postgres=# CREATE LOCAL TEMPORARY TABLE gtt1(c1 serial PRIMARY KEY, c2
VARCHAR (50) UNIQUE NOT NULL) ON COMMIT DELETE ROWS;
CREATE TABLE
postgres=# CREATE LOCAL TEMPORARY TABLE gtt2(c1 integer NOT NULL, c2
integer NOT NULL,
postgres(# PRIMARY KEY (c1, c2),
postgres(# FOREIGN KEY (c1) REFERENCES gtt1 (c1)) ON COMMIT PRESERVE ROWS;
ERROR: unsupported ON COMMIT and foreign key combination
DETAIL: Table "gtt2" references "gtt1", but they do not have the same ON
COMMIT setting.

postgres=# CREATE LOCAL TEMPORARY TABLE gtt3(c1 serial PRIMARY KEY, c2
VARCHAR (50) UNIQUE NOT NULL) ON COMMIT PRESERVE ROWS;
CREATE TABLE
postgres=#
postgres=# CREATE LOCAL TEMPORARY TABLE gtt4(c1 integer NOT NULL, c2
integer NOT NULL,
postgres(# PRIMARY KEY (c1, c2),
postgres(# FOREIGN KEY (c1) REFERENCES gtt3 (c1)) ON COMMIT DELETE ROWS;
CREATE TABLE

The same behavior applies to the local temp table.

Yes, the issue is related to "local temp table".

I think, Cause of the problem is temp table with on commit delete rows is

not good for reference tables.
So, it the error message ”cannot reference an on commit delete rows
temporary table.“ ?

No, this is not always true.
We can create GTT/"local temp table" with "ON COMMIT DELETE ROWS" which
can references to "ON COMMIT DELETE ROWS"

Below are the 4 combinations of GTT/"local temp table" reference.
1. "ON COMMIT PRESERVE ROWS" can references to "ON COMMIT PRESERVE ROWS"
2. "ON COMMIT DELETE ROWS" can references to "ON COMMIT PRESERVE ROWS"
3. "ON COMMIT DELETE ROWS" can references to "ON COMMIT DELETE ROWS"
But
4. "ON COMMIT PRESERVE ROWS" fails to reference "ON COMMIT DELETE ROWS"

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

#203Prabhat Sahu
prabhat.sahu@enterprisedb.com
In reply to: 曾文旌(义从) (#200)
Re: [Proposal] Global temporary tables

Hi Wenjing,
Please check my findings(on gtt_v20.patch) as below:

*TestCase1:* (cache lookup failed on GTT)

-- Session1:
postgres=# create global temporary table gtt1(c1 int) on commit delete rows;
CREATE TABLE

-- Session2:
postgres=# drop table gtt1 ;
DROP TABLE

-- Session1:
postgres=# create global temporary table gtt1(c1 int) on commit delete rows;
ERROR: cache lookup failed for relation 16384

*TestCase2:*

-- Session1:
postgres=# create global temporary table gtt (c1 integer) on commit
preserve rows;
CREATE TABLE
postgres=# insert into gtt values(10);
INSERT 0 1

-- Session2:
postgres=# drop table gtt;
DROP TABLE

I hope "session2" should not allow to perform the "DROP" operation on GTT
having data.

*Behavior of GTT in Oracle Database in such scenario:* For a completed
transaction on GTT with(on_commit_delete_rows='FALSE') with data in a
session, we will not be able to DROP from any session, we need to TRUNCATE
the data first to DROP the table.

SQL> drop table gtt;
drop table gtt
*
ERROR at line 1:
ORA-14452: attempt to create, alter or drop an index on temporary table
already
in use

On Tue, Mar 17, 2020 at 9:16 AM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com> wrote:

2020年3月11日 下午3:52,Prabhat Sahu <prabhat.sahu@enterprisedb.com> 写道:

On Mon, Mar 9, 2020 at 10:02 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com>
wrote:

Fixed in global_temporary_table_v18-pg13.patch.

Hi Wenjing,
Thanks for the patch. I have verified the previous issues with
"gtt_v18_pg13.patch" and those are resolved.
Please find below case:

postgres=# create sequence seq;
CREATE SEQUENCE

postgres=# CREATE GLOBAL TEMPORARY TABLE gtt1(c1 int PRIMARY KEY) ON
COMMIT DELETE ROWS;
CREATE TABLE

postgres=# CREATE GLOBAL TEMPORARY TABLE gtt2(c1 int PRIMARY KEY) ON
COMMIT PRESERVE ROWS;
CREATE TABLE

postgres=# alter table gtt1 add c2 int default nextval('seq');
ERROR: cannot reindex global temporary tables

postgres=# alter table gtt2 add c2 int default nextval('seq');
ERROR: cannot reindex global temporary tables

reindex GTT is already supported

Please check global_temporary_table_v20-pg13.patch

Wenjing

*Note*: We are getting this error if we have a key column(PK/UNIQUE) in a
GTT, and trying to add a column with a default sequence into it.

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

#204tushar
tushar.ahuja@enterprisedb.com
In reply to: 曾文旌(义从) (#200)
Re: [Proposal] Global temporary tables

On 3/17/20 9:15 AM, 曾文旌(义从) wrote:

reindex GTT is already supported

Please check global_temporary_table_v20-pg13.patch

Please refer this scenario -

postgres=# create global temp table co(n int) ;
CREATE TABLE

postgres=# create index fff on co(n);
CREATE INDEX

Case 1-
postgres=# reindex table  co;
REINDEX

Case -2
postgres=# reindex database postgres ;
WARNING:  global temp table "public.co" skip reindexed
REINDEX
postgres=#

Case 2 should work as similar to Case 1.

--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company

#205Prabhat Sahu
prabhat.sahu@enterprisedb.com
In reply to: Prabhat Sahu (#203)
Re: [Proposal] Global temporary tables

Hi All,

Please check the behavior of GTT having column with "SERIAL" datatype and
column with default value as "SEQUENCE" as below:

*Session1:*postgres=# create sequence gtt_c3_seq;
CREATE SEQUENCE
postgres=# create global temporary table gtt(c1 int, c2 serial, c3 int
default nextval('gtt_c3_seq') not null) on commit preserve rows;
CREATE TABLE

-- Structure of column c2 and c3 are similar:
postgres=# \d+ gtt
Table "public.gtt"
Column | Type | Collation | Nullable | Default
| Storage | Stats target | Description
--------+---------+-----------+----------+---------------------------------+---------+--------------+-------------
c1 | integer | | |
| plain | |
c2 | integer | | not null | nextval('gtt_c2_seq'::regclass)
| plain | |
c3 | integer | | not null | nextval('gtt_c3_seq'::regclass)
| plain | |
Access method: heap
Options: on_commit_delete_rows=false

postgres=# insert into gtt select generate_series(1,3);
INSERT 0 3
postgres=# select * from gtt;
c1 | c2 | c3
----+----+----
1 | 1 | 1
2 | 2 | 2
3 | 3 | 3
(3 rows)

*Session2:*postgres=# insert into gtt select generate_series(1,3);
INSERT 0 3
postgres=# select * from gtt;
c1 | c2 | c3
----+----+----
1 | 1 | 4
2 | 2 | 5
3 | 3 | 6
(3 rows)

Kindly let me know, Is this behavior expected?

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

#206Pavel Stehule
pavel.stehule@gmail.com
In reply to: Prabhat Sahu (#205)
Re: [Proposal] Global temporary tables

st 25. 3. 2020 v 13:53 odesílatel Prabhat Sahu <
prabhat.sahu@enterprisedb.com> napsal:

Hi All,

Please check the behavior of GTT having column with "SERIAL" datatype and
column with default value as "SEQUENCE" as below:

*Session1:*postgres=# create sequence gtt_c3_seq;
CREATE SEQUENCE
postgres=# create global temporary table gtt(c1 int, c2 serial, c3 int
default nextval('gtt_c3_seq') not null) on commit preserve rows;
CREATE TABLE

-- Structure of column c2 and c3 are similar:
postgres=# \d+ gtt
Table "public.gtt"
Column | Type | Collation | Nullable | Default
| Storage | Stats target | Description

--------+---------+-----------+----------+---------------------------------+---------+--------------+-------------
c1 | integer | | |
| plain | |
c2 | integer | | not null | nextval('gtt_c2_seq'::regclass)
| plain | |
c3 | integer | | not null | nextval('gtt_c3_seq'::regclass)
| plain | |
Access method: heap
Options: on_commit_delete_rows=false

postgres=# insert into gtt select generate_series(1,3);
INSERT 0 3
postgres=# select * from gtt;
c1 | c2 | c3
----+----+----
1 | 1 | 1
2 | 2 | 2
3 | 3 | 3
(3 rows)

*Session2:*postgres=# insert into gtt select generate_series(1,3);
INSERT 0 3
postgres=# select * from gtt;
c1 | c2 | c3
----+----+----
1 | 1 | 4
2 | 2 | 5
3 | 3 | 6
(3 rows)

Kindly let me know, Is this behavior expected?

It is interesting side effect - theoretically it is not important, because
sequence ensure just unique values - so values are not important.

You created classic shared sequence so the behave is correct and expected.

Pavel

Show quoted text

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

#207tushar
tushar.ahuja@enterprisedb.com
In reply to: 曾文旌(义从) (#200)
Re: [Proposal] Global temporary tables

On 3/17/20 9:15 AM, 曾文旌(义从) wrote:

Please check global_temporary_table_v20-pg13.patch

There is a typo in the error message

postgres=# create global temp table test(a int )
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
postgres=#

--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company

#208wjzeng
wenjing.zwj@alibaba-inc.com
In reply to: Prabhat Sahu (#205)
1 attachment(s)
Re: [Proposal] Global temporary tables

2020年3月25日 下午8:52,Prabhat Sahu <prabhat.sahu@enterprisedb.com> 写道:

Hi All,

Please check the behavior of GTT having column with "SERIAL" datatype and column with default value as "SEQUENCE" as below:

Session1:
postgres=# create sequence gtt_c3_seq;
CREATE SEQUENCE
postgres=# create global temporary table gtt(c1 int, c2 serial, c3 int default nextval('gtt_c3_seq') not null) on commit preserve rows;
CREATE TABLE

-- Structure of column c2 and c3 are similar:
postgres=# \d+ gtt
Table "public.gtt"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
--------+---------+-----------+----------+---------------------------------+---------+--------------+-------------
c1 | integer | | | | plain | |
c2 | integer | | not null | nextval('gtt_c2_seq'::regclass) | plain | |
c3 | integer | | not null | nextval('gtt_c3_seq'::regclass) | plain | |
Access method: heap
Options: on_commit_delete_rows=false

postgres=# insert into gtt select generate_series(1,3);
INSERT 0 3
postgres=# select * from gtt;
c1 | c2 | c3
----+----+----
1 | 1 | 1
2 | 2 | 2
3 | 3 | 3
(3 rows)

Session2:
postgres=# insert into gtt select generate_series(1,3);
INSERT 0 3
postgres=# select * from gtt;
c1 | c2 | c3
----+----+----
1 | 1 | 4
2 | 2 | 5
3 | 3 | 6
(3 rows)

Kindly let me know, Is this behavior expected?

--

postgres=# \d+
List of relations
Schema | Name | Type | Owner | Persistence | Size | Description
--------+------------+----------+-------------+-------------+------------+-------------
public | gtt | table | wenjing.zwj | session | 8192 bytes |
public | gtt_c2_seq | sequence | wenjing.zwj | session | 8192 bytes |
public | gtt_c3_seq | sequence | wenjing.zwj | permanent | 8192 bytes |
(3 rows)

This is expected.
GTT'sequence is the same as GTT, so gtt_c2_seq is independent of each sessions.
gtt_c3_seq is a classic sequence.

Wenjing

Show quoted text

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

Attachments:

smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#209wjzeng
wenjing.zwj@alibaba-inc.com
In reply to: Prabhat Sahu (#203)
2 attachment(s)
Re: [Proposal] Global temporary tables

2020年3月24日 下午9:34,Prabhat Sahu <prabhat.sahu@enterprisedb.com> 写道:

Hi Wenjing,
Please check my findings(on gtt_v20.patch) as below:

TestCase1: (cache lookup failed on GTT)
-- Session1:
postgres=# create global temporary table gtt1(c1 int) on commit delete rows;
CREATE TABLE

-- Session2:
postgres=# drop table gtt1 ;
DROP TABLE

-- Session1:
postgres=# create global temporary table gtt1(c1 int) on commit delete rows;
ERROR: cache lookup failed for relation 16384

TestCase2:
-- Session1:
postgres=# create global temporary table gtt (c1 integer) on commit preserve rows;
CREATE TABLE
postgres=# insert into gtt values(10);
INSERT 0 1

-- Session2:
postgres=# drop table gtt;
DROP TABLE

I hope "session2" should not allow to perform the "DROP" operation on GTT having data.

Sorry, I introduced this bug in my refactoring.
It's been fixed.

Wenjing

Show quoted text

Behavior of GTT in Oracle Database in such scenario: For a completed transaction on GTT with(on_commit_delete_rows='FALSE') with data in a session, we will not be able to DROP from any session, we need to TRUNCATE the data first to DROP the table.
SQL> drop table gtt;
drop table gtt
*
ERROR at line 1:
ORA-14452: attempt to create, alter or drop an index on temporary table already
in use

On Tue, Mar 17, 2020 at 9:16 AM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> wrote:

2020年3月11日 下午3:52,Prabhat Sahu <prabhat.sahu@enterprisedb.com <mailto:prabhat.sahu@enterprisedb.com>> 写道:

On Mon, Mar 9, 2020 at 10:02 PM 曾文旌(义从) <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> wrote:

Fixed in global_temporary_table_v18-pg13.patch.
Hi Wenjing,
Thanks for the patch. I have verified the previous issues with "gtt_v18_pg13.patch" and those are resolved.
Please find below case:

postgres=# create sequence seq;
CREATE SEQUENCE

postgres=# CREATE GLOBAL TEMPORARY TABLE gtt1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
CREATE TABLE

postgres=# CREATE GLOBAL TEMPORARY TABLE gtt2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
CREATE TABLE

postgres=# alter table gtt1 add c2 int default nextval('seq');
ERROR: cannot reindex global temporary tables

postgres=# alter table gtt2 add c2 int default nextval('seq');
ERROR: cannot reindex global temporary tables

reindex GTT is already supported

Please check global_temporary_table_v20-pg13.patch

Wenjing

Note: We are getting this error if we have a key column(PK/UNIQUE) in a GTT, and trying to add a column with a default sequence into it.

--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

Attachments:

global_temporary_table_v21-pg13.patchapplication/octet-stream; name=global_temporary_table_v21-pg13.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index ec207d3..eb5d8cf 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -168,6 +168,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * For global temp table only
+	 * use ShareUpdateExclusiveLock for ensure safety
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temp table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},	
 	/* list terminator */
 	{{NULL}}
 };
@@ -1509,6 +1522,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)},
@@ -1609,13 +1624,18 @@ build_reloptions(Datum reloptions, bool validate,
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	/*
 	 * There are no options for partitioned tables yet, but this is able to do
 	 * some validation.
 	 */
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index dd975b1..1610e7d 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1013,7 +1013,9 @@ gistGetFakeLSN(Relation rel)
 {
 	static XLogRecPtr counter = FirstNormalUnloggedLSN;
 
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	/* global temp is same as local temp table */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 4871b7f..16b00c9 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -149,7 +149,9 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	/* global temp table is same as local temp table */
+	if (!(index->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		index->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ca52846..f0153cf 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -598,7 +598,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -651,7 +651,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 03c43ef..2674132 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -62,6 +62,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -399,9 +400,9 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
 	Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
-	/* not every AM requires these to be valid, but heap does */
-	Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
-	Assert(MultiXactIdIsValid(onerel->rd_rel->relminmxid));
+	/* not every AM requires these to be valid, but regular heap does */
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ MultiXactIdIsValid(onerel->rd_rel->relminmxid));
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 39b8f17..abb76dc 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -601,6 +602,10 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index de2d4ee..7e120b5 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6348,6 +6348,10 @@ StartupXLOG(void)
 	else
 		recoveryTargetTLI = ControlFile->checkPointCopy.ThisTimeLineID;
 
+	/* clean temp relation files */
+	if (max_active_gtt > 0)
+		RemovePgTempFiles();
+
 	/*
 	 * Check for signal files, and if so set up state for offline recovery
 	 */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 9499bb3..ae47364 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -43,6 +43,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 7d6acae..3be8d63 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -393,7 +393,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/* global temp table is same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 9d9e915..785a5cd 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -427,7 +429,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -957,6 +959,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -995,8 +998,18 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/* global temp table not remember transaction info in catalog */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1358,6 +1371,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1938,6 +1952,14 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/* We allow to drop global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			elog(ERROR, "can not drop relation %s when other backend attached this global temp table",
+						RelationGetRelationName(rel));
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3165,7 +3187,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3177,7 +3199,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3223,8 +3245,13 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		/* truncate global temp table only need RowExclusiveLock */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3257,6 +3284,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3265,23 +3293,37 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (!gtt_storage_attached(RelationGetRelid(rel)))
+			return;
+
+		/*
+		 * Truncate global temp table only need RowExclusiveLock
+		 */
+		lockmode = RowExclusiveLock;
+	}
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(rel, 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 76fd938..5203ed2 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -52,6 +52,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -877,6 +878,19 @@ index_create(Relation heapRelation,
 						indexRelationName, RelationGetRelationName(heapRelation))));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temp table use concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot create indexes on global temporary tables using concurrent mode")));
+
+		/* if global temp table not init storage, then skip build index */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -2061,6 +2075,14 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/* We allow to drop index on global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+			elog(ERROR, "can not drop index %s when other backend attached this global temp table.",
+						RelationGetRelationName(userHeapRelation));
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2667,6 +2689,11 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	/* update index stats into localhash and rel_rd_rel for global temp table */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		is_gtt = true;
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2752,21 +2779,35 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
+		if (is_gtt)
+			rel->rd_rel->relpages = (int32) relpages;
+		else if (rd_rel->relpages != (int32) relpages)
 		{
 			rd_rel->relpages = (int32) relpages;
 			dirty = true;
 		}
-		if (rd_rel->reltuples != (float4) reltuples)
+
+		if (is_gtt)
+			rel->rd_rel->reltuples = (float4) reltuples;
+		else if (rd_rel->reltuples != (float4) reltuples)
 		{
 			rd_rel->reltuples = (float4) reltuples;
 			dirty = true;
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+
+		if (is_gtt)
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+		else if (rd_rel->relallvisible != (int32) relallvisible)
 		{
 			rd_rel->relallvisible = (int32) relallvisible;
 			dirty = true;
 		}
+
+		if (is_gtt)
+		{
+			up_gtt_relstats(rel, relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
+		}
 	}
 
 	/*
@@ -2880,6 +2921,15 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, index, val);
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			gtt_force_enable_index(indexRelation);
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3428,6 +3478,10 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	PGRUsage	ru0;
 	bool		progress = (options & REINDEXOPT_REPORT_PROGRESS) != 0;
 
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		!gtt_storage_attached(indexId))
+		return;
+
 	pg_rusage_init(&ru0);
 
 	/*
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 5ff7824..9b58f2a 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -647,6 +647,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		/* global temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp relations in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index fddfbf1..c3661e3 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -26,6 +26,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -56,6 +57,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			relOid;			/* InvalidOid if not a global temp rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -75,7 +77,7 @@ static PendingRelDelete *pendingDeletes = NULL; /* head of linked list */
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -85,6 +87,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;
@@ -112,11 +116,19 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->relOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/* remember global temp table storage info to localhash */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel)
+	{
+		pending->relOid = RelationGetRelid(rel);
+		remember_gtt_storage_info(rnode, rel);
+	}
+
 	return srel;
 }
 
@@ -153,11 +165,15 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->relOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->relOid = RelationGetRelid(rel);
+
 	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
@@ -437,6 +453,7 @@ smgrDoPendingDeletes(bool isCommit)
 				i = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -466,14 +483,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->relOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -486,9 +507,18 @@ smgrDoPendingDeletes(bool isCommit)
 		smgrdounlinkall(srels, nrels, false);
 
 		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* clean global temp table flags when transaction commit or rollback */
+			if (SmgrIsTemp(srels[i]) &&
+				reloids[i] != InvalidOid &&
+				gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..5bf3bb3
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1427 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  code to create and destroy physical storage for global temparary table
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_relstats_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+typedef struct gtt_ctl_data
+{
+	LWLock			lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+	Oid			relfilenode;
+	Oid			spcnode;
+
+	/* pg_class stat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+
+	/* pg_statistic */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_reset_statistics(gtt_local_hash_entry *entry);
+static void gtt_free_statistics(gtt_local_hash_entry *entry);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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();
+	}
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(gtt_fnode);
+	info.entrysize = action_gtt_shared_hash_entry_size();
+	active_gtt_shared_hash =
+		ShmemInitHash("active gtt shared hash",
+						gtt_shared_ctl->max_entry,
+						gtt_shared_ctl->max_entry,
+						&info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			found;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *)&(fnode), 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_global_temporary_table.")));
+	}
+
+	if (found == false)
+	{
+		int			wordnum;
+
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode				fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	if (!skiplock)
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		if (!skiplock)
+			LWLockRelease(&gtt_shared_ctl->lock);
+
+		if (isCommit)
+			elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+	bms_del_member(entry->map, MyBackendId);
+
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+
+	if (!skiplock)
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+	return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	Bitmapset	*map_copy = NULL;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool		in_use = false;
+	int			num_use = 0;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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;
+	gtt_relfilenode			*new_node = NULL;
+	Oid						relid = RelationGetRelid(rel);
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		/* First time through: initialize the hash table */
+		HASHCTL		ctl;
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_relstats_context =
+			AllocSetContextCreate(CacheMemoryContext,
+							"gtt relstats context",
+							ALLOCSET_DEFAULT_SIZES);
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+	if (!entry)
+	{
+		bool		found = false;
+		int 		natts = 0;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash,
+						(void *) &relid, HASH_ENTER, &found);
+
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+		entry->natts = 0;
+		entry->attnum = NULL;
+		entry->att_stat_tups = NULL;
+
+		natts = RelationGetNumberOfAttributes(rel);
+		entry->attnum = palloc0(sizeof(int) * natts);
+		entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+		entry->natts = natts;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			gtt_storage_checkin(relid);
+		}
+	}
+
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* only heap contain transaction information */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	gtt_reset_statistics(entry);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	ListCell		*lc;
+	gtt_relfilenode *d_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (!entry)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+		return;
+	}
+
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == rnode.relNode &&
+			gtt_rnode->spcnode == rnode.spcNode)
+		{
+			d_rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+
+		if (entry->relfilenode_list == NIL)
+		{
+			if (entry->relkind == RELKIND_RELATION ||
+				entry->relkind == RELKIND_SEQUENCE)
+				gtt_storage_checkout(relid, false, isCommit);
+
+			gtt_free_statistics(entry);
+			hash_search(gtt_storage_local_hash,
+					(void *) &(relid), HASH_REMOVE, NULL);
+		}
+
+		return;
+	}
+
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	pfree(d_rnode);
+	if (entry->relfilenode_list == NIL)
+	{
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(relid, false, isCommit);
+
+		gtt_free_statistics(entry);
+		hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_REMOVE, NULL);
+	}
+	else
+		gtt_reset_statistics(entry);
+
+	return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool found;
+
+	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;
+	SMgrRelation	*srels = NULL;
+	Oid				*relids = NULL;
+	char			*relkinds = NULL;
+	int			nrels = 0,
+				nfiles = 0,
+				maxrels = 0,
+				maxfiles = 0,
+				i = 0;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel;
+			RelFileNode 	rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel = smgropen(rnode, MyBackendId);
+
+			if (maxfiles == 0)
+			{
+				maxfiles = 32;
+				srels = palloc(sizeof(SMgrRelation) * maxfiles);
+			}
+			else if (maxfiles <= nfiles)
+			{
+				maxfiles *= 2;
+				srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+			}
+
+			srels[nfiles++] = srel;
+		}
+
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			relids  = palloc(sizeof(Oid) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			relids  = repalloc(relids , sizeof(Oid) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		relkinds[nrels] = entry->relkind;
+		relids[nrels] = entry->relid;
+		nrels++;
+	}
+
+	if (nfiles > 0)
+	{
+		smgrdounlinkall(srels, nfiles, false);
+		for (i = 0; i < nfiles; i++)
+			smgrclose(srels[i]);
+
+		pfree(srels);
+	}
+
+	if (nrels)
+	{
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+		for (i = 0; i < nrels; i++)
+		{
+			if (relkinds[i] == RELKIND_RELATION ||
+				relkinds[i] == RELKIND_SEQUENCE)
+				gtt_storage_checkout(relids[i], true, false);
+		}
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+		pfree(relids);
+		pfree(relkinds);
+	}
+
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+	return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible) 
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+					BlockNumber num_pages,
+					double num_tuples,
+					BlockNumber num_all_visible_pages,
+					TransactionId relfrozenxid,
+					TransactionId relminmxid)
+{
+	Oid		relid = RelationGetRelid(relation);
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_FIND, &found);
+
+	if (!found)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (num_pages >= 0 &&
+		gtt_rnode->relpages != (int32)num_pages)
+		gtt_rnode->relpages = (int32)num_pages;
+
+	if (num_tuples >= 0 &&
+		gtt_rnode->reltuples != (float4)num_tuples)
+		gtt_rnode->reltuples = (float4)num_tuples;
+
+	/* only heap contain transaction information and relallvisible */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (gtt_rnode->relallvisible >= 0 &&
+			gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+		{
+			gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+		}
+
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNewTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible) 
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (gtt_storage_local_hash == NULL)
+		return false;
+
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_FIND, &found);
+
+	if (!found)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	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->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	Assert(entry->relid == reloid);
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == 0)
+		{
+			entry->attnum[i] = attnum;
+			break;
+		}
+		else if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < entry->natts);
+	Assert(entry->att_stat_tups[i] == NULL);
+	entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+						(void *) &reloid, HASH_FIND, &found);
+
+	if (!found)
+		return NULL;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			return entry->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int			i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	HeapTuple	tuple;
+	Relation	rel = NULL;
+	int		attnum = PG_GETARG_INT32(1);
+	Oid		reloid = PG_GETARG_OID(0);
+	char		rel_persistence;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	  tupdesc;
+	MemoryContext oldcontext;
+	Tuplestorestate *tupstore;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+
+	relation_close(rel, NoLock);
+	relation_close(pg_tatistic, AccessShareLock);
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate	*tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Oid			relnode = 0;
+	Relation	rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[6];
+		bool	isnull[6];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(reloid);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(indexOid));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid relOid = index->rd_index->indrelid;
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	if (gtt_storage_attached(relOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int			i;
+	Oid			toastrelid;
+
+	if (operation != CMD_INSERT)
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+
+		index_build(relation, index, info, true, false);
+	}
+
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid 		indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo 	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+static void
+gtt_reset_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+
+		entry->attnum[i] = 0;
+	}
+
+	return;
+}
+
+static void
+gtt_free_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (entry->attnum)
+		pfree(entry->attnum);
+
+	if (entry->att_stat_tups)
+		pfree(entry->att_stat_tups);
+
+	return;
+}
+
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	if (gtt_storage_local_hash == NULL)
+		return InvalidOid;
+
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_FIND, &found);
+
+	if (!found)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index b8a3f46..4b46d77 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM 
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 924ef37..db12eef 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -184,6 +185,13 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -586,14 +594,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 		 * pg_statistic for columns we didn't process, we leave them alone.)
 		 */
 		update_attstats(RelationGetRelid(onerel), inh,
-						attr_cnt, vacattrstats);
+						attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
 
 		for (ind = 0; ind < nindexes; ind++)
 		{
 			AnlIndexData *thisdata = &indexdata[ind];
 
 			update_attstats(RelationGetRelid(Irel[ind]), false,
-							thisdata->attr_cnt, thisdata->vacattrstats);
+							thisdata->attr_cnt, thisdata->vacattrstats,
+							RelationGetRelPersistence(Irel[ind]));
 		}
 
 		/*
@@ -1456,7 +1465,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
  *		by taking a self-exclusive lock on the relation in analyze_rel().
  */
 static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
 {
 	Relation	sd;
 	int			attno;
@@ -1558,31 +1567,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/* Update column statistic to localhash, not catalog */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 11ce1bb..552fd87 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -391,6 +391,12 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/* not support cluster global temp table yet */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("not support cluster global temporary tables yet")));
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index fbde9f8..7974ea4 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -27,6 +27,7 @@
 #include "catalog/dependency.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -1063,7 +1064,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, stmt->filename, stmt->is_program,
@@ -2841,6 +2842,8 @@ CopyFrom(CopyState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	estate->es_result_relations = resultRelInfo;
 	estate->es_num_result_relations = 1;
 	estate->es_result_relation_info = resultRelInfo;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 6d696dd..4f958d6 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2343,7 +2343,9 @@ ReindexIndex(RangeVar *indexRelation, int options, bool concurrent)
 	persistence = irel->rd_rel->relpersistence;
 	index_close(irel, NoLock);
 
-	if (concurrent && persistence != RELPERSISTENCE_TEMP)
+	if (concurrent &&
+		persistence != RELPERSISTENCE_TEMP &&
+		persistence != RELPERSISTENCE_GLOBAL_TEMP)
 		ReindexRelationConcurrently(indOid, options);
 	else
 		reindex_index(indOid, false, persistence,
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index d8cafc4..deecde8 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -96,7 +96,8 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 	 * transaction.
 	 */
 	relpersistence = get_rel_persistence(relid);
-	if (relpersistence == RELPERSISTENCE_TEMP)
+	if (relpersistence == RELPERSISTENCE_TEMP ||
+		relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
 
 	/* Check permissions. */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..d7119b7 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -94,7 +96,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -222,7 +225,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* now initialize the sequence's data */
 	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -327,7 +330,7 @@ ResetSequence(Oid seq_relid)
 	/*
 	 * Insert the modified tuple into the new storage file.
 	 */
-	fill_seq_with_data(seq_rel, tuple);
+	fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -340,17 +343,21 @@ ResetSequence(Oid seq_relid)
  * Initialize a sequence's relation with the specified tuple as content
  */
 static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 	sequence_magic *sm;
 	OffsetNumber offnum;
+	bool lockBuffer = false;
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ReadBuffer(rel, P_NEW);
-	Assert(BufferGetBlockNumber(buf) == 0);
+	if (buf == InvalidBuffer)
+	{
+		buf = ReadBuffer(rel, P_NEW);
+		Assert(BufferGetBlockNumber(buf) == 0);
+		lockBuffer = true;
+	}
 
 	page = BufferGetPage(buf);
 
@@ -360,7 +367,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* Now insert sequence tuple */
 
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+	if (lockBuffer)
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +418,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	END_CRIT_SECTION();
 
-	UnlockReleaseBuffer(buf);
+	if (lockBuffer)
+		UnlockReleaseBuffer(buf);
 }
 
 /*
@@ -502,7 +511,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * Insert the modified tuple into the new storage file.
 		 */
-		fill_seq_with_data(seqrel, newdatatuple);
+		fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
 	}
 
 	/* process OWNED BY if given */
@@ -611,7 +620,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +945,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1154,6 +1163,13 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1954,3 +1970,46 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+void
+gtt_init_seq(Relation rel)
+{
+	/* Initialize sequence for global temporary tables */
+	Datum		value[SEQ_COL_LASTCOL] = {0};
+	bool		null[SEQ_COL_LASTCOL] = {false};
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8c33b67..c2c319f 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -554,6 +555,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 								  Relation partitionTbl);
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static OnCommitAction gtt_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -599,6 +601,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -609,8 +612,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")));
@@ -640,7 +645,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	/* global temp table same as local temp table */
+	if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+		 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -741,6 +748,55 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* global temp table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* check parent table*/
+		if (inheritOids)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			table_close(relation, NoLock);
+		}
+
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temp table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1817,6 +1873,10 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
 		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 			continue;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			continue;
+
 		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
@@ -3568,6 +3628,14 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			elog(ERROR, "can not alter table %s when other backend attached this global temp table",
+						RelationGetRelationName(rel));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -8180,6 +8248,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -12687,6 +12761,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -12889,6 +12966,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temp table");
+
 	/*
 	 * No work if no change in tablespace.
 	 */
@@ -13261,7 +13341,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,
@@ -14668,7 +14748,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",
@@ -17260,3 +17342,28 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
 		table_close(rel, NoLock);
 	}
 }
+
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			if (defGetBoolean(def))
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index d625d17..d780a1c 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/vacuum.h"
@@ -1217,6 +1218,17 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	 /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		is_gtt = true;
+		up_gtt_relstats(relation,
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1230,17 +1242,26 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relpages = (int32) num_pages;
+	else if (pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (is_gtt)
+		relation->rd_rel->reltuples = (float4) num_tuples;
+	else if (pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	else if (pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1285,7 +1306,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1296,7 +1318,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1394,6 +1417,10 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/* global temp table relstats not in pg_class */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1451,6 +1478,25 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/*
+	 * Global temp table get frozenxid from MyProc
+	 * to avoid the vacuum truncate clog that gtt need.
+	 */
+	if (max_active_gtt > 0)
+	{
+		TransactionId oldest_gtt_frozenxid =
+			list_all_session_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid) &&
+			TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+		{
+			ereport(WARNING,
+				(errmsg("global temp table oldest FrozenXid is far in the past"),
+				 errhint("please truncate them or kill those sessions that use them.")));
+			newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1778,6 +1824,18 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		ereport(WARNING,
+				(errmsg("skipping vacuum global temp table \"%s\" because storage is not initialized for current session",
+						RelationGetRelationName(onerel))));
+		relation_close(onerel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6e65103..04706ee 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -530,6 +530,11 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 28130fb..dcb4a0b 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index ef74ad8..b1cd1c0 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
@@ -544,6 +545,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	init_gtt_storage(mtstate->operation, leaf_part_rri);
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d71c0a4..0900907 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,6 +42,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -2372,6 +2373,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			ExecOpenIndices(resultRelInfo,
 							node->onConflictAction != ONCONFLICT_NONE);
 
+		init_gtt_storage(operation, resultRelInfo);
+
 		/*
 		 * If this is an UPDATE and a BEFORE UPDATE trigger is present, the
 		 * trigger itself might modify the partition-key values. So arrange
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 905bbe7..170963d 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -591,6 +591,8 @@ static void
 set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 						  RangeTblEntry *rte)
 {
+	char relpersistence;
+
 	/*
 	 * The flag has previously been initialized to false, so we can just
 	 * return if it becomes clear that we can't safely set it.
@@ -618,7 +620,11 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			/* global temp table is same as local temp table */
+			relpersistence = get_rel_persistence(rte->relid);
+
+			if (relpersistence == RELPERSISTENCE_TEMP ||
+				relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b44efd6..14dbaaf 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6307,7 +6307,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
+	/* global temp table is same as local temp table */
 	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index d82fc5a..c8f06eb 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -28,6 +28,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temp table */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6676412..0685c1c 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2585,6 +2585,11 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("materialized views must not use temporary tables or views")));
 
+		if (is_query_using_gtt(query))
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("materialized views must not use global temporary tables or views")));
+
 		/*
 		 * A materialized view would either need to save parameters for use in
 		 * maintaining/loading the data or prohibit them entirely.  The latter
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7e384f9..6ac4aa0 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3291,17 +3291,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; }
@@ -11613,19 +11607,13 @@ OptTempTableName:
 				}
 			| GLOBAL TEMPORARY opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED opt_table qualified_name
 				{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b875a50..123945b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index c191141..fe1c494 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index da75e75..f4b97e8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2087,6 +2087,11 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* autovacuum skip vacuum global temp table */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2153,7 +2158,9 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		/* autovacuum skip vacuum global temp table */
+		if (classForm->relpersistence == RELPERSISTENCE_TEMP || 
+			classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index e05e2b3..b45904c 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -52,6 +53,7 @@
 #include "utils/rel.h"
 #include "utils/resowner_private.h"
 #include "utils/timestamp.h"
+#include "utils/guc.h"
 
 
 /* Note: these two macros only work on shared buffers, not local ones! */
@@ -2756,6 +2758,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d5..7adb969 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -21,6 +21,7 @@
 #include "access/nbtree.h"
 #include "access/subtrans.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, active_gtt_shared_hash_size());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* global temporary table */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index f45a619..cf8b20a 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -63,6 +63,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -4088,3 +4089,77 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temp table.
+ */
+int
+list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct *arrayP = procArray;
+	TransactionId result = InvalidTransactionId;
+	int			index;
+	int			flags = 0;
+	int			i = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+		*n = 0;
+	}
+
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		volatile PGXACT *pgxact = &allPgXact[pgprocno];
+
+		if (pgxact->vacuumFlags & flags)
+			continue;
+
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->session_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->session_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->session_gtt_frozenxid, result))
+				result = proc->session_gtt_frozenxid;
+
+			if (max_size > 0)
+			{
+				pids[i] = proc->pid;
+				xids[i] = proc->session_gtt_frozenxid;
+				i++;
+			}
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (max_size > 0)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index eb321f7..3893cef 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 8406644..0d99321 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -1008,6 +1008,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 8339f4c..d41c05d 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -112,6 +112,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -4571,12 +4572,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))
 							{
@@ -4701,15 +4715,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))
 		{
@@ -6091,6 +6117,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;
@@ -6108,6 +6135,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,
@@ -6119,6 +6153,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;
@@ -6134,6 +6170,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,
@@ -7047,6 +7090,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))
@@ -7059,6 +7104,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 =
@@ -7071,6 +7124,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.
@@ -7090,6 +7145,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 27bbb58..9b5e719 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -2904,6 +2905,18 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index ff70326..5aa5b38 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -63,6 +63,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1127,6 +1128,24 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1181,6 +1200,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			gtt_fix_index_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -1303,7 +1323,17 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+			if (newrelnode != InvalidOid &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2220,6 +2250,8 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		gtt_fix_index_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3316,6 +3348,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3429,22 +3465,28 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	MultiXactId minmulti = InvalidMultiXactId;
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
+	bool		modify_pg_class = !RELATION_IS_GLOBAL_TEMP(relation);
 
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
 
-	/*
-	 * Get a writable copy of the pg_class tuple for the given relation.
-	 */
-	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+	if (modify_pg_class)
+	{
+		/*
+		 * Get a writable copy of the pg_class tuple for the given relation.
+		 */
+		pg_class = table_open(RelationRelationId, RowExclusiveLock);
 
-	tuple = SearchSysCacheCopy1(RELOID,
-								ObjectIdGetDatum(RelationGetRelid(relation)));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "could not find tuple for relation %u",
-			 RelationGetRelid(relation));
-	classform = (Form_pg_class) GETSTRUCT(tuple);
+		tuple = SearchSysCacheCopy1(RELOID,
+									ObjectIdGetDatum(RelationGetRelid(relation)));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u",
+				 RelationGetRelid(relation));
+		classform = (Form_pg_class) GETSTRUCT(tuple);
+	}
+	else
+		memset(&classform, 0, sizeof(classform));
 
 	/*
 	 * Schedule unlinking of the old storage at transaction commit.
@@ -3470,7 +3512,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;
@@ -3490,6 +3532,15 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 			break;
 	}
 
+	if (!modify_pg_class)
+	{
+		Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+		Assert(RELATION_IS_GLOBAL_TEMP(relation));
+		Assert(!RelationIsMapped(relation));
+		relation->rd_node.relNode = relnode;
+		CacheInvalidateRelcache(relation);
+	}
 	/*
 	 * If we're dealing with a mapped index, pg_class.relfilenode doesn't
 	 * change; instead we have to send the update to the relation mapper.
@@ -3499,7 +3550,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	 * possibly-inaccurate values of relpages etc, but those will be fixed up
 	 * later.
 	 */
-	if (RelationIsMapped(relation))
+	else if (RelationIsMapped(relation))
 	{
 		/* This case is only supported for indexes */
 		Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3545,9 +3596,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 		CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 	}
 
-	heap_freetuple(tuple);
+	if (modify_pg_class)
+	{
+		heap_freetuple(tuple);
 
-	table_close(pg_class, RowExclusiveLock);
+		table_close(pg_class, RowExclusiveLock);
+	}
 
 	/*
 	 * Make the pg_class row change or relation map change visible.  This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 6808231..20ef76f 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -141,6 +141,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,
@@ -2048,6 +2060,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 ced0681..aa9c4a2 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15655,6 +15655,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	{
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
+		char		*table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15706,9 +15707,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 			binary_upgrade_set_pg_class_oids(fout, q,
 											 tbinfo->dobj.catId.oid, false);
 
+		if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED)
+			table_type = "UNLOGGED ";
+		else if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			table_type = "GLOBAL TEMPORARY ";
+		else
+			table_type = "";
+
 		appendPQExpBuffer(q, "CREATE %s%s %s",
-						  tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ?
-						  "UNLOGGED " : "",
+						  table_type,
 						  reltypename,
 						  qualrelname);
 
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 5f9a102..3e40297 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -84,7 +84,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -157,7 +157,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 7e524ea..371168f 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -441,22 +443,46 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 	 * output, so we have to copy that system table.  It's easiest to do that
 	 * by treating it as a user table.
 	 */
-	snprintf(query + strlen(query), sizeof(query) - strlen(query),
-			 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
-			 "  SELECT c.oid, 0::oid, 0::oid "
-			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
-			 "         ON c.relnamespace = n.oid "
-			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
-	/* exclude possible orphaned temp tables */
-			 "    ((n.nspname !~ '^pg_temp_' AND "
-			 "      n.nspname !~ '^pg_toast_temp_' AND "
-			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
-			 "                        'binary_upgrade', 'pg_toast') AND "
-			 "      c.oid >= %u::pg_catalog.oid) OR "
-			 "     (n.nspname = 'pg_catalog' AND "
-			 "      relname IN ('pg_largeobject') ))), ",
-			 FirstNormalObjectId);
+	if (skip_gtt)
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude global temp tables */
+				 "    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
+	else
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
 
 	/*
 	 * Add a CTE that collects OIDs of toast tables belonging to the tables
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 70194eb..8dd9e66 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -411,7 +411,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -642,7 +642,9 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -653,7 +655,9 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 8b90cef..d155205 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -389,7 +389,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 109245f..557cce1 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3724,7 +3724,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ae35fa4..4486f89 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1023,6 +1023,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2386,6 +2388,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2594,6 +2599,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 78b33b2..4be89f6 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -168,6 +168,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 7fb574f..8ce1614 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5507,6 +5507,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4191',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4192',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4193',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4194',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 048003c..af48cdf 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -19,7 +19,7 @@
 #include "storage/smgr.h"
 #include "utils/relcache.h"
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationTruncate(Relation rel, BlockNumber nblocks);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..89a1b51
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,44 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Relation relation,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void gtt_force_enable_index(Relation index);
+extern void gtt_fix_index_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index e2638ab..89a5ce4 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 3f88683..f870e9a 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 8fda8e4..172eae9 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_TBM,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_SXACT,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index d217801..8adde87 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -117,6 +117,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId session_gtt_frozenxid;	/* session level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ce93ace..0f7262e 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -281,6 +281,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 44ed04d..6b6d2da 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	struct SMgrRelationData *rd_smgr;	/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -277,6 +277,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -535,11 +536,13 @@ typedef struct ViewOptions
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -547,6 +550,7 @@ typedef struct ViewOptions
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -559,6 +563,14 @@ typedef struct ViewOptions
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is either local temp relation of this session
+ * 		or global temp relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -602,6 +614,17 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..ca2d135
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,14 @@
+reset search_path;
+select pg_sleep(5);
+ pg_sleep 
+----------
+ 
+(1 row)
+
+drop schema gtt cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
+drop cascades to table gtt.gtt_with_seq
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..2baa395
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,363 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+ n 
+---
+ 9
+(1 row)
+
+commit;
+-- 0 row
+select * from gtt6;
+ n 
+---
+(0 rows)
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+ERROR:  cannot create indexes on global temporary tables using concurrent mode
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+ERROR:  not support cluster global temporary tables yet
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temp table
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  could not create global temporary table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test;
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR:  insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count 
+-------
+     1
+(1 row)
+
+select count(*) from orders;
+ count 
+-------
+     0
+(1 row)
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  2 |  3
+(2 rows)
+
+commit;
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+select * from gtt_s_2 order by c1;
+ c1 | c2 
+----+----
+  1 |  2
+  2 |  4
+(2 rows)
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 1)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 300)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 23 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.gtt6
+drop cascades to table gtt_function.foo
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to sequence gtt_function.seq_1
+drop cascades to table gtt_function.gtt_s_1
+drop cascades to table gtt_function.gtt_s_2
+drop cascades to table gtt_function.gt1
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..0646aae
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,90 @@
+set search_path=gtt,sys;
+select nextval('gtt_with_seq_c2_seq');
+ nextval 
+---------
+       1
+(1 row)
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..de2373c
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,287 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+rollback;
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+commit;
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          114688 |                 614400
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |            49152 |         49152 |                  49152
+(2 rows)
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+ c1 | c2 
+----+----
+  1 |  1
+(1 row)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..8c0c376
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,10 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..4420fdb
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,80 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+ gtt_stats  | gtt_pkey  |        1 |         0 |             0
+(2 rows)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+reindex table gtt;
+reindex index gtt_pkey;
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+ gtt_stats  | gtt_pkey  |       30 |     10000 |             0
+(2 rows)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index c730461..fa08e6d 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1349,6 +1349,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d2b17dd..80e577f 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..2c8e586
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,8 @@
+
+
+reset search_path;
+
+select pg_sleep(5);
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..af2bb20
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,239 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+commit;
+-- 0 row
+select * from gtt6;
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test;
+
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+commit;
+select * from gtt_s_1 order by c1;
+select * from gtt_s_2 order by c1;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d05745e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,44 @@
+
+
+set search_path=gtt,sys;
+
+select nextval('gtt_with_seq_c2_seq');
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..3ac81a1
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,123 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+rollback;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+commit;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..dbe84d1
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,19 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..d61b0ff
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,46 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+reindex table gtt;
+
+reindex index gtt_pkey;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#210wjzeng
wenjing.zwj@alibaba-inc.com
In reply to: tushar (#204)
1 attachment(s)
Re: [Proposal] Global temporary tables

2020年3月25日 下午6:44,tushar <tushar.ahuja@enterprisedb.com> 写道:

On 3/17/20 9:15 AM, 曾文旌(义从) wrote:

reindex GTT is already supported

Please check global_temporary_table_v20-pg13.patch

Please refer this scenario -

postgres=# create global temp table co(n int) ;
CREATE TABLE

postgres=# create index fff on co(n);
CREATE INDEX

Case 1-
postgres=# reindex table co;
REINDEX

Case -2
postgres=# reindex database postgres ;
WARNING: global temp table "public.co" skip reindexed

I fixed in global_temporary_table_v21-pg13.patch

Wenjing

Show quoted text

REINDEX
postgres=#

Case 2 should work as similar to Case 1.

--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company

Attachments:

smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#211wjzeng
wenjing.zwj@alibaba-inc.com
In reply to: tushar (#207)
1 attachment(s)
Re: [Proposal] Global temporary tables

2020年3月25日 下午10:16,tushar <tushar.ahuja@enterprisedb.com> 写道:

On 3/17/20 9:15 AM, 曾文旌(义从) wrote:

Please check global_temporary_table_v20-pg13.patch

There is a typo in the error message

postgres=# create global temp table test(a int ) 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
postgres=#

Thank you pointed out.
I fixed in global_temporary_table_v21-pg13.patch

Wenjing

Show quoted text

--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company

Attachments:

smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#212Prabhat Sahu
prabhat.sahu@enterprisedb.com
In reply to: wjzeng (#209)
Re: [Proposal] Global temporary tables

Sorry, I introduced this bug in my refactoring.
It's been fixed.

Wenjing

Hi Wenjing,

This patch(gtt_v21_pg13.patch) is not applicable on PG HEAD, I hope you
have prepared the patch on top of some previous commit.
Could you please rebase the patch which we can apply on HEAD ?

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

#213曾文旌
wenjing.zwj@alibaba-inc.com
In reply to: Prabhat Sahu (#212)
2 attachment(s)
Re: [Proposal] Global temporary tables

2020年3月26日 下午12:34,Prabhat Sahu <prabhat.sahu@enterprisedb.com> 写道:

Sorry, I introduced this bug in my refactoring.
It's been fixed.

Wenjing

Hi Wenjing,
This patch(gtt_v21_pg13.patch) is not applicable on PG HEAD, I hope you have prepared the patch on top of some previous commit.
Could you please rebase the patch which we can apply on HEAD ?

Yes, It looks like the built-in functions are in conflict with new code.

Wenjing

Show quoted text

--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

Attachments:

global_temporary_table_v22-pg13.patchapplication/octet-stream; name=global_temporary_table_v22-pg13.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index ec207d3..eb5d8cf 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -168,6 +168,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * For global temp table only
+	 * use ShareUpdateExclusiveLock for ensure safety
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temp table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},	
 	/* list terminator */
 	{{NULL}}
 };
@@ -1509,6 +1522,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)},
@@ -1609,13 +1624,18 @@ build_reloptions(Datum reloptions, bool validate,
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	/*
 	 * There are no options for partitioned tables yet, but this is able to do
 	 * some validation.
 	 */
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index dd975b1..1610e7d 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1013,7 +1013,9 @@ gistGetFakeLSN(Relation rel)
 {
 	static XLogRecPtr counter = FirstNormalUnloggedLSN;
 
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	/* global temp is same as local temp table */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 4871b7f..16b00c9 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -149,7 +149,9 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	/* global temp table is same as local temp table */
+	if (!(index->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		index->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ca52846..f0153cf 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -598,7 +598,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -651,7 +651,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 03c43ef..2674132 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -62,6 +62,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -399,9 +400,9 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
 	Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
-	/* not every AM requires these to be valid, but heap does */
-	Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
-	Assert(MultiXactIdIsValid(onerel->rd_rel->relminmxid));
+	/* not every AM requires these to be valid, but regular heap does */
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ MultiXactIdIsValid(onerel->rd_rel->relminmxid));
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 39b8f17..abb76dc 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -601,6 +602,10 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 7621fc0..5e6ca4c 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6370,6 +6370,10 @@ StartupXLOG(void)
 	else
 		recoveryTargetTLI = ControlFile->checkPointCopy.ThisTimeLineID;
 
+	/* clean temp relation files */
+	if (max_active_gtt > 0)
+		RemovePgTempFiles();
+
 	/*
 	 * Check for signal files, and if so set up state for offline recovery
 	 */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 9499bb3..ae47364 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -43,6 +43,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 7d6acae..3be8d63 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -393,7 +393,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/* global temp table is same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 9d9e915..785a5cd 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -427,7 +429,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -957,6 +959,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -995,8 +998,18 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/* global temp table not remember transaction info in catalog */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1358,6 +1371,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1938,6 +1952,14 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/* We allow to drop global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			elog(ERROR, "can not drop relation %s when other backend attached this global temp table",
+						RelationGetRelationName(rel));
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3165,7 +3187,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3177,7 +3199,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3223,8 +3245,13 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		/* truncate global temp table only need RowExclusiveLock */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3257,6 +3284,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3265,23 +3293,37 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (!gtt_storage_attached(RelationGetRelid(rel)))
+			return;
+
+		/*
+		 * Truncate global temp table only need RowExclusiveLock
+		 */
+		lockmode = RowExclusiveLock;
+	}
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(rel, 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 2d81bc3..2270aa4 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -52,6 +52,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -877,6 +878,19 @@ index_create(Relation heapRelation,
 						indexRelationName, RelationGetRelationName(heapRelation))));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temp table use concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot create indexes on global temporary tables using concurrent mode")));
+
+		/* if global temp table not init storage, then skip build index */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -2061,6 +2075,14 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/* We allow to drop index on global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+			elog(ERROR, "can not drop index %s when other backend attached this global temp table.",
+						RelationGetRelationName(userHeapRelation));
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2667,6 +2689,11 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	/* update index stats into localhash and rel_rd_rel for global temp table */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		is_gtt = true;
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2752,21 +2779,35 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
+		if (is_gtt)
+			rel->rd_rel->relpages = (int32) relpages;
+		else if (rd_rel->relpages != (int32) relpages)
 		{
 			rd_rel->relpages = (int32) relpages;
 			dirty = true;
 		}
-		if (rd_rel->reltuples != (float4) reltuples)
+
+		if (is_gtt)
+			rel->rd_rel->reltuples = (float4) reltuples;
+		else if (rd_rel->reltuples != (float4) reltuples)
 		{
 			rd_rel->reltuples = (float4) reltuples;
 			dirty = true;
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+
+		if (is_gtt)
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+		else if (rd_rel->relallvisible != (int32) relallvisible)
 		{
 			rd_rel->relallvisible = (int32) relallvisible;
 			dirty = true;
 		}
+
+		if (is_gtt)
+		{
+			up_gtt_relstats(rel, relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
+		}
 	}
 
 	/*
@@ -2880,6 +2921,15 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, index, val);
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			gtt_force_enable_index(indexRelation);
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3428,6 +3478,10 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	PGRUsage	ru0;
 	bool		progress = (options & REINDEXOPT_REPORT_PROGRESS) != 0;
 
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		!gtt_storage_attached(indexId))
+		return;
+
 	pg_rusage_init(&ru0);
 
 	/*
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 2ec2301..1b6061d 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -655,6 +655,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		/* global temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp relations in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index fddfbf1..c3661e3 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -26,6 +26,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -56,6 +57,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			relOid;			/* InvalidOid if not a global temp rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -75,7 +77,7 @@ static PendingRelDelete *pendingDeletes = NULL; /* head of linked list */
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -85,6 +87,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;
@@ -112,11 +116,19 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->relOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/* remember global temp table storage info to localhash */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel)
+	{
+		pending->relOid = RelationGetRelid(rel);
+		remember_gtt_storage_info(rnode, rel);
+	}
+
 	return srel;
 }
 
@@ -153,11 +165,15 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->relOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->relOid = RelationGetRelid(rel);
+
 	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
@@ -437,6 +453,7 @@ smgrDoPendingDeletes(bool isCommit)
 				i = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -466,14 +483,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->relOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -486,9 +507,18 @@ smgrDoPendingDeletes(bool isCommit)
 		smgrdounlinkall(srels, nrels, false);
 
 		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* clean global temp table flags when transaction commit or rollback */
+			if (SmgrIsTemp(srels[i]) &&
+				reloids[i] != InvalidOid &&
+				gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..5bf3bb3
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1427 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  code to create and destroy physical storage for global temparary table
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_relstats_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+typedef struct gtt_ctl_data
+{
+	LWLock			lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+	Oid			relfilenode;
+	Oid			spcnode;
+
+	/* pg_class stat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+
+	/* pg_statistic */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_reset_statistics(gtt_local_hash_entry *entry);
+static void gtt_free_statistics(gtt_local_hash_entry *entry);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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();
+	}
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(gtt_fnode);
+	info.entrysize = action_gtt_shared_hash_entry_size();
+	active_gtt_shared_hash =
+		ShmemInitHash("active gtt shared hash",
+						gtt_shared_ctl->max_entry,
+						gtt_shared_ctl->max_entry,
+						&info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			found;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *)&(fnode), 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_global_temporary_table.")));
+	}
+
+	if (found == false)
+	{
+		int			wordnum;
+
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode				fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	if (!skiplock)
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		if (!skiplock)
+			LWLockRelease(&gtt_shared_ctl->lock);
+
+		if (isCommit)
+			elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+	bms_del_member(entry->map, MyBackendId);
+
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+
+	if (!skiplock)
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+	return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	Bitmapset	*map_copy = NULL;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool		in_use = false;
+	int			num_use = 0;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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;
+	gtt_relfilenode			*new_node = NULL;
+	Oid						relid = RelationGetRelid(rel);
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		/* First time through: initialize the hash table */
+		HASHCTL		ctl;
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_relstats_context =
+			AllocSetContextCreate(CacheMemoryContext,
+							"gtt relstats context",
+							ALLOCSET_DEFAULT_SIZES);
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+	if (!entry)
+	{
+		bool		found = false;
+		int 		natts = 0;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash,
+						(void *) &relid, HASH_ENTER, &found);
+
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+		entry->natts = 0;
+		entry->attnum = NULL;
+		entry->att_stat_tups = NULL;
+
+		natts = RelationGetNumberOfAttributes(rel);
+		entry->attnum = palloc0(sizeof(int) * natts);
+		entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+		entry->natts = natts;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			gtt_storage_checkin(relid);
+		}
+	}
+
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* only heap contain transaction information */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	gtt_reset_statistics(entry);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	ListCell		*lc;
+	gtt_relfilenode *d_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (!entry)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+		return;
+	}
+
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == rnode.relNode &&
+			gtt_rnode->spcnode == rnode.spcNode)
+		{
+			d_rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+
+		if (entry->relfilenode_list == NIL)
+		{
+			if (entry->relkind == RELKIND_RELATION ||
+				entry->relkind == RELKIND_SEQUENCE)
+				gtt_storage_checkout(relid, false, isCommit);
+
+			gtt_free_statistics(entry);
+			hash_search(gtt_storage_local_hash,
+					(void *) &(relid), HASH_REMOVE, NULL);
+		}
+
+		return;
+	}
+
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	pfree(d_rnode);
+	if (entry->relfilenode_list == NIL)
+	{
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(relid, false, isCommit);
+
+		gtt_free_statistics(entry);
+		hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_REMOVE, NULL);
+	}
+	else
+		gtt_reset_statistics(entry);
+
+	return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool found;
+
+	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;
+	SMgrRelation	*srels = NULL;
+	Oid				*relids = NULL;
+	char			*relkinds = NULL;
+	int			nrels = 0,
+				nfiles = 0,
+				maxrels = 0,
+				maxfiles = 0,
+				i = 0;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel;
+			RelFileNode 	rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel = smgropen(rnode, MyBackendId);
+
+			if (maxfiles == 0)
+			{
+				maxfiles = 32;
+				srels = palloc(sizeof(SMgrRelation) * maxfiles);
+			}
+			else if (maxfiles <= nfiles)
+			{
+				maxfiles *= 2;
+				srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+			}
+
+			srels[nfiles++] = srel;
+		}
+
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			relids  = palloc(sizeof(Oid) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			relids  = repalloc(relids , sizeof(Oid) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		relkinds[nrels] = entry->relkind;
+		relids[nrels] = entry->relid;
+		nrels++;
+	}
+
+	if (nfiles > 0)
+	{
+		smgrdounlinkall(srels, nfiles, false);
+		for (i = 0; i < nfiles; i++)
+			smgrclose(srels[i]);
+
+		pfree(srels);
+	}
+
+	if (nrels)
+	{
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+		for (i = 0; i < nrels; i++)
+		{
+			if (relkinds[i] == RELKIND_RELATION ||
+				relkinds[i] == RELKIND_SEQUENCE)
+				gtt_storage_checkout(relids[i], true, false);
+		}
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+		pfree(relids);
+		pfree(relkinds);
+	}
+
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+	return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible) 
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+					BlockNumber num_pages,
+					double num_tuples,
+					BlockNumber num_all_visible_pages,
+					TransactionId relfrozenxid,
+					TransactionId relminmxid)
+{
+	Oid		relid = RelationGetRelid(relation);
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_FIND, &found);
+
+	if (!found)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (num_pages >= 0 &&
+		gtt_rnode->relpages != (int32)num_pages)
+		gtt_rnode->relpages = (int32)num_pages;
+
+	if (num_tuples >= 0 &&
+		gtt_rnode->reltuples != (float4)num_tuples)
+		gtt_rnode->reltuples = (float4)num_tuples;
+
+	/* only heap contain transaction information and relallvisible */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (gtt_rnode->relallvisible >= 0 &&
+			gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+		{
+			gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+		}
+
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNewTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible) 
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (gtt_storage_local_hash == NULL)
+		return false;
+
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_FIND, &found);
+
+	if (!found)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	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->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	Assert(entry->relid == reloid);
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == 0)
+		{
+			entry->attnum[i] = attnum;
+			break;
+		}
+		else if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < entry->natts);
+	Assert(entry->att_stat_tups[i] == NULL);
+	entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+						(void *) &reloid, HASH_FIND, &found);
+
+	if (!found)
+		return NULL;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			return entry->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int			i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	HeapTuple	tuple;
+	Relation	rel = NULL;
+	int		attnum = PG_GETARG_INT32(1);
+	Oid		reloid = PG_GETARG_OID(0);
+	char		rel_persistence;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	  tupdesc;
+	MemoryContext oldcontext;
+	Tuplestorestate *tupstore;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+
+	relation_close(rel, NoLock);
+	relation_close(pg_tatistic, AccessShareLock);
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate	*tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Oid			relnode = 0;
+	Relation	rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[6];
+		bool	isnull[6];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(reloid);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(indexOid));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid relOid = index->rd_index->indrelid;
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	if (gtt_storage_attached(relOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int			i;
+	Oid			toastrelid;
+
+	if (operation != CMD_INSERT)
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+
+		index_build(relation, index, info, true, false);
+	}
+
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid 		indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo 	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+static void
+gtt_reset_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+
+		entry->attnum[i] = 0;
+	}
+
+	return;
+}
+
+static void
+gtt_free_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (entry->attnum)
+		pfree(entry->attnum);
+
+	if (entry->att_stat_tups)
+		pfree(entry->att_stat_tups);
+
+	return;
+}
+
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	if (gtt_storage_local_hash == NULL)
+		return InvalidOid;
+
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_FIND, &found);
+
+	if (!found)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 5a6dc61..4ee0e51 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM 
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 924ef37..db12eef 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -184,6 +185,13 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -586,14 +594,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 		 * pg_statistic for columns we didn't process, we leave them alone.)
 		 */
 		update_attstats(RelationGetRelid(onerel), inh,
-						attr_cnt, vacattrstats);
+						attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
 
 		for (ind = 0; ind < nindexes; ind++)
 		{
 			AnlIndexData *thisdata = &indexdata[ind];
 
 			update_attstats(RelationGetRelid(Irel[ind]), false,
-							thisdata->attr_cnt, thisdata->vacattrstats);
+							thisdata->attr_cnt, thisdata->vacattrstats,
+							RelationGetRelPersistence(Irel[ind]));
 		}
 
 		/*
@@ -1456,7 +1465,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
  *		by taking a self-exclusive lock on the relation in analyze_rel().
  */
 static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
 {
 	Relation	sd;
 	int			attno;
@@ -1558,31 +1567,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/* Update column statistic to localhash, not catalog */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fc1cea0..7059e96 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -391,6 +391,12 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/* not support cluster global temp table yet */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("not support cluster global temporary tables yet")));
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index fbde9f8..7974ea4 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -27,6 +27,7 @@
 #include "catalog/dependency.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -1063,7 +1064,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, stmt->filename, stmt->is_program,
@@ -2841,6 +2842,8 @@ CopyFrom(CopyState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	estate->es_result_relations = resultRelInfo;
 	estate->es_num_result_relations = 1;
 	estate->es_result_relation_info = resultRelInfo;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 4e8263a..1a4c6bc 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2343,7 +2343,9 @@ ReindexIndex(RangeVar *indexRelation, int options, bool concurrent)
 	persistence = irel->rd_rel->relpersistence;
 	index_close(irel, NoLock);
 
-	if (concurrent && persistence != RELPERSISTENCE_TEMP)
+	if (concurrent &&
+		persistence != RELPERSISTENCE_TEMP &&
+		persistence != RELPERSISTENCE_GLOBAL_TEMP)
 		ReindexRelationConcurrently(indOid, options);
 	else
 		reindex_index(indOid, false, persistence,
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index d8cafc4..deecde8 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -96,7 +96,8 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 	 * transaction.
 	 */
 	relpersistence = get_rel_persistence(relid);
-	if (relpersistence == RELPERSISTENCE_TEMP)
+	if (relpersistence == RELPERSISTENCE_TEMP ||
+		relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
 
 	/* Check permissions. */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..d7119b7 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -94,7 +96,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -222,7 +225,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* now initialize the sequence's data */
 	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -327,7 +330,7 @@ ResetSequence(Oid seq_relid)
 	/*
 	 * Insert the modified tuple into the new storage file.
 	 */
-	fill_seq_with_data(seq_rel, tuple);
+	fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -340,17 +343,21 @@ ResetSequence(Oid seq_relid)
  * Initialize a sequence's relation with the specified tuple as content
  */
 static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 	sequence_magic *sm;
 	OffsetNumber offnum;
+	bool lockBuffer = false;
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ReadBuffer(rel, P_NEW);
-	Assert(BufferGetBlockNumber(buf) == 0);
+	if (buf == InvalidBuffer)
+	{
+		buf = ReadBuffer(rel, P_NEW);
+		Assert(BufferGetBlockNumber(buf) == 0);
+		lockBuffer = true;
+	}
 
 	page = BufferGetPage(buf);
 
@@ -360,7 +367,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* Now insert sequence tuple */
 
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+	if (lockBuffer)
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +418,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	END_CRIT_SECTION();
 
-	UnlockReleaseBuffer(buf);
+	if (lockBuffer)
+		UnlockReleaseBuffer(buf);
 }
 
 /*
@@ -502,7 +511,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * Insert the modified tuple into the new storage file.
 		 */
-		fill_seq_with_data(seqrel, newdatatuple);
+		fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
 	}
 
 	/* process OWNED BY if given */
@@ -611,7 +620,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +945,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1154,6 +1163,13 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1954,3 +1970,46 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+void
+gtt_init_seq(Relation rel)
+{
+	/* Initialize sequence for global temporary tables */
+	Datum		value[SEQ_COL_LASTCOL] = {0};
+	bool		null[SEQ_COL_LASTCOL] = {false};
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8e35c5b..240612c 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -554,6 +555,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 								  Relation partitionTbl);
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static OnCommitAction gtt_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -599,6 +601,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -609,8 +612,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")));
@@ -640,7 +645,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	/* global temp table same as local temp table */
+	if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+		 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -741,6 +748,55 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* global temp table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* check parent table*/
+		if (inheritOids)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			table_close(relation, NoLock);
+		}
+
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temp table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1817,6 +1873,10 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
 		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 			continue;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			continue;
+
 		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
@@ -3568,6 +3628,14 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			elog(ERROR, "can not alter table %s when other backend attached this global temp table",
+						RelationGetRelationName(rel));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -8182,6 +8250,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -12689,6 +12763,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -12891,6 +12968,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temp table");
+
 	/*
 	 * No work if no change in tablespace.
 	 */
@@ -13263,7 +13343,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,
@@ -14670,7 +14750,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",
@@ -17263,3 +17345,28 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
 		table_close(rel, NoLock);
 	}
 }
+
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			if (defGetBoolean(def))
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 59731d6..33c02cc 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/vacuum.h"
@@ -1217,6 +1218,17 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	 /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		is_gtt = true;
+		up_gtt_relstats(relation,
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1230,17 +1242,26 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relpages = (int32) num_pages;
+	else if (pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (is_gtt)
+		relation->rd_rel->reltuples = (float4) num_tuples;
+	else if (pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	else if (pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1285,7 +1306,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1296,7 +1318,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1394,6 +1417,10 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/* global temp table relstats not in pg_class */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1451,6 +1478,25 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/*
+	 * Global temp table get frozenxid from MyProc
+	 * to avoid the vacuum truncate clog that gtt need.
+	 */
+	if (max_active_gtt > 0)
+	{
+		TransactionId oldest_gtt_frozenxid =
+			list_all_session_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid) &&
+			TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+		{
+			ereport(WARNING,
+				(errmsg("global temp table oldest FrozenXid is far in the past"),
+				 errhint("please truncate them or kill those sessions that use them.")));
+			newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1778,6 +1824,18 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		ereport(WARNING,
+				(errmsg("skipping vacuum global temp table \"%s\" because storage is not initialized for current session",
+						RelationGetRelationName(onerel))));
+		relation_close(onerel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6e65103..04706ee 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -530,6 +530,11 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 4fdffad..0aa7559 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index fb6ce49..06d9237 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
@@ -545,6 +546,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	init_gtt_storage(mtstate->operation, leaf_part_rri);
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d71c0a4..0900907 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,6 +42,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -2372,6 +2373,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			ExecOpenIndices(resultRelInfo,
 							node->onConflictAction != ONCONFLICT_NONE);
 
+		init_gtt_storage(operation, resultRelInfo);
+
 		/*
 		 * If this is an UPDATE and a BEFORE UPDATE trigger is present, the
 		 * trigger itself might modify the partition-key values. So arrange
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 905bbe7..170963d 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -591,6 +591,8 @@ static void
 set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 						  RangeTblEntry *rte)
 {
+	char relpersistence;
+
 	/*
 	 * The flag has previously been initialized to false, so we can just
 	 * return if it becomes clear that we can't safely set it.
@@ -618,7 +620,11 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			/* global temp table is same as local temp table */
+			relpersistence = get_rel_persistence(rte->relid);
+
+			if (relpersistence == RELPERSISTENCE_TEMP ||
+				relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b65abf6..9735ab3 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6305,7 +6305,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
+	/* global temp table is same as local temp table */
 	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index d82fc5a..c8f06eb 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -28,6 +28,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temp table */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6676412..0685c1c 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2585,6 +2585,11 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("materialized views must not use temporary tables or views")));
 
+		if (is_query_using_gtt(query))
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("materialized views must not use global temporary tables or views")));
+
 		/*
 		 * A materialized view would either need to save parameters for use in
 		 * maintaining/loading the data or prohibit them entirely.  The latter
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7e384f9..6ac4aa0 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3291,17 +3291,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; }
@@ -11613,19 +11607,13 @@ OptTempTableName:
 				}
 			| GLOBAL TEMPORARY opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED opt_table qualified_name
 				{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b875a50..123945b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index c191141..fe1c494 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index da75e75..f4b97e8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2087,6 +2087,11 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* autovacuum skip vacuum global temp table */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2153,7 +2158,9 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		/* autovacuum skip vacuum global temp table */
+		if (classForm->relpersistence == RELPERSISTENCE_TEMP || 
+			classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index e05e2b3..b45904c 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -52,6 +53,7 @@
 #include "utils/rel.h"
 #include "utils/resowner_private.h"
 #include "utils/timestamp.h"
+#include "utils/guc.h"
 
 
 /* Note: these two macros only work on shared buffers, not local ones! */
@@ -2756,6 +2758,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d5..7adb969 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -21,6 +21,7 @@
 #include "access/nbtree.h"
 #include "access/subtrans.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, active_gtt_shared_hash_size());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* global temporary table */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index f45a619..cf8b20a 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -63,6 +63,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -4088,3 +4089,77 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temp table.
+ */
+int
+list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct *arrayP = procArray;
+	TransactionId result = InvalidTransactionId;
+	int			index;
+	int			flags = 0;
+	int			i = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+		*n = 0;
+	}
+
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		volatile PGXACT *pgxact = &allPgXact[pgprocno];
+
+		if (pgxact->vacuumFlags & flags)
+			continue;
+
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->session_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->session_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->session_gtt_frozenxid, result))
+				result = proc->session_gtt_frozenxid;
+
+			if (max_size > 0)
+			{
+				pids[i] = proc->pid;
+				xids[i] = proc->session_gtt_frozenxid;
+				i++;
+			}
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (max_size > 0)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 9938cdd..231750b 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 8406644..0d99321 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -1008,6 +1008,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 8339f4c..d41c05d 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -112,6 +112,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -4571,12 +4572,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))
 							{
@@ -4701,15 +4715,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))
 		{
@@ -6091,6 +6117,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;
@@ -6108,6 +6135,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,
@@ -6119,6 +6153,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;
@@ -6134,6 +6170,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,
@@ -7047,6 +7090,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))
@@ -7059,6 +7104,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 =
@@ -7071,6 +7124,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.
@@ -7090,6 +7145,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 27bbb58..9b5e719 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -2904,6 +2905,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 76f41db..cb8d121 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -63,6 +63,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1127,6 +1128,24 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1181,6 +1200,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			gtt_fix_index_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -1303,7 +1323,17 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+			if (newrelnode != InvalidOid &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2220,6 +2250,8 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		gtt_fix_index_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3313,6 +3345,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3420,28 +3456,34 @@ void
 RelationSetNewRelfilenode(Relation relation, char persistence)
 {
 	Oid			newrelfilenode;
-	Relation	pg_class;
-	HeapTuple	tuple;
+	Relation	pg_class = NULL;
+	HeapTuple	tuple = NULL;
 	Form_pg_class classform;
 	MultiXactId minmulti = InvalidMultiXactId;
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
+	bool		modify_pg_class = !RELATION_IS_GLOBAL_TEMP(relation);
 
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
 
-	/*
-	 * Get a writable copy of the pg_class tuple for the given relation.
-	 */
-	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+	if (modify_pg_class)
+	{
+		/*
+		 * Get a writable copy of the pg_class tuple for the given relation.
+		 */
+		pg_class = table_open(RelationRelationId, RowExclusiveLock);
 
-	tuple = SearchSysCacheCopy1(RELOID,
-								ObjectIdGetDatum(RelationGetRelid(relation)));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "could not find tuple for relation %u",
-			 RelationGetRelid(relation));
-	classform = (Form_pg_class) GETSTRUCT(tuple);
+		tuple = SearchSysCacheCopy1(RELOID,
+									ObjectIdGetDatum(RelationGetRelid(relation)));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u",
+				 RelationGetRelid(relation));
+		classform = (Form_pg_class) GETSTRUCT(tuple);
+	}
+	else
+		memset(&classform, 0, sizeof(classform));
 
 	/*
 	 * Schedule unlinking of the old storage at transaction commit.
@@ -3467,7 +3509,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;
@@ -3487,6 +3529,15 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 			break;
 	}
 
+	if (!modify_pg_class)
+	{
+		Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+		Assert(RELATION_IS_GLOBAL_TEMP(relation));
+		Assert(!RelationIsMapped(relation));
+		relation->rd_node.relNode = relnode;
+		CacheInvalidateRelcache(relation);
+	}
 	/*
 	 * If we're dealing with a mapped index, pg_class.relfilenode doesn't
 	 * change; instead we have to send the update to the relation mapper.
@@ -3496,7 +3547,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	 * possibly-inaccurate values of relpages etc, but those will be fixed up
 	 * later.
 	 */
-	if (RelationIsMapped(relation))
+	else if (RelationIsMapped(relation))
 	{
 		/* This case is only supported for indexes */
 		Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3542,9 +3593,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 		CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 	}
 
-	heap_freetuple(tuple);
+	if (modify_pg_class)
+	{
+		heap_freetuple(tuple);
 
-	table_close(pg_class, RowExclusiveLock);
+		table_close(pg_class, RowExclusiveLock);
+	}
 
 	/*
 	 * Make the pg_class row change or relation map change visible.  This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index af876d1..aed539c 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -141,6 +141,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,
@@ -2068,6 +2080,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 1849dfe..f097e4f 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15756,6 +15756,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
 		char	   *foreign = "";
+		char	   *table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15809,9 +15810,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 			binary_upgrade_set_pg_class_oids(fout, q,
 											 tbinfo->dobj.catId.oid, false);
 
+		if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED)
+			table_type = "UNLOGGED ";
+		else if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			table_type = "GLOBAL TEMPORARY ";
+		else
+			table_type = "";
+
 		appendPQExpBuffer(q, "CREATE %s%s %s",
-						  tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ?
-						  "UNLOGGED " : "",
+						  table_type,
 						  reltypename,
 						  qualrelname);
 
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 00aef85..9ad71c8 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -84,7 +84,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -157,7 +157,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 7e524ea..371168f 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -441,22 +443,46 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 	 * output, so we have to copy that system table.  It's easiest to do that
 	 * by treating it as a user table.
 	 */
-	snprintf(query + strlen(query), sizeof(query) - strlen(query),
-			 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
-			 "  SELECT c.oid, 0::oid, 0::oid "
-			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
-			 "         ON c.relnamespace = n.oid "
-			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
-	/* exclude possible orphaned temp tables */
-			 "    ((n.nspname !~ '^pg_temp_' AND "
-			 "      n.nspname !~ '^pg_toast_temp_' AND "
-			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
-			 "                        'binary_upgrade', 'pg_toast') AND "
-			 "      c.oid >= %u::pg_catalog.oid) OR "
-			 "     (n.nspname = 'pg_catalog' AND "
-			 "      relname IN ('pg_largeobject') ))), ",
-			 FirstNormalObjectId);
+	if (skip_gtt)
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude global temp tables */
+				 "    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
+	else
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
 
 	/*
 	 * Add a CTE that collects OIDs of toast tables belonging to the tables
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 70194eb..8dd9e66 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -411,7 +411,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -642,7 +642,9 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -653,7 +655,9 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 8b90cef..d155205 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -389,7 +389,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 109245f..557cce1 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3724,7 +3724,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ae35fa4..4486f89 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1023,6 +1023,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2386,6 +2388,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2594,6 +2599,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 78b33b2..4be89f6 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -168,6 +168,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 87d25d4..a32ad0b 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5507,6 +5507,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4388',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4389',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4390',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4391',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 048003c..af48cdf 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -19,7 +19,7 @@
 #include "storage/smgr.h"
 #include "utils/relcache.h"
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationTruncate(Relation rel, BlockNumber nblocks);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..89a1b51
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,44 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Relation relation,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void gtt_force_enable_index(Relation index);
+extern void gtt_fix_index_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index e2638ab..89a5ce4 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 3f88683..f870e9a 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 8fda8e4..172eae9 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_TBM,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_SXACT,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index d217801..8adde87 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -117,6 +117,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId session_gtt_frozenxid;	/* session level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ce93ace..0f7262e 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -281,6 +281,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 39cdcdd..7c93d1e 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	struct SMgrRelationData *rd_smgr;	/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -282,6 +282,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
@@ -540,11 +541,13 @@ typedef struct ViewOptions
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -552,6 +555,7 @@ typedef struct ViewOptions
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -564,6 +568,14 @@ typedef struct ViewOptions
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is either local temp relation of this session
+ * 		or global temp relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -607,6 +619,17 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..ca2d135
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,14 @@
+reset search_path;
+select pg_sleep(5);
+ pg_sleep 
+----------
+ 
+(1 row)
+
+drop schema gtt cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
+drop cascades to table gtt.gtt_with_seq
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..2baa395
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,363 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+ n 
+---
+ 9
+(1 row)
+
+commit;
+-- 0 row
+select * from gtt6;
+ n 
+---
+(0 rows)
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+ERROR:  cannot create indexes on global temporary tables using concurrent mode
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+ERROR:  not support cluster global temporary tables yet
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temp table
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  could not create global temporary table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test;
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR:  insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count 
+-------
+     1
+(1 row)
+
+select count(*) from orders;
+ count 
+-------
+     0
+(1 row)
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  2 |  3
+(2 rows)
+
+commit;
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+select * from gtt_s_2 order by c1;
+ c1 | c2 
+----+----
+  1 |  2
+  2 |  4
+(2 rows)
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 1)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 300)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 23 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.gtt6
+drop cascades to table gtt_function.foo
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to sequence gtt_function.seq_1
+drop cascades to table gtt_function.gtt_s_1
+drop cascades to table gtt_function.gtt_s_2
+drop cascades to table gtt_function.gt1
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..0646aae
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,90 @@
+set search_path=gtt,sys;
+select nextval('gtt_with_seq_c2_seq');
+ nextval 
+---------
+       1
+(1 row)
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..de2373c
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,287 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+rollback;
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+commit;
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          114688 |                 614400
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |            49152 |         49152 |                  49152
+(2 rows)
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+ c1 | c2 
+----+----
+  1 |  1
+(1 row)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..8c0c376
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,10 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..4420fdb
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,80 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+ gtt_stats  | gtt_pkey  |        1 |         0 |             0
+(2 rows)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+reindex table gtt;
+reindex index gtt_pkey;
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+ gtt_stats  | gtt_pkey  |       30 |     10000 |             0
+(2 rows)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index a2077bb..2cf9c3c 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1349,6 +1349,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d2b17dd..80e577f 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..2c8e586
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,8 @@
+
+
+reset search_path;
+
+select pg_sleep(5);
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..af2bb20
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,239 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+commit;
+-- 0 row
+select * from gtt6;
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test;
+
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+commit;
+select * from gtt_s_1 order by c1;
+select * from gtt_s_2 order by c1;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d05745e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,44 @@
+
+
+set search_path=gtt,sys;
+
+select nextval('gtt_with_seq_c2_seq');
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..3ac81a1
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,123 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+rollback;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+commit;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..dbe84d1
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,19 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..d61b0ff
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,46 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+reindex table gtt;
+
+reindex index gtt_pkey;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#214tushar
tushar.ahuja@enterprisedb.com
In reply to: 曾文旌 (#213)
Re: [Proposal] Global temporary tables

On 3/27/20 10:55 AM, 曾文旌 wrote:

Hi Wenjing,
This patch(gtt_v21_pg13.patch) is not applicable on PG HEAD, I hope
you have prepared the patch on top of some previous commit.
Could you please rebase the patch which we can apply on HEAD ?

Yes, It looks like the built-in functions are in conflict with new code.

This error message looks wrong  to me-

postgres=# reindex table concurrently t ;
ERROR:  cannot create indexes on global temporary tables using
concurrent mode
postgres=#

Better message would be-

ERROR:  cannot reindex global temporary tables concurrently

--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company

#215tushar
tushar.ahuja@enterprisedb.com
In reply to: 曾文旌 (#213)
Re: [Proposal] Global temporary tables

On 3/27/20 10:55 AM, 曾文旌 wrote:

Hi Wenjing,
This patch(gtt_v21_pg13.patch) is not applicable on PG HEAD, I hope
you have prepared the patch on top of some previous commit.
Could you please rebase the patch which we can apply on HEAD ?

Yes, It looks like the built-in functions are in conflict with new code.

In this below scenario, pg_dump is failing -

test=# CREATE database foo;
CREATE DATABASE
test=# \c foo
You are now connected to database "foo" as user "tushar".
foo=# CREATE GLOBAL TEMPORARY TABLE bar(c1 bigint, c2 bigserial) on
commit PRESERVE rows;
CREATE TABLE
foo=# \q

[tushar@localhost bin]$ ./pg_dump -Fp foo > /tmp/rf2
pg_dump: error: query to get data of sequence "bar_c2_seq" returned 0
rows (expected 1)
[tushar@localhost bin]$

--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company

#216曾文旌
wenjing.zwj@alibaba-inc.com
In reply to: tushar (#215)
2 attachment(s)
Re: [Proposal] Global temporary tables

2020年3月27日 下午6:06,tushar <tushar.ahuja@enterprisedb.com> 写道:

On 3/27/20 10:55 AM, 曾文旌 wrote:

Hi Wenjing,
This patch(gtt_v21_pg13.patch) is not applicable on PG HEAD, I hope you have prepared the patch on top of some previous commit.
Could you please rebase the patch which we can apply on HEAD ?

Yes, It looks like the built-in functions are in conflict with new code.

In this below scenario, pg_dump is failing -

test=# CREATE database foo;
CREATE DATABASE
test=# \c foo
You are now connected to database "foo" as user "tushar".
foo=# CREATE GLOBAL TEMPORARY TABLE bar(c1 bigint, c2 bigserial) on commit PRESERVE rows;
CREATE TABLE
foo=# \q

[tushar@localhost bin]$ ./pg_dump -Fp foo > /tmp/rf2
pg_dump: error: query to get data of sequence "bar_c2_seq" returned 0 rows (expected 1)
[tushar@localhost bin]$

Thanks for review
Fixed in global_temporary_table_v23-pg13.patch

Wenjing

Show quoted text

--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/ <https://www.enterprisedb.com/&gt;
The Enterprise PostgreSQL Company

Attachments:

global_temporary_table_v23-pg13.patchapplication/octet-stream; name=global_temporary_table_v23-pg13.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 8ccc228..b061de9 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -168,6 +168,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * For global temp table only
+	 * use ShareUpdateExclusiveLock for ensure safety
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temp table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},	
 	/* list terminator */
 	{{NULL}}
 };
@@ -1817,6 +1830,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1961,13 +1976,18 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	/*
 	 * There are no options for partitioned tables yet, but this is able to do
 	 * some validation.
 	 */
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index dd975b1..1610e7d 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1013,7 +1013,9 @@ gistGetFakeLSN(Relation rel)
 {
 	static XLogRecPtr counter = FirstNormalUnloggedLSN;
 
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	/* global temp is same as local temp table */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 3ec6d52..1222594 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -150,7 +150,9 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	/* global temp table is same as local temp table */
+	if (!(index->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		index->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ca52846..f0153cf 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -598,7 +598,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -651,7 +651,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index af322c1..0734ea0 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -62,6 +62,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -420,9 +421,9 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
 	Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
-	/* not every AM requires these to be valid, but heap does */
-	Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
-	Assert(MultiXactIdIsValid(onerel->rd_rel->relminmxid));
+	/* not every AM requires these to be valid, but regular heap does */
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ MultiXactIdIsValid(onerel->rd_rel->relminmxid));
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 39b8f17..abb76dc 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -601,6 +602,10 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 1951103..fa91a1c 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6429,6 +6429,10 @@ StartupXLOG(void)
 	else
 		recoveryTargetTLI = ControlFile->checkPointCopy.ThisTimeLineID;
 
+	/* clean temp relation files */
+	if (max_active_gtt > 0)
+		RemovePgTempFiles();
+
 	/*
 	 * Check for signal files, and if so set up state for offline recovery
 	 */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 9499bb3..ae47364 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -43,6 +43,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 7d6acae..3be8d63 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -393,7 +393,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/* global temp table is same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 632c058..01cf990 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -427,7 +429,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -959,6 +961,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -997,8 +1000,18 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/* global temp table not remember transaction info in catalog */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1360,6 +1373,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1940,6 +1954,14 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/* We allow to drop global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			elog(ERROR, "can not drop relation %s when other backend attached this global temp table",
+						RelationGetRelationName(rel));
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3167,7 +3189,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3179,7 +3201,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3225,8 +3247,13 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		/* truncate global temp table only need RowExclusiveLock */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3259,6 +3286,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3267,23 +3295,37 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (!gtt_storage_attached(RelationGetRelid(rel)))
+			return;
+
+		/*
+		 * Truncate global temp table only need RowExclusiveLock
+		 */
+		lockmode = RowExclusiveLock;
+	}
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(rel, 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index bd7ec92..afbb655 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -53,6 +53,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -881,6 +882,19 @@ index_create(Relation heapRelation,
 						indexRelationName, RelationGetRelationName(heapRelation))));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temp table use concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot reindex global temporary tables concurrently")));
+
+		/* if global temp table not init storage, then skip build index */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -2034,6 +2048,7 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 				indexrelid;
 	LOCKTAG		heaplocktag;
 	LOCKMODE	lockmode;
+	char		rel_persistence;
 
 	/*
 	 * A temporary relation uses a non-concurrent DROP.  Other backends can't
@@ -2041,7 +2056,8 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 * lock (see comments in RemoveRelations), and a non-concurrent DROP is
 	 * more efficient.
 	 */
-	Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+	rel_persistence = get_rel_persistence(indexId);
+	Assert(!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP) ||
 		   (!concurrent && !concurrent_lock_mode));
 
 	/*
@@ -2073,6 +2089,14 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/* We allow to drop index on global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+			elog(ERROR, "can not drop index %s when other backend attached this global temp table.",
+						RelationGetRelationName(userHeapRelation));
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2681,6 +2705,11 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	/* update index stats into localhash and rel_rd_rel for global temp table */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		is_gtt = true;
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2766,21 +2795,35 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
+		if (is_gtt)
+			rel->rd_rel->relpages = (int32) relpages;
+		else if (rd_rel->relpages != (int32) relpages)
 		{
 			rd_rel->relpages = (int32) relpages;
 			dirty = true;
 		}
-		if (rd_rel->reltuples != (float4) reltuples)
+
+		if (is_gtt)
+			rel->rd_rel->reltuples = (float4) reltuples;
+		else if (rd_rel->reltuples != (float4) reltuples)
 		{
 			rd_rel->reltuples = (float4) reltuples;
 			dirty = true;
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+
+		if (is_gtt)
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+		else if (rd_rel->relallvisible != (int32) relallvisible)
 		{
 			rd_rel->relallvisible = (int32) relallvisible;
 			dirty = true;
 		}
+
+		if (is_gtt)
+		{
+			up_gtt_relstats(rel, relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
+		}
 	}
 
 	/*
@@ -2894,6 +2937,15 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, index, val);
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			gtt_force_enable_index(indexRelation);
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3442,6 +3494,10 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	PGRUsage	ru0;
 	bool		progress = (options & REINDEXOPT_REPORT_PROGRESS) != 0;
 
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		!gtt_storage_attached(indexId))
+		return;
+
 	pg_rusage_init(&ru0);
 
 	/*
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 2ec2301..1b6061d 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -655,6 +655,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		/* global temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp relations in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index fddfbf1..c3661e3 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -26,6 +26,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -56,6 +57,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			relOid;			/* InvalidOid if not a global temp rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -75,7 +77,7 @@ static PendingRelDelete *pendingDeletes = NULL; /* head of linked list */
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -85,6 +87,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;
@@ -112,11 +116,19 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->relOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/* remember global temp table storage info to localhash */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel)
+	{
+		pending->relOid = RelationGetRelid(rel);
+		remember_gtt_storage_info(rnode, rel);
+	}
+
 	return srel;
 }
 
@@ -153,11 +165,15 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->relOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->relOid = RelationGetRelid(rel);
+
 	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
@@ -437,6 +453,7 @@ smgrDoPendingDeletes(bool isCommit)
 				i = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -466,14 +483,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->relOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -486,9 +507,18 @@ smgrDoPendingDeletes(bool isCommit)
 		smgrdounlinkall(srels, nrels, false);
 
 		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* clean global temp table flags when transaction commit or rollback */
+			if (SmgrIsTemp(srels[i]) &&
+				reloids[i] != InvalidOid &&
+				gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..5bf3bb3
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1427 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  code to create and destroy physical storage for global temparary table
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_relstats_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+typedef struct gtt_ctl_data
+{
+	LWLock			lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+	Oid			relfilenode;
+	Oid			spcnode;
+
+	/* pg_class stat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+
+	/* pg_statistic */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_reset_statistics(gtt_local_hash_entry *entry);
+static void gtt_free_statistics(gtt_local_hash_entry *entry);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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();
+	}
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(gtt_fnode);
+	info.entrysize = action_gtt_shared_hash_entry_size();
+	active_gtt_shared_hash =
+		ShmemInitHash("active gtt shared hash",
+						gtt_shared_ctl->max_entry,
+						gtt_shared_ctl->max_entry,
+						&info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			found;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *)&(fnode), 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_global_temporary_table.")));
+	}
+
+	if (found == false)
+	{
+		int			wordnum;
+
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode				fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	if (!skiplock)
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		if (!skiplock)
+			LWLockRelease(&gtt_shared_ctl->lock);
+
+		if (isCommit)
+			elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+	bms_del_member(entry->map, MyBackendId);
+
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+
+	if (!skiplock)
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+	return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	Bitmapset	*map_copy = NULL;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool		in_use = false;
+	int			num_use = 0;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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;
+	gtt_relfilenode			*new_node = NULL;
+	Oid						relid = RelationGetRelid(rel);
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		/* First time through: initialize the hash table */
+		HASHCTL		ctl;
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_relstats_context =
+			AllocSetContextCreate(CacheMemoryContext,
+							"gtt relstats context",
+							ALLOCSET_DEFAULT_SIZES);
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+	if (!entry)
+	{
+		bool		found = false;
+		int 		natts = 0;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash,
+						(void *) &relid, HASH_ENTER, &found);
+
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+		entry->natts = 0;
+		entry->attnum = NULL;
+		entry->att_stat_tups = NULL;
+
+		natts = RelationGetNumberOfAttributes(rel);
+		entry->attnum = palloc0(sizeof(int) * natts);
+		entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+		entry->natts = natts;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			gtt_storage_checkin(relid);
+		}
+	}
+
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* only heap contain transaction information */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	gtt_reset_statistics(entry);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	ListCell		*lc;
+	gtt_relfilenode *d_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (!entry)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+		return;
+	}
+
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == rnode.relNode &&
+			gtt_rnode->spcnode == rnode.spcNode)
+		{
+			d_rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+
+		if (entry->relfilenode_list == NIL)
+		{
+			if (entry->relkind == RELKIND_RELATION ||
+				entry->relkind == RELKIND_SEQUENCE)
+				gtt_storage_checkout(relid, false, isCommit);
+
+			gtt_free_statistics(entry);
+			hash_search(gtt_storage_local_hash,
+					(void *) &(relid), HASH_REMOVE, NULL);
+		}
+
+		return;
+	}
+
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	pfree(d_rnode);
+	if (entry->relfilenode_list == NIL)
+	{
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(relid, false, isCommit);
+
+		gtt_free_statistics(entry);
+		hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_REMOVE, NULL);
+	}
+	else
+		gtt_reset_statistics(entry);
+
+	return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool found;
+
+	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;
+	SMgrRelation	*srels = NULL;
+	Oid				*relids = NULL;
+	char			*relkinds = NULL;
+	int			nrels = 0,
+				nfiles = 0,
+				maxrels = 0,
+				maxfiles = 0,
+				i = 0;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel;
+			RelFileNode 	rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel = smgropen(rnode, MyBackendId);
+
+			if (maxfiles == 0)
+			{
+				maxfiles = 32;
+				srels = palloc(sizeof(SMgrRelation) * maxfiles);
+			}
+			else if (maxfiles <= nfiles)
+			{
+				maxfiles *= 2;
+				srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+			}
+
+			srels[nfiles++] = srel;
+		}
+
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			relids  = palloc(sizeof(Oid) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			relids  = repalloc(relids , sizeof(Oid) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		relkinds[nrels] = entry->relkind;
+		relids[nrels] = entry->relid;
+		nrels++;
+	}
+
+	if (nfiles > 0)
+	{
+		smgrdounlinkall(srels, nfiles, false);
+		for (i = 0; i < nfiles; i++)
+			smgrclose(srels[i]);
+
+		pfree(srels);
+	}
+
+	if (nrels)
+	{
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+		for (i = 0; i < nrels; i++)
+		{
+			if (relkinds[i] == RELKIND_RELATION ||
+				relkinds[i] == RELKIND_SEQUENCE)
+				gtt_storage_checkout(relids[i], true, false);
+		}
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+		pfree(relids);
+		pfree(relkinds);
+	}
+
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+	return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible) 
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+					BlockNumber num_pages,
+					double num_tuples,
+					BlockNumber num_all_visible_pages,
+					TransactionId relfrozenxid,
+					TransactionId relminmxid)
+{
+	Oid		relid = RelationGetRelid(relation);
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_FIND, &found);
+
+	if (!found)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (num_pages >= 0 &&
+		gtt_rnode->relpages != (int32)num_pages)
+		gtt_rnode->relpages = (int32)num_pages;
+
+	if (num_tuples >= 0 &&
+		gtt_rnode->reltuples != (float4)num_tuples)
+		gtt_rnode->reltuples = (float4)num_tuples;
+
+	/* only heap contain transaction information and relallvisible */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (gtt_rnode->relallvisible >= 0 &&
+			gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+		{
+			gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+		}
+
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNewTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible) 
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (gtt_storage_local_hash == NULL)
+		return false;
+
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_FIND, &found);
+
+	if (!found)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	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->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	Assert(entry->relid == reloid);
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == 0)
+		{
+			entry->attnum[i] = attnum;
+			break;
+		}
+		else if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < entry->natts);
+	Assert(entry->att_stat_tups[i] == NULL);
+	entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+						(void *) &reloid, HASH_FIND, &found);
+
+	if (!found)
+		return NULL;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			return entry->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int			i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	HeapTuple	tuple;
+	Relation	rel = NULL;
+	int		attnum = PG_GETARG_INT32(1);
+	Oid		reloid = PG_GETARG_OID(0);
+	char		rel_persistence;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	  tupdesc;
+	MemoryContext oldcontext;
+	Tuplestorestate *tupstore;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+
+	relation_close(rel, NoLock);
+	relation_close(pg_tatistic, AccessShareLock);
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate	*tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Oid			relnode = 0;
+	Relation	rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[6];
+		bool	isnull[6];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(reloid);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(indexOid));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid relOid = index->rd_index->indrelid;
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	if (gtt_storage_attached(relOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int			i;
+	Oid			toastrelid;
+
+	if (operation != CMD_INSERT)
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+
+		index_build(relation, index, info, true, false);
+	}
+
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid 		indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo 	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+static void
+gtt_reset_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+
+		entry->attnum[i] = 0;
+	}
+
+	return;
+}
+
+static void
+gtt_free_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (entry->attnum)
+		pfree(entry->attnum);
+
+	if (entry->att_stat_tups)
+		pfree(entry->att_stat_tups);
+
+	return;
+}
+
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	if (gtt_storage_local_hash == NULL)
+		return InvalidOid;
+
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_FIND, &found);
+
+	if (!found)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 83d00c6..a294f6f 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM 
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 924ef37..db12eef 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -184,6 +185,13 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -586,14 +594,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 		 * pg_statistic for columns we didn't process, we leave them alone.)
 		 */
 		update_attstats(RelationGetRelid(onerel), inh,
-						attr_cnt, vacattrstats);
+						attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
 
 		for (ind = 0; ind < nindexes; ind++)
 		{
 			AnlIndexData *thisdata = &indexdata[ind];
 
 			update_attstats(RelationGetRelid(Irel[ind]), false,
-							thisdata->attr_cnt, thisdata->vacattrstats);
+							thisdata->attr_cnt, thisdata->vacattrstats,
+							RelationGetRelPersistence(Irel[ind]));
 		}
 
 		/*
@@ -1456,7 +1465,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
  *		by taking a self-exclusive lock on the relation in analyze_rel().
  */
 static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
 {
 	Relation	sd;
 	int			attno;
@@ -1558,31 +1567,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/* Update column statistic to localhash, not catalog */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fc1cea0..7059e96 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -391,6 +391,12 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/* not support cluster global temp table yet */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("not support cluster global temporary tables yet")));
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index efb1e0d..66221ee 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -27,6 +27,7 @@
 #include "catalog/dependency.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -1063,7 +1064,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, stmt->filename, stmt->is_program,
@@ -2842,6 +2843,8 @@ CopyFrom(CopyState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	estate->es_result_relations = resultRelInfo;
 	estate->es_num_result_relations = 1;
 	estate->es_result_relation_info = resultRelInfo;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 2e5997b..0a99f91 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -521,6 +521,7 @@ DefineIndex(Oid relationId,
 	Snapshot	snapshot;
 	int			save_nestlevel = -1;
 	int			i;
+	char		rel_persistence;
 
 	/*
 	 * Some callers need us to run with an empty default_tablespace; this is a
@@ -542,7 +543,9 @@ DefineIndex(Oid relationId,
 	 * is more efficient.  Do this before any use of the concurrent option is
 	 * done.
 	 */
-	if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+	rel_persistence = get_rel_persistence(relationId);
+	if (stmt->concurrent &&
+		!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		concurrent = true;
 	else
 		concurrent = false;
@@ -2406,7 +2409,8 @@ ReindexIndex(RangeVar *indexRelation, int options, bool concurrent)
 	persistence = irel->rd_rel->relpersistence;
 	index_close(irel, NoLock);
 
-	if (concurrent && persistence != RELPERSISTENCE_TEMP)
+	if (concurrent &&
+		!(persistence == RELPERSISTENCE_TEMP || persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		ReindexRelationConcurrently(indOid, options);
 	else
 		reindex_index(indOid, false, persistence,
@@ -2492,6 +2496,7 @@ ReindexTable(RangeVar *relation, int options, bool concurrent)
 {
 	Oid			heapOid;
 	bool		result;
+	char		rel_persistence;
 
 	/*
 	 * The lock level used here should match reindex_relation().
@@ -2506,7 +2511,9 @@ ReindexTable(RangeVar *relation, int options, bool concurrent)
 									   0,
 									   RangeVarCallbackOwnsTable, NULL);
 
-	if (concurrent && get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+	rel_persistence = get_rel_persistence(heapOid);
+	if (concurrent &&
+		!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 	{
 		result = ReindexRelationConcurrently(heapOid, options);
 
@@ -2707,12 +2714,15 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 	foreach(l, relids)
 	{
 		Oid			relid = lfirst_oid(l);
+		char		rel_persistence;
 
 		StartTransactionCommand();
 		/* functions in indexes may want a snapshot set */
 		PushActiveSnapshot(GetTransactionSnapshot());
 
-		if (concurrent && get_rel_persistence(relid) != RELPERSISTENCE_TEMP)
+		rel_persistence = get_rel_persistence(relid);
+		if (concurrent &&
+			!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		{
 			(void) ReindexRelationConcurrently(relid, options);
 			/* ReindexRelationConcurrently() does the verbose output */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..a45863a 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -94,7 +96,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -222,7 +225,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* now initialize the sequence's data */
 	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -327,7 +330,7 @@ ResetSequence(Oid seq_relid)
 	/*
 	 * Insert the modified tuple into the new storage file.
 	 */
-	fill_seq_with_data(seq_rel, tuple);
+	fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -340,17 +343,21 @@ ResetSequence(Oid seq_relid)
  * Initialize a sequence's relation with the specified tuple as content
  */
 static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 	sequence_magic *sm;
 	OffsetNumber offnum;
+	bool lockBuffer = false;
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ReadBuffer(rel, P_NEW);
-	Assert(BufferGetBlockNumber(buf) == 0);
+	if (buf == InvalidBuffer)
+	{
+		buf = ReadBuffer(rel, P_NEW);
+		Assert(BufferGetBlockNumber(buf) == 0);
+		lockBuffer = true;
+	}
 
 	page = BufferGetPage(buf);
 
@@ -360,7 +367,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* Now insert sequence tuple */
 
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+	if (lockBuffer)
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +418,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	END_CRIT_SECTION();
 
-	UnlockReleaseBuffer(buf);
+	if (lockBuffer)
+		UnlockReleaseBuffer(buf);
 }
 
 /*
@@ -451,6 +460,13 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 
 	init_sequence(relid, &elm, &seqrel);
 
+	if (RELATION_IS_GLOBAL_TEMP(seqrel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(seqrel)))
+			elog(ERROR, "cannot alter global temp sequence %s when other backend attached it",
+						RelationGetRelationName(seqrel));
+	}
+
 	rel = table_open(SequenceRelationId, RowExclusiveLock);
 	seqtuple = SearchSysCacheCopy1(SEQRELID,
 								   ObjectIdGetDatum(relid));
@@ -502,7 +518,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * Insert the modified tuple into the new storage file.
 		 */
-		fill_seq_with_data(seqrel, newdatatuple);
+		fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
 	}
 
 	/* process OWNED BY if given */
@@ -611,7 +627,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +952,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1154,6 +1170,13 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1954,3 +1977,46 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+void
+gtt_init_seq(Relation rel)
+{
+	/* Initialize sequence for global temporary tables */
+	Datum		value[SEQ_COL_LASTCOL] = {0};
+	bool		null[SEQ_COL_LASTCOL] = {false};
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index c8c88be..95a735b 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -554,6 +555,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 								  Relation partitionTbl);
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static OnCommitAction gtt_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -599,6 +601,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -609,8 +612,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")));
@@ -640,7 +645,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	/* global temp table same as local temp table */
+	if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+		 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -741,6 +748,55 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* global temp table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* check parent table*/
+		if (inheritOids)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			table_close(relation, NoLock);
+		}
+
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temp table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1328,6 +1384,7 @@ RemoveRelations(DropStmt *drop)
 		Oid			relOid;
 		ObjectAddress obj;
 		struct DropRelationCallbackState state;
+		char		rel_persistence;
 
 		/*
 		 * These next few steps are a great deal like relation_openrv, but we
@@ -1361,8 +1418,9 @@ RemoveRelations(DropStmt *drop)
 		 * Decide if concurrent mode needs to be used here or not.  The
 		 * relation persistence cannot be known without its OID.
 		 */
+		rel_persistence = get_rel_persistence(relOid);
 		if (drop->concurrent &&
-			get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+			!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		{
 			Assert(list_length(drop->objects) == 1 &&
 				   drop->removeType == OBJECT_INDEX);
@@ -1817,6 +1875,10 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
 		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 			continue;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			continue;
+
 		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
@@ -3568,6 +3630,14 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			elog(ERROR, "can not alter table %s when other backend attached this global temp table",
+						RelationGetRelationName(rel));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -8182,6 +8252,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -12689,6 +12765,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -12891,6 +12970,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temp table");
+
 	/*
 	 * No work if no change in tablespace.
 	 */
@@ -13263,7 +13345,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,
@@ -14670,7 +14752,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",
@@ -17263,3 +17347,28 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
 		table_close(rel, NoLock);
 	}
 }
+
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			if (defGetBoolean(def))
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 59731d6..33c02cc 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/vacuum.h"
@@ -1217,6 +1218,17 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	 /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		is_gtt = true;
+		up_gtt_relstats(relation,
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1230,17 +1242,26 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relpages = (int32) num_pages;
+	else if (pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (is_gtt)
+		relation->rd_rel->reltuples = (float4) num_tuples;
+	else if (pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	else if (pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1285,7 +1306,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1296,7 +1318,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1394,6 +1417,10 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/* global temp table relstats not in pg_class */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1451,6 +1478,25 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/*
+	 * Global temp table get frozenxid from MyProc
+	 * to avoid the vacuum truncate clog that gtt need.
+	 */
+	if (max_active_gtt > 0)
+	{
+		TransactionId oldest_gtt_frozenxid =
+			list_all_session_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid) &&
+			TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+		{
+			ereport(WARNING,
+				(errmsg("global temp table oldest FrozenXid is far in the past"),
+				 errhint("please truncate them or kill those sessions that use them.")));
+			newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1778,6 +1824,18 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		ereport(WARNING,
+				(errmsg("skipping vacuum global temp table \"%s\" because storage is not initialized for current session",
+						RelationGetRelationName(onerel))));
+		relation_close(onerel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6e65103..04706ee 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -530,6 +530,11 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 4fdffad..0aa7559 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index fb6ce49..06d9237 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
@@ -545,6 +546,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	init_gtt_storage(mtstate->operation, leaf_part_rri);
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d71c0a4..0900907 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,6 +42,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -2372,6 +2373,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			ExecOpenIndices(resultRelInfo,
 							node->onConflictAction != ONCONFLICT_NONE);
 
+		init_gtt_storage(operation, resultRelInfo);
+
 		/*
 		 * If this is an UPDATE and a BEFORE UPDATE trigger is present, the
 		 * trigger itself might modify the partition-key values. So arrange
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 905bbe7..170963d 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -591,6 +591,8 @@ static void
 set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 						  RangeTblEntry *rte)
 {
+	char relpersistence;
+
 	/*
 	 * The flag has previously been initialized to false, so we can just
 	 * return if it becomes clear that we can't safely set it.
@@ -618,7 +620,11 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			/* global temp table is same as local temp table */
+			relpersistence = get_rel_persistence(rte->relid);
+
+			if (relpersistence == RELPERSISTENCE_TEMP ||
+				relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index f52226c..6355cd8 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 51470dd..1e67141 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -28,6 +28,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temp table */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6676412..0685c1c 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2585,6 +2585,11 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("materialized views must not use temporary tables or views")));
 
+		if (is_query_using_gtt(query))
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("materialized views must not use global temporary tables or views")));
+
 		/*
 		 * A materialized view would either need to save parameters for use in
 		 * maintaining/loading the data or prohibit them entirely.  The latter
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index eb0bf12..b5e572c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3291,17 +3291,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; }
@@ -11623,19 +11617,13 @@ OptTempTableName:
 				}
 			| GLOBAL TEMPORARY opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED opt_table qualified_name
 				{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b875a50..123945b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index ae322aa..c4d6adf 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
@@ -3085,6 +3088,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		cxt.isforeign = false;
 	}
 	cxt.relation = stmt->relation;
+	cxt.relation->relpersistence = RelationGetRelPersistence(rel);
 	cxt.rel = rel;
 	cxt.inhRelations = NIL;
 	cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 7e97ffa..1f2ac58 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2089,6 +2089,11 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* autovacuum skip vacuum global temp table */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2155,7 +2160,9 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		/* autovacuum skip vacuum global temp table */
+		if (classForm->relpersistence == RELPERSISTENCE_TEMP || 
+			classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index e72d607..5436c5c 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -53,6 +54,7 @@
 #include "utils/rel.h"
 #include "utils/resowner_private.h"
 #include "utils/timestamp.h"
+#include "utils/guc.h"
 
 
 /* Note: these two macros only work on shared buffers, not local ones! */
@@ -2757,6 +2759,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d5..7adb969 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -21,6 +21,7 @@
 #include "access/nbtree.h"
 #include "access/subtrans.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, active_gtt_shared_hash_size());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* global temporary table */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index cfb88db..96b0fc4 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -63,6 +63,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -4095,3 +4096,77 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temp table.
+ */
+int
+list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct *arrayP = procArray;
+	TransactionId result = InvalidTransactionId;
+	int			index;
+	int			flags = 0;
+	int			i = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+		*n = 0;
+	}
+
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		volatile PGXACT *pgxact = &allPgXact[pgprocno];
+
+		if (pgxact->vacuumFlags & flags)
+			continue;
+
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->session_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->session_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->session_gtt_frozenxid, result))
+				result = proc->session_gtt_frozenxid;
+
+			if (max_size > 0)
+			{
+				pids[i] = proc->pid;
+				xids[i] = proc->session_gtt_frozenxid;
+				i++;
+			}
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (max_size > 0)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 9938cdd..231750b 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 8406644..0d99321 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -1008,6 +1008,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index e62b69d..61f3c7b 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -112,6 +112,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -4571,12 +4572,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))
 							{
@@ -4701,15 +4715,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))
 		{
@@ -6091,6 +6117,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;
@@ -6108,6 +6135,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,
@@ -6119,6 +6153,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;
@@ -6134,6 +6170,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,
@@ -7052,6 +7095,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))
@@ -7064,6 +7109,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 =
@@ -7076,6 +7129,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.
@@ -7095,6 +7150,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 0a6db0d..56d1a36 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -2939,6 +2940,18 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index f8e2c6e..bfb0f8a 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -63,6 +63,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1127,6 +1128,24 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1181,6 +1200,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			gtt_fix_index_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -1303,7 +1323,17 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+			if (newrelnode != InvalidOid &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2222,6 +2252,8 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		gtt_fix_index_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3315,6 +3347,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3422,28 +3458,34 @@ void
 RelationSetNewRelfilenode(Relation relation, char persistence)
 {
 	Oid			newrelfilenode;
-	Relation	pg_class;
-	HeapTuple	tuple;
+	Relation	pg_class = NULL;
+	HeapTuple	tuple = NULL;
 	Form_pg_class classform;
 	MultiXactId minmulti = InvalidMultiXactId;
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
+	bool		modify_pg_class = !RELATION_IS_GLOBAL_TEMP(relation);
 
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
 
-	/*
-	 * Get a writable copy of the pg_class tuple for the given relation.
-	 */
-	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+	if (modify_pg_class)
+	{
+		/*
+		 * Get a writable copy of the pg_class tuple for the given relation.
+		 */
+		pg_class = table_open(RelationRelationId, RowExclusiveLock);
 
-	tuple = SearchSysCacheCopy1(RELOID,
-								ObjectIdGetDatum(RelationGetRelid(relation)));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "could not find tuple for relation %u",
-			 RelationGetRelid(relation));
-	classform = (Form_pg_class) GETSTRUCT(tuple);
+		tuple = SearchSysCacheCopy1(RELOID,
+									ObjectIdGetDatum(RelationGetRelid(relation)));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u",
+				 RelationGetRelid(relation));
+		classform = (Form_pg_class) GETSTRUCT(tuple);
+	}
+	else
+		memset(&classform, 0, sizeof(classform));
 
 	/*
 	 * Schedule unlinking of the old storage at transaction commit.
@@ -3469,7 +3511,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;
@@ -3489,6 +3531,15 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 			break;
 	}
 
+	if (!modify_pg_class)
+	{
+		Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+		Assert(RELATION_IS_GLOBAL_TEMP(relation));
+		Assert(!RelationIsMapped(relation));
+		relation->rd_node.relNode = relnode;
+		CacheInvalidateRelcache(relation);
+	}
 	/*
 	 * If we're dealing with a mapped index, pg_class.relfilenode doesn't
 	 * change; instead we have to send the update to the relation mapper.
@@ -3498,7 +3549,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	 * possibly-inaccurate values of relpages etc, but those will be fixed up
 	 * later.
 	 */
-	if (RelationIsMapped(relation))
+	else if (RelationIsMapped(relation))
 	{
 		/* This case is only supported for indexes */
 		Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3544,9 +3595,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 		CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 	}
 
-	heap_freetuple(tuple);
+	if (modify_pg_class)
+	{
+		heap_freetuple(tuple);
 
-	table_close(pg_class, RowExclusiveLock);
+		table_close(pg_class, RowExclusiveLock);
+	}
 
 	/*
 	 * Make the pg_class row change or relation map change visible.  This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 79bc7ac..61578f3 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -141,6 +141,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,
@@ -2068,6 +2080,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 1849dfe..b3b187a 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2424,6 +2424,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
 		dopt->no_unlogged_table_data)
 		return;
 
+	/* Don't dump data in global temp table/sequence */
+	if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		return;
+
 	/* Check that the data is not explicitly excluded */
 	if (simple_oid_list_member(&tabledata_exclude_oids,
 							   tbinfo->dobj.catId.oid))
@@ -15756,6 +15760,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
 		char	   *foreign = "";
+		char	   *table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15809,9 +15814,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 			binary_upgrade_set_pg_class_oids(fout, q,
 											 tbinfo->dobj.catId.oid, false);
 
+		if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED)
+			table_type = "UNLOGGED ";
+		else if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			table_type = "GLOBAL TEMPORARY ";
+		else
+			table_type = "";
+
 		appendPQExpBuffer(q, "CREATE %s%s %s",
-						  tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ?
-						  "UNLOGGED " : "",
+						  table_type,
 						  reltypename,
 						  qualrelname);
 
@@ -17110,6 +17121,7 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	PQExpBuffer query = createPQExpBuffer();
 	PQExpBuffer delqry = createPQExpBuffer();
 	char	   *qseqname;
+	bool	   global_temp_seq = false;
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
@@ -17119,9 +17131,11 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
-						  "FROM pg_catalog.pg_sequence "
-						  "WHERE seqrelid = '%u'::oid",
+						  "seqcache, seqcycle, c.relpersistence "
+						  "FROM pg_catalog.pg_sequence s, "
+						  "pg_catalog.pg_class c "
+						  "WHERE seqrelid = '%u'::oid "
+						  "and s.seqrelid = c.oid",
 						  tbinfo->dobj.catId.oid);
 	}
 	else if (fout->remoteVersion >= 80400)
@@ -17166,6 +17180,9 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
 
+	if (fout->remoteVersion >= 130000)
+		global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
 	if (strcmp(seqtype, "smallint") == 0)
@@ -17240,9 +17257,13 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	}
 	else
 	{
-		appendPQExpBuffer(query,
-						  "CREATE SEQUENCE %s\n",
-						  fmtQualifiedDumpable(tbinfo));
+		appendPQExpBuffer(query, "CREATE ");
+
+		if (global_temp_seq)
+			appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+		appendPQExpBuffer(query, "SEQUENCE %s\n",
+								  fmtQualifiedDumpable(tbinfo));
 
 		if (strcmp(seqtype, "bigint") != 0)
 			appendPQExpBuffer(query, "    AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 00aef85..9ad71c8 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -84,7 +84,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -157,7 +157,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 7e524ea..371168f 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -441,22 +443,46 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 	 * output, so we have to copy that system table.  It's easiest to do that
 	 * by treating it as a user table.
 	 */
-	snprintf(query + strlen(query), sizeof(query) - strlen(query),
-			 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
-			 "  SELECT c.oid, 0::oid, 0::oid "
-			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
-			 "         ON c.relnamespace = n.oid "
-			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
-	/* exclude possible orphaned temp tables */
-			 "    ((n.nspname !~ '^pg_temp_' AND "
-			 "      n.nspname !~ '^pg_toast_temp_' AND "
-			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
-			 "                        'binary_upgrade', 'pg_toast') AND "
-			 "      c.oid >= %u::pg_catalog.oid) OR "
-			 "     (n.nspname = 'pg_catalog' AND "
-			 "      relname IN ('pg_largeobject') ))), ",
-			 FirstNormalObjectId);
+	if (skip_gtt)
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude global temp tables */
+				 "    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
+	else
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
 
 	/*
 	 * Add a CTE that collects OIDs of toast tables belonging to the tables
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 70194eb..8dd9e66 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -411,7 +411,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -642,7 +642,9 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -653,7 +655,9 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 8b90cef..d155205 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -389,7 +389,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 109245f..557cce1 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3724,7 +3724,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ca8f0d7..29e1aa3 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1023,6 +1023,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2390,6 +2392,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2598,6 +2603,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 78b33b2..4be89f6 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -168,6 +168,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index a6a708c..d9e3f95 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5512,6 +5512,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4388',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4389',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4390',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4391',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 048003c..af48cdf 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -19,7 +19,7 @@
 #include "storage/smgr.h"
 #include "utils/relcache.h"
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationTruncate(Relation rel, BlockNumber nblocks);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..89a1b51
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,44 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Relation relation,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void gtt_force_enable_index(Relation index);
+extern void gtt_fix_index_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index e2638ab..89a5ce4 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 3f88683..f870e9a 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 8fda8e4..172eae9 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_TBM,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_SXACT,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index d217801..8adde87 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -117,6 +117,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId session_gtt_frozenxid;	/* session level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ce93ace..0f7262e 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -281,6 +281,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 74106b3..19f25db 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	struct SMgrRelationData *rd_smgr;	/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -285,6 +285,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
@@ -543,11 +544,13 @@ typedef struct ViewOptions
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -555,6 +558,7 @@ typedef struct ViewOptions
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -567,6 +571,14 @@ typedef struct ViewOptions
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is either local temp relation of this session
+ * 		or global temp relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -610,6 +622,17 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..ca2d135
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,14 @@
+reset search_path;
+select pg_sleep(5);
+ pg_sleep 
+----------
+ 
+(1 row)
+
+drop schema gtt cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
+drop cascades to table gtt.gtt_with_seq
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..889e3ff
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,362 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+ n 
+---
+ 9
+(1 row)
+
+commit;
+-- 0 row
+select * from gtt6;
+ n 
+---
+(0 rows)
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+ERROR:  not support cluster global temporary tables yet
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temp table
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  could not create global temporary table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test;
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR:  insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count 
+-------
+     1
+(1 row)
+
+select count(*) from orders;
+ count 
+-------
+     0
+(1 row)
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  2 |  3
+(2 rows)
+
+commit;
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+select * from gtt_s_2 order by c1;
+ c1 | c2 
+----+----
+  1 |  2
+  2 |  4
+(2 rows)
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 1)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 300)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 23 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.gtt6
+drop cascades to table gtt_function.foo
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to sequence gtt_function.seq_1
+drop cascades to table gtt_function.gtt_s_1
+drop cascades to table gtt_function.gtt_s_2
+drop cascades to table gtt_function.gt1
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..0646aae
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,90 @@
+set search_path=gtt,sys;
+select nextval('gtt_with_seq_c2_seq');
+ nextval 
+---------
+       1
+(1 row)
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..de2373c
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,287 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+rollback;
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+commit;
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          114688 |                 614400
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |            49152 |         49152 |                  49152
+(2 rows)
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+ c1 | c2 
+----+----
+  1 |  1
+(1 row)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..8c0c376
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,10 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..4420fdb
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,80 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+ gtt_stats  | gtt_pkey  |        1 |         0 |             0
+(2 rows)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+reindex table gtt;
+reindex index gtt_pkey;
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+ gtt_stats  | gtt_pkey  |       30 |     10000 |             0
+(2 rows)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 7245b0e..649c349 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1349,6 +1349,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d2b17dd..80e577f 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..2c8e586
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,8 @@
+
+
+reset search_path;
+
+select pg_sleep(5);
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..af2bb20
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,239 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+commit;
+-- 0 row
+select * from gtt6;
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test;
+
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+commit;
+select * from gtt_s_1 order by c1;
+select * from gtt_s_2 order by c1;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d05745e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,44 @@
+
+
+set search_path=gtt,sys;
+
+select nextval('gtt_with_seq_c2_seq');
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..3ac81a1
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,123 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+rollback;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+commit;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..dbe84d1
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,19 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..d61b0ff
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,46 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+reindex table gtt;
+
+reindex index gtt_pkey;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#217曾文旌
wenjing.zwj@alibaba-inc.com
In reply to: tushar (#214)
2 attachment(s)
Re: [Proposal] Global temporary tables

2020年3月27日 下午5:21,tushar <tushar.ahuja@enterprisedb.com> 写道:

On 3/27/20 10:55 AM, 曾文旌 wrote:

Hi Wenjing,
This patch(gtt_v21_pg13.patch) is not applicable on PG HEAD, I hope you have prepared the patch on top of some previous commit.
Could you please rebase the patch which we can apply on HEAD ?

Yes, It looks like the built-in functions are in conflict with new code.

This error message looks wrong to me-

postgres=# reindex table concurrently t ;
ERROR: cannot create indexes on global temporary tables using concurrent mode
postgres=#

Better message would be-

ERROR: cannot reindex global temporary tables concurrently

I found that the local temp table automatically disables concurrency mode.
so, I made some improvements, The reindex GTT behaves the same as the local temp table.

Wenjing

Show quoted text

--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/ <https://www.enterprisedb.com/&gt;
The Enterprise PostgreSQL Company

Attachments:

global_temporary_table_v23-pg13.patchapplication/octet-stream; name=global_temporary_table_v23-pg13.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 8ccc228..b061de9 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -168,6 +168,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * For global temp table only
+	 * use ShareUpdateExclusiveLock for ensure safety
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temp table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},	
 	/* list terminator */
 	{{NULL}}
 };
@@ -1817,6 +1830,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1961,13 +1976,18 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	/*
 	 * There are no options for partitioned tables yet, but this is able to do
 	 * some validation.
 	 */
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index dd975b1..1610e7d 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1013,7 +1013,9 @@ gistGetFakeLSN(Relation rel)
 {
 	static XLogRecPtr counter = FirstNormalUnloggedLSN;
 
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	/* global temp is same as local temp table */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 3ec6d52..1222594 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -150,7 +150,9 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	/* global temp table is same as local temp table */
+	if (!(index->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		index->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ca52846..f0153cf 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -598,7 +598,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -651,7 +651,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index af322c1..0734ea0 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -62,6 +62,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -420,9 +421,9 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
 	Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
-	/* not every AM requires these to be valid, but heap does */
-	Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
-	Assert(MultiXactIdIsValid(onerel->rd_rel->relminmxid));
+	/* not every AM requires these to be valid, but regular heap does */
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ MultiXactIdIsValid(onerel->rd_rel->relminmxid));
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 39b8f17..abb76dc 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -601,6 +602,10 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 1951103..fa91a1c 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6429,6 +6429,10 @@ StartupXLOG(void)
 	else
 		recoveryTargetTLI = ControlFile->checkPointCopy.ThisTimeLineID;
 
+	/* clean temp relation files */
+	if (max_active_gtt > 0)
+		RemovePgTempFiles();
+
 	/*
 	 * Check for signal files, and if so set up state for offline recovery
 	 */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 9499bb3..ae47364 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -43,6 +43,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 7d6acae..3be8d63 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -393,7 +393,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/* global temp table is same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 632c058..01cf990 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -427,7 +429,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -959,6 +961,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -997,8 +1000,18 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/* global temp table not remember transaction info in catalog */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1360,6 +1373,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1940,6 +1954,14 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/* We allow to drop global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			elog(ERROR, "can not drop relation %s when other backend attached this global temp table",
+						RelationGetRelationName(rel));
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3167,7 +3189,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3179,7 +3201,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3225,8 +3247,13 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		/* truncate global temp table only need RowExclusiveLock */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3259,6 +3286,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3267,23 +3295,37 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (!gtt_storage_attached(RelationGetRelid(rel)))
+			return;
+
+		/*
+		 * Truncate global temp table only need RowExclusiveLock
+		 */
+		lockmode = RowExclusiveLock;
+	}
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(rel, 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index bd7ec92..afbb655 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -53,6 +53,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -881,6 +882,19 @@ index_create(Relation heapRelation,
 						indexRelationName, RelationGetRelationName(heapRelation))));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temp table use concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot reindex global temporary tables concurrently")));
+
+		/* if global temp table not init storage, then skip build index */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -2034,6 +2048,7 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 				indexrelid;
 	LOCKTAG		heaplocktag;
 	LOCKMODE	lockmode;
+	char		rel_persistence;
 
 	/*
 	 * A temporary relation uses a non-concurrent DROP.  Other backends can't
@@ -2041,7 +2056,8 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 * lock (see comments in RemoveRelations), and a non-concurrent DROP is
 	 * more efficient.
 	 */
-	Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+	rel_persistence = get_rel_persistence(indexId);
+	Assert(!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP) ||
 		   (!concurrent && !concurrent_lock_mode));
 
 	/*
@@ -2073,6 +2089,14 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/* We allow to drop index on global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+			elog(ERROR, "can not drop index %s when other backend attached this global temp table.",
+						RelationGetRelationName(userHeapRelation));
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2681,6 +2705,11 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	/* update index stats into localhash and rel_rd_rel for global temp table */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		is_gtt = true;
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2766,21 +2795,35 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
+		if (is_gtt)
+			rel->rd_rel->relpages = (int32) relpages;
+		else if (rd_rel->relpages != (int32) relpages)
 		{
 			rd_rel->relpages = (int32) relpages;
 			dirty = true;
 		}
-		if (rd_rel->reltuples != (float4) reltuples)
+
+		if (is_gtt)
+			rel->rd_rel->reltuples = (float4) reltuples;
+		else if (rd_rel->reltuples != (float4) reltuples)
 		{
 			rd_rel->reltuples = (float4) reltuples;
 			dirty = true;
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+
+		if (is_gtt)
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+		else if (rd_rel->relallvisible != (int32) relallvisible)
 		{
 			rd_rel->relallvisible = (int32) relallvisible;
 			dirty = true;
 		}
+
+		if (is_gtt)
+		{
+			up_gtt_relstats(rel, relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
+		}
 	}
 
 	/*
@@ -2894,6 +2937,15 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, index, val);
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			gtt_force_enable_index(indexRelation);
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3442,6 +3494,10 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	PGRUsage	ru0;
 	bool		progress = (options & REINDEXOPT_REPORT_PROGRESS) != 0;
 
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		!gtt_storage_attached(indexId))
+		return;
+
 	pg_rusage_init(&ru0);
 
 	/*
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 2ec2301..1b6061d 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -655,6 +655,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		/* global temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp relations in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index fddfbf1..c3661e3 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -26,6 +26,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -56,6 +57,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			relOid;			/* InvalidOid if not a global temp rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -75,7 +77,7 @@ static PendingRelDelete *pendingDeletes = NULL; /* head of linked list */
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -85,6 +87,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;
@@ -112,11 +116,19 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->relOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/* remember global temp table storage info to localhash */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel)
+	{
+		pending->relOid = RelationGetRelid(rel);
+		remember_gtt_storage_info(rnode, rel);
+	}
+
 	return srel;
 }
 
@@ -153,11 +165,15 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->relOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->relOid = RelationGetRelid(rel);
+
 	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
@@ -437,6 +453,7 @@ smgrDoPendingDeletes(bool isCommit)
 				i = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -466,14 +483,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->relOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -486,9 +507,18 @@ smgrDoPendingDeletes(bool isCommit)
 		smgrdounlinkall(srels, nrels, false);
 
 		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* clean global temp table flags when transaction commit or rollback */
+			if (SmgrIsTemp(srels[i]) &&
+				reloids[i] != InvalidOid &&
+				gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..5bf3bb3
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1427 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  code to create and destroy physical storage for global temparary table
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_relstats_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+typedef struct gtt_ctl_data
+{
+	LWLock			lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+	Oid			relfilenode;
+	Oid			spcnode;
+
+	/* pg_class stat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+
+	/* pg_statistic */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_reset_statistics(gtt_local_hash_entry *entry);
+static void gtt_free_statistics(gtt_local_hash_entry *entry);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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();
+	}
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(gtt_fnode);
+	info.entrysize = action_gtt_shared_hash_entry_size();
+	active_gtt_shared_hash =
+		ShmemInitHash("active gtt shared hash",
+						gtt_shared_ctl->max_entry,
+						gtt_shared_ctl->max_entry,
+						&info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			found;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *)&(fnode), 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_global_temporary_table.")));
+	}
+
+	if (found == false)
+	{
+		int			wordnum;
+
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode				fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	if (!skiplock)
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		if (!skiplock)
+			LWLockRelease(&gtt_shared_ctl->lock);
+
+		if (isCommit)
+			elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+	bms_del_member(entry->map, MyBackendId);
+
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+
+	if (!skiplock)
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+	return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	Bitmapset	*map_copy = NULL;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool		in_use = false;
+	int			num_use = 0;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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;
+	gtt_relfilenode			*new_node = NULL;
+	Oid						relid = RelationGetRelid(rel);
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		/* First time through: initialize the hash table */
+		HASHCTL		ctl;
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_relstats_context =
+			AllocSetContextCreate(CacheMemoryContext,
+							"gtt relstats context",
+							ALLOCSET_DEFAULT_SIZES);
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+	if (!entry)
+	{
+		bool		found = false;
+		int 		natts = 0;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash,
+						(void *) &relid, HASH_ENTER, &found);
+
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+		entry->natts = 0;
+		entry->attnum = NULL;
+		entry->att_stat_tups = NULL;
+
+		natts = RelationGetNumberOfAttributes(rel);
+		entry->attnum = palloc0(sizeof(int) * natts);
+		entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+		entry->natts = natts;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			gtt_storage_checkin(relid);
+		}
+	}
+
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* only heap contain transaction information */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	gtt_reset_statistics(entry);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	ListCell		*lc;
+	gtt_relfilenode *d_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (!entry)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+		return;
+	}
+
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == rnode.relNode &&
+			gtt_rnode->spcnode == rnode.spcNode)
+		{
+			d_rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+
+		if (entry->relfilenode_list == NIL)
+		{
+			if (entry->relkind == RELKIND_RELATION ||
+				entry->relkind == RELKIND_SEQUENCE)
+				gtt_storage_checkout(relid, false, isCommit);
+
+			gtt_free_statistics(entry);
+			hash_search(gtt_storage_local_hash,
+					(void *) &(relid), HASH_REMOVE, NULL);
+		}
+
+		return;
+	}
+
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	pfree(d_rnode);
+	if (entry->relfilenode_list == NIL)
+	{
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(relid, false, isCommit);
+
+		gtt_free_statistics(entry);
+		hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_REMOVE, NULL);
+	}
+	else
+		gtt_reset_statistics(entry);
+
+	return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool found;
+
+	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;
+	SMgrRelation	*srels = NULL;
+	Oid				*relids = NULL;
+	char			*relkinds = NULL;
+	int			nrels = 0,
+				nfiles = 0,
+				maxrels = 0,
+				maxfiles = 0,
+				i = 0;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel;
+			RelFileNode 	rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel = smgropen(rnode, MyBackendId);
+
+			if (maxfiles == 0)
+			{
+				maxfiles = 32;
+				srels = palloc(sizeof(SMgrRelation) * maxfiles);
+			}
+			else if (maxfiles <= nfiles)
+			{
+				maxfiles *= 2;
+				srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+			}
+
+			srels[nfiles++] = srel;
+		}
+
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			relids  = palloc(sizeof(Oid) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			relids  = repalloc(relids , sizeof(Oid) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		relkinds[nrels] = entry->relkind;
+		relids[nrels] = entry->relid;
+		nrels++;
+	}
+
+	if (nfiles > 0)
+	{
+		smgrdounlinkall(srels, nfiles, false);
+		for (i = 0; i < nfiles; i++)
+			smgrclose(srels[i]);
+
+		pfree(srels);
+	}
+
+	if (nrels)
+	{
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+		for (i = 0; i < nrels; i++)
+		{
+			if (relkinds[i] == RELKIND_RELATION ||
+				relkinds[i] == RELKIND_SEQUENCE)
+				gtt_storage_checkout(relids[i], true, false);
+		}
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+		pfree(relids);
+		pfree(relkinds);
+	}
+
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+	return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible) 
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+					BlockNumber num_pages,
+					double num_tuples,
+					BlockNumber num_all_visible_pages,
+					TransactionId relfrozenxid,
+					TransactionId relminmxid)
+{
+	Oid		relid = RelationGetRelid(relation);
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_FIND, &found);
+
+	if (!found)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (num_pages >= 0 &&
+		gtt_rnode->relpages != (int32)num_pages)
+		gtt_rnode->relpages = (int32)num_pages;
+
+	if (num_tuples >= 0 &&
+		gtt_rnode->reltuples != (float4)num_tuples)
+		gtt_rnode->reltuples = (float4)num_tuples;
+
+	/* only heap contain transaction information and relallvisible */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (gtt_rnode->relallvisible >= 0 &&
+			gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+		{
+			gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+		}
+
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNewTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible) 
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (gtt_storage_local_hash == NULL)
+		return false;
+
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_FIND, &found);
+
+	if (!found)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	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->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	Assert(entry->relid == reloid);
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == 0)
+		{
+			entry->attnum[i] = attnum;
+			break;
+		}
+		else if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < entry->natts);
+	Assert(entry->att_stat_tups[i] == NULL);
+	entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+						(void *) &reloid, HASH_FIND, &found);
+
+	if (!found)
+		return NULL;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			return entry->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int			i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	HeapTuple	tuple;
+	Relation	rel = NULL;
+	int		attnum = PG_GETARG_INT32(1);
+	Oid		reloid = PG_GETARG_OID(0);
+	char		rel_persistence;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	  tupdesc;
+	MemoryContext oldcontext;
+	Tuplestorestate *tupstore;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+
+	relation_close(rel, NoLock);
+	relation_close(pg_tatistic, AccessShareLock);
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate	*tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Oid			relnode = 0;
+	Relation	rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[6];
+		bool	isnull[6];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(reloid);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(indexOid));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid relOid = index->rd_index->indrelid;
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	if (gtt_storage_attached(relOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int			i;
+	Oid			toastrelid;
+
+	if (operation != CMD_INSERT)
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+
+		index_build(relation, index, info, true, false);
+	}
+
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid 		indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo 	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+static void
+gtt_reset_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+
+		entry->attnum[i] = 0;
+	}
+
+	return;
+}
+
+static void
+gtt_free_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (entry->attnum)
+		pfree(entry->attnum);
+
+	if (entry->att_stat_tups)
+		pfree(entry->att_stat_tups);
+
+	return;
+}
+
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	if (gtt_storage_local_hash == NULL)
+		return InvalidOid;
+
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_FIND, &found);
+
+	if (!found)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 83d00c6..a294f6f 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM 
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 924ef37..db12eef 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -184,6 +185,13 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -586,14 +594,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 		 * pg_statistic for columns we didn't process, we leave them alone.)
 		 */
 		update_attstats(RelationGetRelid(onerel), inh,
-						attr_cnt, vacattrstats);
+						attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
 
 		for (ind = 0; ind < nindexes; ind++)
 		{
 			AnlIndexData *thisdata = &indexdata[ind];
 
 			update_attstats(RelationGetRelid(Irel[ind]), false,
-							thisdata->attr_cnt, thisdata->vacattrstats);
+							thisdata->attr_cnt, thisdata->vacattrstats,
+							RelationGetRelPersistence(Irel[ind]));
 		}
 
 		/*
@@ -1456,7 +1465,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
  *		by taking a self-exclusive lock on the relation in analyze_rel().
  */
 static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
 {
 	Relation	sd;
 	int			attno;
@@ -1558,31 +1567,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/* Update column statistic to localhash, not catalog */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fc1cea0..7059e96 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -391,6 +391,12 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/* not support cluster global temp table yet */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("not support cluster global temporary tables yet")));
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index efb1e0d..66221ee 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -27,6 +27,7 @@
 #include "catalog/dependency.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -1063,7 +1064,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, stmt->filename, stmt->is_program,
@@ -2842,6 +2843,8 @@ CopyFrom(CopyState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	estate->es_result_relations = resultRelInfo;
 	estate->es_num_result_relations = 1;
 	estate->es_result_relation_info = resultRelInfo;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 2e5997b..0a99f91 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -521,6 +521,7 @@ DefineIndex(Oid relationId,
 	Snapshot	snapshot;
 	int			save_nestlevel = -1;
 	int			i;
+	char		rel_persistence;
 
 	/*
 	 * Some callers need us to run with an empty default_tablespace; this is a
@@ -542,7 +543,9 @@ DefineIndex(Oid relationId,
 	 * is more efficient.  Do this before any use of the concurrent option is
 	 * done.
 	 */
-	if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+	rel_persistence = get_rel_persistence(relationId);
+	if (stmt->concurrent &&
+		!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		concurrent = true;
 	else
 		concurrent = false;
@@ -2406,7 +2409,8 @@ ReindexIndex(RangeVar *indexRelation, int options, bool concurrent)
 	persistence = irel->rd_rel->relpersistence;
 	index_close(irel, NoLock);
 
-	if (concurrent && persistence != RELPERSISTENCE_TEMP)
+	if (concurrent &&
+		!(persistence == RELPERSISTENCE_TEMP || persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		ReindexRelationConcurrently(indOid, options);
 	else
 		reindex_index(indOid, false, persistence,
@@ -2492,6 +2496,7 @@ ReindexTable(RangeVar *relation, int options, bool concurrent)
 {
 	Oid			heapOid;
 	bool		result;
+	char		rel_persistence;
 
 	/*
 	 * The lock level used here should match reindex_relation().
@@ -2506,7 +2511,9 @@ ReindexTable(RangeVar *relation, int options, bool concurrent)
 									   0,
 									   RangeVarCallbackOwnsTable, NULL);
 
-	if (concurrent && get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+	rel_persistence = get_rel_persistence(heapOid);
+	if (concurrent &&
+		!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 	{
 		result = ReindexRelationConcurrently(heapOid, options);
 
@@ -2707,12 +2714,15 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 	foreach(l, relids)
 	{
 		Oid			relid = lfirst_oid(l);
+		char		rel_persistence;
 
 		StartTransactionCommand();
 		/* functions in indexes may want a snapshot set */
 		PushActiveSnapshot(GetTransactionSnapshot());
 
-		if (concurrent && get_rel_persistence(relid) != RELPERSISTENCE_TEMP)
+		rel_persistence = get_rel_persistence(relid);
+		if (concurrent &&
+			!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		{
 			(void) ReindexRelationConcurrently(relid, options);
 			/* ReindexRelationConcurrently() does the verbose output */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..a45863a 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -94,7 +96,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -222,7 +225,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* now initialize the sequence's data */
 	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -327,7 +330,7 @@ ResetSequence(Oid seq_relid)
 	/*
 	 * Insert the modified tuple into the new storage file.
 	 */
-	fill_seq_with_data(seq_rel, tuple);
+	fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -340,17 +343,21 @@ ResetSequence(Oid seq_relid)
  * Initialize a sequence's relation with the specified tuple as content
  */
 static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 	sequence_magic *sm;
 	OffsetNumber offnum;
+	bool lockBuffer = false;
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ReadBuffer(rel, P_NEW);
-	Assert(BufferGetBlockNumber(buf) == 0);
+	if (buf == InvalidBuffer)
+	{
+		buf = ReadBuffer(rel, P_NEW);
+		Assert(BufferGetBlockNumber(buf) == 0);
+		lockBuffer = true;
+	}
 
 	page = BufferGetPage(buf);
 
@@ -360,7 +367,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* Now insert sequence tuple */
 
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+	if (lockBuffer)
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +418,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	END_CRIT_SECTION();
 
-	UnlockReleaseBuffer(buf);
+	if (lockBuffer)
+		UnlockReleaseBuffer(buf);
 }
 
 /*
@@ -451,6 +460,13 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 
 	init_sequence(relid, &elm, &seqrel);
 
+	if (RELATION_IS_GLOBAL_TEMP(seqrel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(seqrel)))
+			elog(ERROR, "cannot alter global temp sequence %s when other backend attached it",
+						RelationGetRelationName(seqrel));
+	}
+
 	rel = table_open(SequenceRelationId, RowExclusiveLock);
 	seqtuple = SearchSysCacheCopy1(SEQRELID,
 								   ObjectIdGetDatum(relid));
@@ -502,7 +518,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * Insert the modified tuple into the new storage file.
 		 */
-		fill_seq_with_data(seqrel, newdatatuple);
+		fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
 	}
 
 	/* process OWNED BY if given */
@@ -611,7 +627,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +952,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1154,6 +1170,13 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1954,3 +1977,46 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+void
+gtt_init_seq(Relation rel)
+{
+	/* Initialize sequence for global temporary tables */
+	Datum		value[SEQ_COL_LASTCOL] = {0};
+	bool		null[SEQ_COL_LASTCOL] = {false};
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index c8c88be..95a735b 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -554,6 +555,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 								  Relation partitionTbl);
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static OnCommitAction gtt_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -599,6 +601,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -609,8 +612,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")));
@@ -640,7 +645,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	/* global temp table same as local temp table */
+	if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+		 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -741,6 +748,55 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* global temp table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* check parent table*/
+		if (inheritOids)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			table_close(relation, NoLock);
+		}
+
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temp table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1328,6 +1384,7 @@ RemoveRelations(DropStmt *drop)
 		Oid			relOid;
 		ObjectAddress obj;
 		struct DropRelationCallbackState state;
+		char		rel_persistence;
 
 		/*
 		 * These next few steps are a great deal like relation_openrv, but we
@@ -1361,8 +1418,9 @@ RemoveRelations(DropStmt *drop)
 		 * Decide if concurrent mode needs to be used here or not.  The
 		 * relation persistence cannot be known without its OID.
 		 */
+		rel_persistence = get_rel_persistence(relOid);
 		if (drop->concurrent &&
-			get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+			!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		{
 			Assert(list_length(drop->objects) == 1 &&
 				   drop->removeType == OBJECT_INDEX);
@@ -1817,6 +1875,10 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
 		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 			continue;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			continue;
+
 		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
@@ -3568,6 +3630,14 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			elog(ERROR, "can not alter table %s when other backend attached this global temp table",
+						RelationGetRelationName(rel));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -8182,6 +8252,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -12689,6 +12765,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -12891,6 +12970,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temp table");
+
 	/*
 	 * No work if no change in tablespace.
 	 */
@@ -13263,7 +13345,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,
@@ -14670,7 +14752,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",
@@ -17263,3 +17347,28 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
 		table_close(rel, NoLock);
 	}
 }
+
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			if (defGetBoolean(def))
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 59731d6..33c02cc 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/vacuum.h"
@@ -1217,6 +1218,17 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	 /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		is_gtt = true;
+		up_gtt_relstats(relation,
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1230,17 +1242,26 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relpages = (int32) num_pages;
+	else if (pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (is_gtt)
+		relation->rd_rel->reltuples = (float4) num_tuples;
+	else if (pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	else if (pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1285,7 +1306,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1296,7 +1318,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1394,6 +1417,10 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/* global temp table relstats not in pg_class */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1451,6 +1478,25 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/*
+	 * Global temp table get frozenxid from MyProc
+	 * to avoid the vacuum truncate clog that gtt need.
+	 */
+	if (max_active_gtt > 0)
+	{
+		TransactionId oldest_gtt_frozenxid =
+			list_all_session_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid) &&
+			TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+		{
+			ereport(WARNING,
+				(errmsg("global temp table oldest FrozenXid is far in the past"),
+				 errhint("please truncate them or kill those sessions that use them.")));
+			newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1778,6 +1824,18 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		ereport(WARNING,
+				(errmsg("skipping vacuum global temp table \"%s\" because storage is not initialized for current session",
+						RelationGetRelationName(onerel))));
+		relation_close(onerel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6e65103..04706ee 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -530,6 +530,11 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 4fdffad..0aa7559 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index fb6ce49..06d9237 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
@@ -545,6 +546,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	init_gtt_storage(mtstate->operation, leaf_part_rri);
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d71c0a4..0900907 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,6 +42,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -2372,6 +2373,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			ExecOpenIndices(resultRelInfo,
 							node->onConflictAction != ONCONFLICT_NONE);
 
+		init_gtt_storage(operation, resultRelInfo);
+
 		/*
 		 * If this is an UPDATE and a BEFORE UPDATE trigger is present, the
 		 * trigger itself might modify the partition-key values. So arrange
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 905bbe7..170963d 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -591,6 +591,8 @@ static void
 set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 						  RangeTblEntry *rte)
 {
+	char relpersistence;
+
 	/*
 	 * The flag has previously been initialized to false, so we can just
 	 * return if it becomes clear that we can't safely set it.
@@ -618,7 +620,11 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			/* global temp table is same as local temp table */
+			relpersistence = get_rel_persistence(rte->relid);
+
+			if (relpersistence == RELPERSISTENCE_TEMP ||
+				relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index f52226c..6355cd8 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 51470dd..1e67141 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -28,6 +28,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temp table */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6676412..0685c1c 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2585,6 +2585,11 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("materialized views must not use temporary tables or views")));
 
+		if (is_query_using_gtt(query))
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("materialized views must not use global temporary tables or views")));
+
 		/*
 		 * A materialized view would either need to save parameters for use in
 		 * maintaining/loading the data or prohibit them entirely.  The latter
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index eb0bf12..b5e572c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3291,17 +3291,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; }
@@ -11623,19 +11617,13 @@ OptTempTableName:
 				}
 			| GLOBAL TEMPORARY opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED opt_table qualified_name
 				{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b875a50..123945b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index ae322aa..c4d6adf 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
@@ -3085,6 +3088,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		cxt.isforeign = false;
 	}
 	cxt.relation = stmt->relation;
+	cxt.relation->relpersistence = RelationGetRelPersistence(rel);
 	cxt.rel = rel;
 	cxt.inhRelations = NIL;
 	cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 7e97ffa..1f2ac58 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2089,6 +2089,11 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* autovacuum skip vacuum global temp table */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2155,7 +2160,9 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		/* autovacuum skip vacuum global temp table */
+		if (classForm->relpersistence == RELPERSISTENCE_TEMP || 
+			classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index e72d607..5436c5c 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -53,6 +54,7 @@
 #include "utils/rel.h"
 #include "utils/resowner_private.h"
 #include "utils/timestamp.h"
+#include "utils/guc.h"
 
 
 /* Note: these two macros only work on shared buffers, not local ones! */
@@ -2757,6 +2759,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d5..7adb969 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -21,6 +21,7 @@
 #include "access/nbtree.h"
 #include "access/subtrans.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, active_gtt_shared_hash_size());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* global temporary table */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index cfb88db..96b0fc4 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -63,6 +63,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -4095,3 +4096,77 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temp table.
+ */
+int
+list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct *arrayP = procArray;
+	TransactionId result = InvalidTransactionId;
+	int			index;
+	int			flags = 0;
+	int			i = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+		*n = 0;
+	}
+
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		volatile PGXACT *pgxact = &allPgXact[pgprocno];
+
+		if (pgxact->vacuumFlags & flags)
+			continue;
+
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->session_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->session_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->session_gtt_frozenxid, result))
+				result = proc->session_gtt_frozenxid;
+
+			if (max_size > 0)
+			{
+				pids[i] = proc->pid;
+				xids[i] = proc->session_gtt_frozenxid;
+				i++;
+			}
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (max_size > 0)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 9938cdd..231750b 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 8406644..0d99321 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -1008,6 +1008,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index e62b69d..61f3c7b 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -112,6 +112,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -4571,12 +4572,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))
 							{
@@ -4701,15 +4715,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))
 		{
@@ -6091,6 +6117,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;
@@ -6108,6 +6135,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,
@@ -6119,6 +6153,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;
@@ -6134,6 +6170,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,
@@ -7052,6 +7095,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))
@@ -7064,6 +7109,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 =
@@ -7076,6 +7129,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.
@@ -7095,6 +7150,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 0a6db0d..56d1a36 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -2939,6 +2940,18 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index f8e2c6e..bfb0f8a 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -63,6 +63,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1127,6 +1128,24 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1181,6 +1200,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			gtt_fix_index_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -1303,7 +1323,17 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+			if (newrelnode != InvalidOid &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2222,6 +2252,8 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		gtt_fix_index_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3315,6 +3347,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3422,28 +3458,34 @@ void
 RelationSetNewRelfilenode(Relation relation, char persistence)
 {
 	Oid			newrelfilenode;
-	Relation	pg_class;
-	HeapTuple	tuple;
+	Relation	pg_class = NULL;
+	HeapTuple	tuple = NULL;
 	Form_pg_class classform;
 	MultiXactId minmulti = InvalidMultiXactId;
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
+	bool		modify_pg_class = !RELATION_IS_GLOBAL_TEMP(relation);
 
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
 
-	/*
-	 * Get a writable copy of the pg_class tuple for the given relation.
-	 */
-	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+	if (modify_pg_class)
+	{
+		/*
+		 * Get a writable copy of the pg_class tuple for the given relation.
+		 */
+		pg_class = table_open(RelationRelationId, RowExclusiveLock);
 
-	tuple = SearchSysCacheCopy1(RELOID,
-								ObjectIdGetDatum(RelationGetRelid(relation)));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "could not find tuple for relation %u",
-			 RelationGetRelid(relation));
-	classform = (Form_pg_class) GETSTRUCT(tuple);
+		tuple = SearchSysCacheCopy1(RELOID,
+									ObjectIdGetDatum(RelationGetRelid(relation)));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u",
+				 RelationGetRelid(relation));
+		classform = (Form_pg_class) GETSTRUCT(tuple);
+	}
+	else
+		memset(&classform, 0, sizeof(classform));
 
 	/*
 	 * Schedule unlinking of the old storage at transaction commit.
@@ -3469,7 +3511,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;
@@ -3489,6 +3531,15 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 			break;
 	}
 
+	if (!modify_pg_class)
+	{
+		Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+		Assert(RELATION_IS_GLOBAL_TEMP(relation));
+		Assert(!RelationIsMapped(relation));
+		relation->rd_node.relNode = relnode;
+		CacheInvalidateRelcache(relation);
+	}
 	/*
 	 * If we're dealing with a mapped index, pg_class.relfilenode doesn't
 	 * change; instead we have to send the update to the relation mapper.
@@ -3498,7 +3549,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	 * possibly-inaccurate values of relpages etc, but those will be fixed up
 	 * later.
 	 */
-	if (RelationIsMapped(relation))
+	else if (RelationIsMapped(relation))
 	{
 		/* This case is only supported for indexes */
 		Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3544,9 +3595,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 		CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 	}
 
-	heap_freetuple(tuple);
+	if (modify_pg_class)
+	{
+		heap_freetuple(tuple);
 
-	table_close(pg_class, RowExclusiveLock);
+		table_close(pg_class, RowExclusiveLock);
+	}
 
 	/*
 	 * Make the pg_class row change or relation map change visible.  This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 79bc7ac..61578f3 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -141,6 +141,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,
@@ -2068,6 +2080,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 1849dfe..b3b187a 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2424,6 +2424,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
 		dopt->no_unlogged_table_data)
 		return;
 
+	/* Don't dump data in global temp table/sequence */
+	if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		return;
+
 	/* Check that the data is not explicitly excluded */
 	if (simple_oid_list_member(&tabledata_exclude_oids,
 							   tbinfo->dobj.catId.oid))
@@ -15756,6 +15760,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
 		char	   *foreign = "";
+		char	   *table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15809,9 +15814,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 			binary_upgrade_set_pg_class_oids(fout, q,
 											 tbinfo->dobj.catId.oid, false);
 
+		if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED)
+			table_type = "UNLOGGED ";
+		else if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			table_type = "GLOBAL TEMPORARY ";
+		else
+			table_type = "";
+
 		appendPQExpBuffer(q, "CREATE %s%s %s",
-						  tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ?
-						  "UNLOGGED " : "",
+						  table_type,
 						  reltypename,
 						  qualrelname);
 
@@ -17110,6 +17121,7 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	PQExpBuffer query = createPQExpBuffer();
 	PQExpBuffer delqry = createPQExpBuffer();
 	char	   *qseqname;
+	bool	   global_temp_seq = false;
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
@@ -17119,9 +17131,11 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
-						  "FROM pg_catalog.pg_sequence "
-						  "WHERE seqrelid = '%u'::oid",
+						  "seqcache, seqcycle, c.relpersistence "
+						  "FROM pg_catalog.pg_sequence s, "
+						  "pg_catalog.pg_class c "
+						  "WHERE seqrelid = '%u'::oid "
+						  "and s.seqrelid = c.oid",
 						  tbinfo->dobj.catId.oid);
 	}
 	else if (fout->remoteVersion >= 80400)
@@ -17166,6 +17180,9 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
 
+	if (fout->remoteVersion >= 130000)
+		global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
 	if (strcmp(seqtype, "smallint") == 0)
@@ -17240,9 +17257,13 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	}
 	else
 	{
-		appendPQExpBuffer(query,
-						  "CREATE SEQUENCE %s\n",
-						  fmtQualifiedDumpable(tbinfo));
+		appendPQExpBuffer(query, "CREATE ");
+
+		if (global_temp_seq)
+			appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+		appendPQExpBuffer(query, "SEQUENCE %s\n",
+								  fmtQualifiedDumpable(tbinfo));
 
 		if (strcmp(seqtype, "bigint") != 0)
 			appendPQExpBuffer(query, "    AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 00aef85..9ad71c8 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -84,7 +84,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -157,7 +157,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 7e524ea..371168f 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -441,22 +443,46 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 	 * output, so we have to copy that system table.  It's easiest to do that
 	 * by treating it as a user table.
 	 */
-	snprintf(query + strlen(query), sizeof(query) - strlen(query),
-			 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
-			 "  SELECT c.oid, 0::oid, 0::oid "
-			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
-			 "         ON c.relnamespace = n.oid "
-			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
-	/* exclude possible orphaned temp tables */
-			 "    ((n.nspname !~ '^pg_temp_' AND "
-			 "      n.nspname !~ '^pg_toast_temp_' AND "
-			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
-			 "                        'binary_upgrade', 'pg_toast') AND "
-			 "      c.oid >= %u::pg_catalog.oid) OR "
-			 "     (n.nspname = 'pg_catalog' AND "
-			 "      relname IN ('pg_largeobject') ))), ",
-			 FirstNormalObjectId);
+	if (skip_gtt)
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude global temp tables */
+				 "    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
+	else
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
 
 	/*
 	 * Add a CTE that collects OIDs of toast tables belonging to the tables
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 70194eb..8dd9e66 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -411,7 +411,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -642,7 +642,9 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -653,7 +655,9 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 8b90cef..d155205 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -389,7 +389,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 109245f..557cce1 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3724,7 +3724,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ca8f0d7..29e1aa3 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1023,6 +1023,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2390,6 +2392,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2598,6 +2603,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 78b33b2..4be89f6 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -168,6 +168,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index a6a708c..d9e3f95 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5512,6 +5512,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4388',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4389',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4390',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4391',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 048003c..af48cdf 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -19,7 +19,7 @@
 #include "storage/smgr.h"
 #include "utils/relcache.h"
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationTruncate(Relation rel, BlockNumber nblocks);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..89a1b51
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,44 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Relation relation,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void gtt_force_enable_index(Relation index);
+extern void gtt_fix_index_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index e2638ab..89a5ce4 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 3f88683..f870e9a 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 8fda8e4..172eae9 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_TBM,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_SXACT,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index d217801..8adde87 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -117,6 +117,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId session_gtt_frozenxid;	/* session level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ce93ace..0f7262e 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -281,6 +281,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 74106b3..19f25db 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	struct SMgrRelationData *rd_smgr;	/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -285,6 +285,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
@@ -543,11 +544,13 @@ typedef struct ViewOptions
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -555,6 +558,7 @@ typedef struct ViewOptions
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -567,6 +571,14 @@ typedef struct ViewOptions
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is either local temp relation of this session
+ * 		or global temp relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -610,6 +622,17 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..ca2d135
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,14 @@
+reset search_path;
+select pg_sleep(5);
+ pg_sleep 
+----------
+ 
+(1 row)
+
+drop schema gtt cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
+drop cascades to table gtt.gtt_with_seq
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..889e3ff
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,362 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+ n 
+---
+ 9
+(1 row)
+
+commit;
+-- 0 row
+select * from gtt6;
+ n 
+---
+(0 rows)
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+ERROR:  not support cluster global temporary tables yet
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temp table
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  could not create global temporary table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test;
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR:  insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count 
+-------
+     1
+(1 row)
+
+select count(*) from orders;
+ count 
+-------
+     0
+(1 row)
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  2 |  3
+(2 rows)
+
+commit;
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+select * from gtt_s_2 order by c1;
+ c1 | c2 
+----+----
+  1 |  2
+  2 |  4
+(2 rows)
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 1)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 300)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 23 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.gtt6
+drop cascades to table gtt_function.foo
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to sequence gtt_function.seq_1
+drop cascades to table gtt_function.gtt_s_1
+drop cascades to table gtt_function.gtt_s_2
+drop cascades to table gtt_function.gt1
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..0646aae
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,90 @@
+set search_path=gtt,sys;
+select nextval('gtt_with_seq_c2_seq');
+ nextval 
+---------
+       1
+(1 row)
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..de2373c
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,287 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+rollback;
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+commit;
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          114688 |                 614400
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |            49152 |         49152 |                  49152
+(2 rows)
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+ c1 | c2 
+----+----
+  1 |  1
+(1 row)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..8c0c376
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,10 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..4420fdb
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,80 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+ gtt_stats  | gtt_pkey  |        1 |         0 |             0
+(2 rows)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+reindex table gtt;
+reindex index gtt_pkey;
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+ gtt_stats  | gtt_pkey  |       30 |     10000 |             0
+(2 rows)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 7245b0e..649c349 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1349,6 +1349,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d2b17dd..80e577f 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..2c8e586
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,8 @@
+
+
+reset search_path;
+
+select pg_sleep(5);
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..af2bb20
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,239 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+commit;
+-- 0 row
+select * from gtt6;
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test;
+
+-- ok
+ALTER TABLE gtt_test ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+commit;
+select * from gtt_s_1 order by c1;
+select * from gtt_s_2 order by c1;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d05745e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,44 @@
+
+
+set search_path=gtt,sys;
+
+select nextval('gtt_with_seq_c2_seq');
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..3ac81a1
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,123 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+rollback;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+commit;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..dbe84d1
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,19 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..d61b0ff
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,46 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+reindex table gtt;
+
+reindex index gtt_pkey;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#218Prabhat Sahu
prabhat.sahu@enterprisedb.com
In reply to: 曾文旌 (#217)
Re: [Proposal] Global temporary tables

Hi Wenjing,
Thanks for the new patch.
I saw with the patch(gtt_v23.patch), we are supporting the new concept
"global temporary sequence"(i.e. session-specific sequence), is this
intentional?

postgres=# create *global temporary sequence* gt_seq;
CREATE SEQUENCE
postgres=# create sequence seq;
CREATE SEQUENCE
postgres=# \d+
List of relations
Schema | Name | Type | Owner | Persistence | Size |
Description
--------+--------+----------+-------+-------------+------------+-------------
*public | gt_seq | sequence | edb | session | 8192 bytes |*
public | seq | sequence | edb | permanent | 8192 bytes |
(2 rows)

postgres=# select *nextval('gt_seq')*, nextval('seq');
nextval | nextval
---------+---------
* 1* | 1
(1 row)

postgres=# select nextval('gt_seq'), nextval('seq');
nextval | nextval
---------+---------
*2* | 2
(1 row)

-- Exit and re-connect to psql prompt:
postgres=# \q
[edb@localhost bin]$ ./psql postgres
psql (13devel)
Type "help" for help.

postgres=# select nextval('gt_seq'), nextval('seq');
nextval | nextval
---------+---------
* 1* | 3
(1 row)

postgres=# select nextval('gt_seq'), nextval('seq');
nextval | nextval
---------+---------
*2 *| 4
(1 row)

On Tue, Mar 31, 2020 at 9:46 AM 曾文旌 <wenjing.zwj@alibaba-inc.com> wrote:

2020年3月27日 下午5:21,tushar <tushar.ahuja@enterprisedb.com> 写道:

On 3/27/20 10:55 AM, 曾文旌 wrote:

Hi Wenjing,
This patch(gtt_v21_pg13.patch) is not applicable on PG HEAD, I hope you
have prepared the patch on top of some previous commit.
Could you please rebase the patch which we can apply on HEAD ?

Yes, It looks like the built-in functions are in conflict with new code.

This error message looks wrong to me-

postgres=# reindex table concurrently t ;
ERROR: cannot create indexes on global temporary tables using concurrent
mode
postgres=#

Better message would be-

ERROR: cannot reindex global temporary tables concurrently

I found that the local temp table automatically disables concurrency mode.
so, I made some improvements, The reindex GTT behaves the same as the
local temp table.

Wenjing

--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

#219曾文旌
wenjing.zwj@alibaba-inc.com
In reply to: Prabhat Sahu (#218)
1 attachment(s)
Re: [Proposal] Global temporary tables

2020年3月31日 下午9:59,Prabhat Sahu <prabhat.sahu@enterprisedb.com> 写道:

Hi Wenjing,
Thanks for the new patch.
I saw with the patch(gtt_v23.patch), we are supporting the new concept "global temporary sequence"(i.e. session-specific sequence), is this intentional?

It was supported in earlier versions,
This causes the sequence built into the GTT to automatically become a "global temp sequence",
Such as create global temp table (a serial);
Like GTT, the global temp sequnce is used individually for each session.

Recently, I added the global temp sequence syntax so that it can be created independently.
The purpose of this is to enable such sequence built into the GTT to support pg_dump and pg_restore.

Wenjing

Show quoted text

postgres=# create global temporary sequence gt_seq;
CREATE SEQUENCE
postgres=# create sequence seq;
CREATE SEQUENCE
postgres=# \d+
List of relations
Schema | Name | Type | Owner | Persistence | Size | Description
--------+--------+----------+-------+-------------+------------+-------------
public | gt_seq | sequence | edb | session | 8192 bytes |
public | seq | sequence | edb | permanent | 8192 bytes |
(2 rows)

postgres=# select nextval('gt_seq'), nextval('seq');
nextval | nextval
---------+---------
1 | 1
(1 row)

postgres=# select nextval('gt_seq'), nextval('seq');
nextval | nextval
---------+---------
2 | 2
(1 row)

-- Exit and re-connect to psql prompt:
postgres=# \q
[edb@localhost bin]$ ./psql postgres
psql (13devel)
Type "help" for help.

postgres=# select nextval('gt_seq'), nextval('seq');
nextval | nextval
---------+---------
1 | 3
(1 row)

postgres=# select nextval('gt_seq'), nextval('seq');
nextval | nextval
---------+---------
2 | 4
(1 row)

On Tue, Mar 31, 2020 at 9:46 AM 曾文旌 <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> wrote:

2020年3月27日 下午5:21,tushar <tushar.ahuja@enterprisedb.com <mailto:tushar.ahuja@enterprisedb.com>> 写道:

On 3/27/20 10:55 AM, 曾文旌 wrote:

Hi Wenjing,
This patch(gtt_v21_pg13.patch) is not applicable on PG HEAD, I hope you have prepared the patch on top of some previous commit.
Could you please rebase the patch which we can apply on HEAD ?

Yes, It looks like the built-in functions are in conflict with new code.

This error message looks wrong to me-

postgres=# reindex table concurrently t ;
ERROR: cannot create indexes on global temporary tables using concurrent mode
postgres=#

Better message would be-

ERROR: cannot reindex global temporary tables concurrently

I found that the local temp table automatically disables concurrency mode.
so, I made some improvements, The reindex GTT behaves the same as the local temp table.

Wenjing

--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/ <https://www.enterprisedb.com/&gt;
The Enterprise PostgreSQL Company

--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

Attachments:

smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#220Prabhat Sahu
prabhat.sahu@enterprisedb.com
In reply to: 曾文旌 (#219)
Re: [Proposal] Global temporary tables

On Wed, Apr 1, 2020 at 8:52 AM 曾文旌 <wenjing.zwj@alibaba-inc.com> wrote:

2020年3月31日 下午9:59,Prabhat Sahu <prabhat.sahu@enterprisedb.com> 写道:

Hi Wenjing,
Thanks for the new patch.
I saw with the patch(gtt_v23.patch), we are supporting the new concept
"global temporary sequence"(i.e. session-specific sequence), is this
intentional?

It was supported in earlier versions,

yes.

This causes the sequence built into the GTT to automatically become a

"global temp sequence",
Such as create global temp table (a serial);
Like GTT, the global temp sequnce is used individually for each session.

Recently, I added the global temp sequence syntax so that it can be
created independently.
The purpose of this is to enable such sequence built into the GTT to
support pg_dump and pg_restore.

Thanks for the explanation.

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

#221Prabhat Sahu
prabhat.sahu@enterprisedb.com
In reply to: Prabhat Sahu (#220)
Re: [Proposal] Global temporary tables

Hi Wenjing,
I hope we need to change the below error message.

postgres=# create global temporary table gtt(c1 int) on commit preserve
rows;
CREATE TABLE

postgres=# create materialized view mvw as select * from gtt;
ERROR: materialized views must not use global temporary tables* or views*

Anyways we are not allowed to create a "global temporary view",
so the above ERROR message should change(i.e. *" or view"* need to be
removed from the error message) something like:
*"ERROR: materialized views must not use global temporary tables"*

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

#222Prabhat Sahu
prabhat.sahu@enterprisedb.com
In reply to: Prabhat Sahu (#221)
Re: [Proposal] Global temporary tables

Hi All,

I have noted down few behavioral difference in our GTT implementation in PG
as compared to Oracle DB:
As per my understanding, the behavior of DROP TABLE in case of "Normal
table and GTT" in Oracle DB are as below:

1. Any tables(Normal table / GTT) without having data in a session, we
will be able to DROP from another session.
2. For a completed transaction on a normal table having data, we will be
able to DROP from another session. If the transaction is not yet complete,
and we are trying to drop the table from another session, then we will get
an error. (working as expected)
3. For a completed transaction on GTT with(on commit delete rows) (i.e.
no data in GTT) in a session, we will be able to DROP from another session.
4. For a completed transaction on GTT with(on commit preserve rows) with
data in a session, we will not be able to DROP from any session(not even
from the session in which GTT is created), we need to truncate the table
data first from all the session(session1, session2) which is having data.

*1. Any tables(Normal table / GTT) without having data in a session, we
will be able to DROP from another session.*
*Session1:*
create table t1 (c1 integer);
create global temporary table gtt1 (c1 integer) on commit delete rows;
create global temporary table gtt2 (c1 integer) on commit preserve rows;

*Session2:*
drop table t1;
drop table gtt1;
drop table gtt2;

-- *Issue 1:* But we are able to drop a simple table and failed to drop GTT
as below.

postgres=# drop table t1;
DROP TABLE
postgres=# drop table gtt1;
ERROR: can not drop relation gtt1 when other backend attached this global
temp table
postgres=# drop table gtt2;
ERROR: can not drop relation gtt2 when other backend attached this global
temp table

*3. For a completed transaction on GTT with(on commit delete rows) (i.e. no
data in GTT) in a session, we will be able to DROP from another session.*

*Session1:*create global temporary table gtt1 (c1 integer) on commit delete
rows;

*Session2:*
drop table gtt1;

-- *Issue 2:* But we are getting error for GTT with(on_commit_delete_rows)
without data.

postgres=# drop table gtt1;
ERROR: can not drop relation gtt1 when other backend attached this global
temp table

*4. For a completed transaction on GTT with(on commit preserve rows) with
data in any session, we will not be able to DROP from any session(not even
from the session in which GTT is created)*

*Case1:*
create global temporary table gtt2 (c1 integer) on commit preserve rows;
insert into gtt2 values(100);
drop table gtt2;

SQL> drop table gtt2;
drop table gtt2
*
ERROR at line 1:
ORA-14452: attempt to create, alter or drop an index on temporary table
already in use

-- *Issue 3:* But, we are able to drop the GTT(having data) which we have
created in the same session.

postgres=# drop table gtt2;
DROP TABLE

*Case2: GTT with(on commit preserve rows) having data in both session1 and
session2Session1:*create global temporary table gtt2 (c1 integer) on commit
preserve rows;
insert into gtt2 values(100);

*Session2:*insert into gtt2 values(200);

-- If we try to drop the table from any session we should get an error, it
is working fine.
drop table gtt2;

SQL> drop table gtt2;
drop table gtt2
*
ERROR at line 1:
ORA-14452: attempt to create, alter or drop an index on temporary table
already in use

postgres=# drop table gtt2 ;
ERROR: can not drop relation gtt2 when other backend attached this global
temp table

-- To drop the table gtt2 from any session1/session2, we need to truncate
the table data first from all the session(session1, session2) which is
having data.
*Session1:*
truncate table gtt2;
-- Session2:
truncate table gtt2;

*Session 2:*
SQL> drop table gtt2;

Table dropped.

-- *Issue 4:* But we are not able to drop the GTT, even after TRUNCATE the
table in all the sessions.
-- truncate from all sessions where GTT have data.
postgres=# truncate gtt2 ;
TRUNCATE TABLE

-- *try to DROP GTT still, we are getting error.*

postgres=# drop table gtt2 ;
ERROR: can not drop relation gtt2 when other backend attached this global
temp table

To drop the GTT from any session, we need to exit from all other sessions.
postgres=# drop table gtt2 ;
DROP TABLE

Kindly let me know if I am missing something.

On Wed, Apr 1, 2020 at 6:26 PM Prabhat Sahu <prabhat.sahu@enterprisedb.com>
wrote:

Hi Wenjing,
I hope we need to change the below error message.

postgres=# create global temporary table gtt(c1 int) on commit preserve
rows;
CREATE TABLE

postgres=# create materialized view mvw as select * from gtt;
ERROR: materialized views must not use global temporary tables* or views*

Anyways we are not allowed to create a "global temporary view",
so the above ERROR message should change(i.e. *" or view"* need to be
removed from the error message) something like:
*"ERROR: materialized views must not use global temporary tables"*

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

#223Pavel Stehule
pavel.stehule@gmail.com
In reply to: Prabhat Sahu (#222)
Re: [Proposal] Global temporary tables

čt 2. 4. 2020 v 10:45 odesílatel Prabhat Sahu <prabhat.sahu@enterprisedb.com>
napsal:

Hi All,

I have noted down few behavioral difference in our GTT implementation in
PG as compared to Oracle DB:
As per my understanding, the behavior of DROP TABLE in case of "Normal
table and GTT" in Oracle DB are as below:

1. Any tables(Normal table / GTT) without having data in a session, we
will be able to DROP from another session.
2. For a completed transaction on a normal table having data, we will
be able to DROP from another session. If the transaction is not yet
complete, and we are trying to drop the table from another session, then we
will get an error. (working as expected)
3. For a completed transaction on GTT with(on commit delete rows)
(i.e. no data in GTT) in a session, we will be able to DROP from another
session.
4. For a completed transaction on GTT with(on commit preserve rows)
with data in a session, we will not be able to DROP from any session(not
even from the session in which GTT is created), we need to truncate the
table data first from all the session(session1, session2) which is having
data.

*1. Any tables(Normal table / GTT) without having data in a session, we
will be able to DROP from another session.*
*Session1:*
create table t1 (c1 integer);
create global temporary table gtt1 (c1 integer) on commit delete rows;
create global temporary table gtt2 (c1 integer) on commit preserve rows;

*Session2:*
drop table t1;
drop table gtt1;
drop table gtt2;

-- *Issue 1:* But we are able to drop a simple table and failed to drop
GTT as below.

postgres=# drop table t1;
DROP TABLE
postgres=# drop table gtt1;
ERROR: can not drop relation gtt1 when other backend attached this global
temp table
postgres=# drop table gtt2;
ERROR: can not drop relation gtt2 when other backend attached this global
temp table

I think so this is expected behave. It was proposed for first release - and
for next releases there can be support for DROP TABLE with force option
like DROP DATABASE (force).

Regards

Pavel

Show quoted text

*3. For a completed transaction on GTT with(on commit delete rows) (i.e.
no data in GTT) in a session, we will be able to DROP from another session.*

*Session1:*create global temporary table gtt1 (c1 integer) on commit
delete rows;

*Session2:*
drop table gtt1;

-- *Issue 2:* But we are getting error for GTT
with(on_commit_delete_rows) without data.

postgres=# drop table gtt1;
ERROR: can not drop relation gtt1 when other backend attached this global
temp table

*4. For a completed transaction on GTT with(on commit preserve rows) with
data in any session, we will not be able to DROP from any session(not even
from the session in which GTT is created)*

*Case1:*
create global temporary table gtt2 (c1 integer) on commit preserve rows;
insert into gtt2 values(100);
drop table gtt2;

SQL> drop table gtt2;
drop table gtt2
*
ERROR at line 1:
ORA-14452: attempt to create, alter or drop an index on temporary table
already in use

-- *Issue 3:* But, we are able to drop the GTT(having data) which we have
created in the same session.

postgres=# drop table gtt2;
DROP TABLE

*Case2: GTT with(on commit preserve rows) having data in both session1 and
session2Session1:*create global temporary table gtt2 (c1 integer) on
commit preserve rows;
insert into gtt2 values(100);

*Session2:*insert into gtt2 values(200);

-- If we try to drop the table from any session we should get an error, it
is working fine.
drop table gtt2;

SQL> drop table gtt2;
drop table gtt2
*
ERROR at line 1:
ORA-14452: attempt to create, alter or drop an index on temporary table
already in use

postgres=# drop table gtt2 ;
ERROR: can not drop relation gtt2 when other backend attached this global
temp table

-- To drop the table gtt2 from any session1/session2, we need to truncate
the table data first from all the session(session1, session2) which is
having data.
*Session1:*
truncate table gtt2;
-- Session2:
truncate table gtt2;

*Session 2:*
SQL> drop table gtt2;

Table dropped.

-- *Issue 4:* But we are not able to drop the GTT, even after TRUNCATE
the table in all the sessions.
-- truncate from all sessions where GTT have data.
postgres=# truncate gtt2 ;
TRUNCATE TABLE

-- *try to DROP GTT still, we are getting error.*

postgres=# drop table gtt2 ;
ERROR: can not drop relation gtt2 when other backend attached this global
temp table

To drop the GTT from any session, we need to exit from all other sessions.
postgres=# drop table gtt2 ;
DROP TABLE

Kindly let me know if I am missing something.

On Wed, Apr 1, 2020 at 6:26 PM Prabhat Sahu <prabhat.sahu@enterprisedb.com>
wrote:

Hi Wenjing,
I hope we need to change the below error message.

postgres=# create global temporary table gtt(c1 int) on commit preserve
rows;
CREATE TABLE

postgres=# create materialized view mvw as select * from gtt;
ERROR: materialized views must not use global temporary tables* or views*

Anyways we are not allowed to create a "global temporary view",
so the above ERROR message should change(i.e. *" or view"* need to be
removed from the error message) something like:
*"ERROR: materialized views must not use global temporary tables"*

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

#224曾文旌
wenjing.zwj@alibaba-inc.com
In reply to: Prabhat Sahu (#222)
1 attachment(s)
Re: [Proposal] Global temporary tables

In my opinion
1 We are developing GTT according to the SQL standard, not Oracle.

2 The implementation differences you listed come from pg and oracle storage modules and DDL implementations.

2.1 issue 1 and issue 2
The creation of Normal table/GTT defines the catalog and initializes the data store file, in the case of the GTT, which initializes the store file for the current session.
But in oracle It just looks like only defines the catalog.
This causes other sessions can not drop the GTT in PostgreSQL.
This is the reason for issue 1 and issue 2, I think it is reasonable.

2.2 issue 3
I thinking the logic of drop GTT is
When only the current session is using the GTT, it is safe to drop the GTT.
because the GTT's definition and storage files can completely delete from db.
But, If multiple sessions are using this GTT, it is hard to drop GTT in session a, because remove the local buffer and data file of the GTT in other session is difficult.
I am not sure why oracle has this limitation.
So, issue 3 is reasonable.

2.3 TRUNCATE Normal table/GTT
TRUNCATE Normal table / GTT clean up the logical data but not unlink data store file. in the case of the GTT, which is the store file for the current session.
But in oracle, It just looks like data store file was cleaned up.
PostgreSQL storage is obviously different from oracle, In other words, session is detached from storage.
This is the reason for issue4 I think it is reasonable.

All in all, I think the current implementation is sufficient for dba to manage GTT.

Show quoted text

2020年4月2日 下午4:45,Prabhat Sahu <prabhat.sahu@enterprisedb.com> 写道:

Hi All,

I have noted down few behavioral difference in our GTT implementation in PG as compared to Oracle DB:
As per my understanding, the behavior of DROP TABLE in case of "Normal table and GTT" in Oracle DB are as below:
Any tables(Normal table / GTT) without having data in a session, we will be able to DROP from another session.
For a completed transaction on a normal table having data, we will be able to DROP from another session. If the transaction is not yet complete, and we are trying to drop the table from another session, then we will get an error. (working as expected)
For a completed transaction on GTT with(on commit delete rows) (i.e. no data in GTT) in a session, we will be able to DROP from another session.
For a completed transaction on GTT with(on commit preserve rows) with data in a session, we will not be able to DROP from any session(not even from the session in which GTT is created), we need to truncate the table data first from all the session(session1, session2) which is having data.
1. Any tables(Normal table / GTT) without having data in a session, we will be able to DROP from another session.
Session1:
create table t1 (c1 integer);
create global temporary table gtt1 (c1 integer) on commit delete rows;
create global temporary table gtt2 (c1 integer) on commit preserve rows;

Session2:
drop table t1;
drop table gtt1;
drop table gtt2;

-- Issue 1: But we are able to drop a simple table and failed to drop GTT as below.
postgres=# drop table t1;
DROP TABLE
postgres=# drop table gtt1;
ERROR: can not drop relation gtt1 when other backend attached this global temp table
postgres=# drop table gtt2;
ERROR: can not drop relation gtt2 when other backend attached this global temp table

3. For a completed transaction on GTT with(on commit delete rows) (i.e. no data in GTT) in a session, we will be able to DROP from another session.
Session1:
create global temporary table gtt1 (c1 integer) on commit delete rows;

Session2:
drop table gtt1;

-- Issue 2: But we are getting error for GTT with(on_commit_delete_rows) without data.
postgres=# drop table gtt1;
ERROR: can not drop relation gtt1 when other backend attached this global temp table

4. For a completed transaction on GTT with(on commit preserve rows) with data in any session, we will not be able to DROP from any session(not even from the session in which GTT is created)

Case1:
create global temporary table gtt2 (c1 integer) on commit preserve rows;
insert into gtt2 values(100);
drop table gtt2;

SQL> drop table gtt2;
drop table gtt2
*
ERROR at line 1:
ORA-14452: attempt to create, alter or drop an index on temporary table already in use

-- Issue 3: But, we are able to drop the GTT(having data) which we have created in the same session.
postgres=# drop table gtt2;
DROP TABLE

Case2: GTT with(on commit preserve rows) having data in both session1 and session2
Session1:
create global temporary table gtt2 (c1 integer) on commit preserve rows;
insert into gtt2 values(100);

Session2:
insert into gtt2 values(200);

-- If we try to drop the table from any session we should get an error, it is working fine.
drop table gtt2;
SQL> drop table gtt2;
drop table gtt2
*
ERROR at line 1:
ORA-14452: attempt to create, alter or drop an index on temporary table already in use

postgres=# drop table gtt2 ;
ERROR: can not drop relation gtt2 when other backend attached this global temp table

-- To drop the table gtt2 from any session1/session2, we need to truncate the table data first from all the session(session1, session2) which is having data.
Session1:
truncate table gtt2;
-- Session2:
truncate table gtt2;

Session 2:
SQL> drop table gtt2;

Table dropped.

-- Issue 4: But we are not able to drop the GTT, even after TRUNCATE the table in all the sessions.
-- truncate from all sessions where GTT have data.
postgres=# truncate gtt2 ;
TRUNCATE TABLE

-- try to DROP GTT still, we are getting error.
postgres=# drop table gtt2 ;
ERROR: can not drop relation gtt2 when other backend attached this global temp table

To drop the GTT from any session, we need to exit from all other sessions.
postgres=# drop table gtt2 ;
DROP TABLE

Kindly let me know if I am missing something.

On Wed, Apr 1, 2020 at 6:26 PM Prabhat Sahu <prabhat.sahu@enterprisedb.com <mailto:prabhat.sahu@enterprisedb.com>> wrote:
Hi Wenjing,
I hope we need to change the below error message.

postgres=# create global temporary table gtt(c1 int) on commit preserve rows;
CREATE TABLE

postgres=# create materialized view mvw as select * from gtt;
ERROR: materialized views must not use global temporary tables or views

Anyways we are not allowed to create a "global temporary view",
so the above ERROR message should change(i.e. " or view" need to be removed from the error message) something like:
"ERROR: materialized views must not use global temporary tables"

--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

Attachments:

smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#225Pavel Stehule
pavel.stehule@gmail.com
In reply to: 曾文旌 (#224)
Re: [Proposal] Global temporary tables

pá 3. 4. 2020 v 9:52 odesílatel 曾文旌 <wenjing.zwj@alibaba-inc.com> napsal:

In my opinion
1 We are developing GTT according to the SQL standard, not Oracle.

2 The implementation differences you listed come from pg and oracle
storage modules and DDL implementations.

2.1 issue 1 and issue 2
The creation of Normal table/GTT defines the catalog and initializes the
data store file, in the case of the GTT, which initializes the store file
for the current session.
But in oracle It just looks like only defines the catalog.
This causes other sessions can not drop the GTT in PostgreSQL.
This is the reason for issue 1 and issue 2, I think it is reasonable.

2.2 issue 3
I thinking the logic of drop GTT is
When only the current session is using the GTT, it is safe to drop the
GTT.
because the GTT's definition and storage files can completely delete from
db.
But, If multiple sessions are using this GTT, it is hard to drop GTT in
session a, because remove the local buffer and data file of the GTT in
other session is difficult.
I am not sure why oracle has this limitation.
So, issue 3 is reasonable.

2.3 TRUNCATE Normal table/GTT
TRUNCATE Normal table / GTT clean up the logical data but not unlink data
store file. in the case of the GTT, which is the store file for the
current session.
But in oracle, It just looks like data store file was cleaned up.
PostgreSQL storage is obviously different from oracle, In other words,
session is detached from storage.
This is the reason for issue4 I think it is reasonable.

Although the implementation of GTT is different, I think so TRUNCATE on
Postgres (when it is really finalized) can remove session metadata of GTT
too (and reduce usage's counter). It is not critical feature, but I think
so it should not be hard to implement. From practical reason can be nice to
have a tool how to refresh GTT without a necessity to close session.
TRUNCATE can be this tool.

Regards

Pavel

Show quoted text

All in all, I think the current implementation is sufficient for dba to
manage GTT.

2020年4月2日 下午4:45,Prabhat Sahu <prabhat.sahu@enterprisedb.com> 写道:

Hi All,

I have noted down few behavioral difference in our GTT implementation in
PG as compared to Oracle DB:
As per my understanding, the behavior of DROP TABLE in case of "Normal
table and GTT" in Oracle DB are as below:

1. Any tables(Normal table / GTT) without having data in a session, we
will be able to DROP from another session.
2. For a completed transaction on a normal table having data, we will
be able to DROP from another session. If the transaction is not yet
complete, and we are trying to drop the table from another session, then we
will get an error. (working as expected)
3. For a completed transaction on GTT with(on commit delete rows)
(i.e. no data in GTT) in a session, we will be able to DROP from another
session.
4. For a completed transaction on GTT with(on commit preserve rows)
with data in a session, we will not be able to DROP from any session(not
even from the session in which GTT is created), we need to truncate the
table data first from all the session(session1, session2) which is having
data.

*1. Any tables(Normal table / GTT) without having data in a session, we
will be able to DROP from another session.*
*Session1:*
create table t1 (c1 integer);
create global temporary table gtt1 (c1 integer) on commit delete rows;
create global temporary table gtt2 (c1 integer) on commit preserve rows;

*Session2:*
drop table t1;
drop table gtt1;
drop table gtt2;

-- *Issue 1:* But we are able to drop a simple table and failed to drop
GTT as below.

postgres=# drop table t1;
DROP TABLE
postgres=# drop table gtt1;
ERROR: can not drop relation gtt1 when other backend attached this global
temp table
postgres=# drop table gtt2;
ERROR: can not drop relation gtt2 when other backend attached this global
temp table

*3. For a completed transaction on GTT with(on commit delete rows) (i.e.
no data in GTT) in a session, we will be able to DROP from another session.*

*Session1:*create global temporary table gtt1 (c1 integer) on commit
delete rows;

*Session2:*
drop table gtt1;

-- *Issue 2:* But we are getting error for GTT
with(on_commit_delete_rows) without data.

postgres=# drop table gtt1;
ERROR: can not drop relation gtt1 when other backend attached this global
temp table

*4. For a completed transaction on GTT with(on commit preserve rows) with
data in any session, we will not be able to DROP from any session(not even
from the session in which GTT is created)*

*Case1:*
create global temporary table gtt2 (c1 integer) on commit preserve rows;
insert into gtt2 values(100);
drop table gtt2;

SQL> drop table gtt2;
drop table gtt2
*
ERROR at line 1:
ORA-14452: attempt to create, alter or drop an index on temporary table
already in use

-- *Issue 3:* But, we are able to drop the GTT(having data) which we have
created in the same session.

postgres=# drop table gtt2;
DROP TABLE

*Case2: GTT with(on commit preserve rows) having data in both session1 and
session2Session1:*create global temporary table gtt2 (c1 integer) on
commit preserve rows;
insert into gtt2 values(100);

*Session2:*insert into gtt2 values(200);

-- If we try to drop the table from any session we should get an error, it
is working fine.
drop table gtt2;

SQL> drop table gtt2;
drop table gtt2
*
ERROR at line 1:
ORA-14452: attempt to create, alter or drop an index on temporary table
already in use

postgres=# drop table gtt2 ;
ERROR: can not drop relation gtt2 when other backend attached this global
temp table

-- To drop the table gtt2 from any session1/session2, we need to truncate
the table data first from all the session(session1, session2) which is
having data.
*Session1:*
truncate table gtt2;
-- Session2:
truncate table gtt2;

*Session 2:*
SQL> drop table gtt2;

Table dropped.

-- *Issue 4:* But we are not able to drop the GTT, even after TRUNCATE
the table in all the sessions.
-- truncate from all sessions where GTT have data.
postgres=# truncate gtt2 ;
TRUNCATE TABLE

-- *try to DROP GTT still, we are getting error.*

postgres=# drop table gtt2 ;
ERROR: can not drop relation gtt2 when other backend attached this global
temp table

To drop the GTT from any session, we need to exit from all other sessions.
postgres=# drop table gtt2 ;
DROP TABLE

Kindly let me know if I am missing something.

On Wed, Apr 1, 2020 at 6:26 PM Prabhat Sahu <prabhat.sahu@enterprisedb.com>
wrote:

Hi Wenjing,
I hope we need to change the below error message.

postgres=# create global temporary table gtt(c1 int) on commit preserve
rows;
CREATE TABLE

postgres=# create materialized view mvw as select * from gtt;
ERROR: materialized views must not use global temporary tables* or views*

Anyways we are not allowed to create a "global temporary view",
so the above ERROR message should change(i.e. *" or view"* need to be
removed from the error message) something like:
*"ERROR: materialized views must not use global temporary tables"*

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

#226Prabhat Sahu
prabhat.sahu@enterprisedb.com
In reply to: Pavel Stehule (#225)
Re: [Proposal] Global temporary tables

Hi Wenjing,

Please check the allowed values for boolean parameter
"on_commit_delete_rows".

postgres=# create global temp table gtt1(c1 int)
with(on_commit_delete_rows='true');
CREATE TABLE
Similarly we can successfully create GTT by using the values as:
'true','false', true, false, 'ON', 'OFF', ON, OFF, 1, 0 for boolean
parameter "on_commit_delete_rows"

But we are getting error while using the boolean value as: '1', '0', 't',
'f', 'yes', 'no', 'y', 'n' as below.
postgres=# create global temp table gtt11(c1 int)
with(on_commit_delete_rows='1');
ERROR: on_commit_delete_rows requires a Boolean value
postgres=# create global temp table gtt11(c1 int)
with(on_commit_delete_rows='0');
ERROR: on_commit_delete_rows requires a Boolean value
postgres=# create global temp table gtt11(c1 int)
with(on_commit_delete_rows='t');
ERROR: on_commit_delete_rows requires a Boolean value
postgres=# create global temp table gtt11(c1 int)
with(on_commit_delete_rows='f');
ERROR: on_commit_delete_rows requires a Boolean value
postgres=# create global temp table gtt11(c1 int)
with(on_commit_delete_rows='yes');
ERROR: on_commit_delete_rows requires a Boolean value
postgres=# create global temp table gtt11(c1 int)
with(on_commit_delete_rows='no');
ERROR: on_commit_delete_rows requires a Boolean value
postgres=# create global temp table gtt11(c1 int)
with(on_commit_delete_rows='y');
ERROR: on_commit_delete_rows requires a Boolean value
postgres=# create global temp table gtt11(c1 int)
with(on_commit_delete_rows='n');
ERROR: on_commit_delete_rows requires a Boolean value

-- As per the error message "ERROR: on_commit_delete_rows requires a
Boolean value" either we should allow all the boolean values.

*Example*: CREATE VIEW view1 WITH (security_barrier = 'true') as select 5;
The syntax of VIEW allows all the above possible boolean values for the
boolean parameter "security_barrier"

-- or else we should change the error message something like
"ERROR: on_commit_delete_rows requires 'true','false','ON','OFF',1,0 as
Boolean value".

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

#227曾文旌
wenjing.zwj@alibaba-inc.com
In reply to: Prabhat Sahu (#226)
2 attachment(s)
Re: [Proposal] Global temporary tables

2020年4月3日 下午8:43,Prabhat Sahu <prabhat.sahu@enterprisedb.com> 写道:

Hi Wenjing,

Please check the allowed values for boolean parameter "on_commit_delete_rows".

postgres=# create global temp table gtt1(c1 int) with(on_commit_delete_rows='true');
CREATE TABLE
Similarly we can successfully create GTT by using the values as: 'true','false', true, false, 'ON', 'OFF', ON, OFF, 1, 0 for boolean parameter "on_commit_delete_rows"

But we are getting error while using the boolean value as: '1', '0', 't', 'f', 'yes', 'no', 'y', 'n' as below.
postgres=# create global temp table gtt11(c1 int) with(on_commit_delete_rows='1');
ERROR: on_commit_delete_rows requires a Boolean value
postgres=# create global temp table gtt11(c1 int) with(on_commit_delete_rows='0');
ERROR: on_commit_delete_rows requires a Boolean value
postgres=# create global temp table gtt11(c1 int) with(on_commit_delete_rows='t');
ERROR: on_commit_delete_rows requires a Boolean value
postgres=# create global temp table gtt11(c1 int) with(on_commit_delete_rows='f');
ERROR: on_commit_delete_rows requires a Boolean value
postgres=# create global temp table gtt11(c1 int) with(on_commit_delete_rows='yes');
ERROR: on_commit_delete_rows requires a Boolean value
postgres=# create global temp table gtt11(c1 int) with(on_commit_delete_rows='no');
ERROR: on_commit_delete_rows requires a Boolean value
postgres=# create global temp table gtt11(c1 int) with(on_commit_delete_rows='y');
ERROR: on_commit_delete_rows requires a Boolean value
postgres=# create global temp table gtt11(c1 int) with(on_commit_delete_rows='n');
ERROR: on_commit_delete_rows requires a Boolean value

Thanks for review.
This parameter should support all types of writing of the bool type like parameter autovacuum_enabled.
So I fixed in global_temporary_table_v24-pg13.patch.

Wenjing

Show quoted text

-- As per the error message "ERROR: on_commit_delete_rows requires a Boolean value" either we should allow all the boolean values.
Example: CREATE VIEW view1 WITH (security_barrier = 'true') as select 5;
The syntax of VIEW allows all the above possible boolean values for the boolean parameter "security_barrier"

-- or else we should change the error message something like
"ERROR: on_commit_delete_rows requires 'true','false','ON','OFF',1,0 as Boolean value".

--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

Attachments:

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

2020年2月15日 下午6:06,Pavel Stehule <pavel.stehule@gmail.com> 写道:

postgres=# insert into foo select generate_series(1,10000);
INSERT 0 10000
postgres=# \dt+ foo
List of relations
┌────────┬──────┬───────┬───────┬─────────────┬────────┬─────────────┐
│ Schema │ Name │ Type │ Owner │ Persistence │ Size │ Description │
╞════════╪══════╪═══════╪═══════╪═════════════╪════════╪═════════════╡
│ public │ foo │ table │ pavel │ session │ 384 kB │ │
└────────┴──────┴───────┴───────┴─────────────┴────────┴─────────────┘
(1 row)

postgres=# truncate foo;
TRUNCATE TABLE
postgres=# \dt+ foo
List of relations
┌────────┬──────┬───────┬───────┬─────────────┬───────┬─────────────┐
│ Schema │ Name │ Type │ Owner │ Persistence │ Size │ Description │
╞════════╪══════╪═══════╪═══════╪═════════════╪═══════╪═════════════╡
│ public │ foo │ table │ pavel │ session │ 16 kB │ │
└────────┴──────┴───────┴───────┴─────────────┴───────┴─────────────┘
(1 row)

I expect zero size after truncate.

Thanks for review.

I can explain, I don't think it's a bug.
The current implementation of the truncated GTT retains two blocks of FSM pages.
The same is true for truncating regular tables in subtransactions.
This is an implementation that truncates the table without changing the relfilenode of the table.

This is not extra important feature - now this is little bit a surprise, because I was not under transaction.

Changing relfilenode, I think, is necessary, minimally for future VACUUM FULL support.

HI all

Vacuum full GTT, cluster GTT is already supported in global_temporary_table_v24-pg13.patch.

Wenjing

Show quoted text

Regards

Pavel Stehule

Wenjing

Regards

Pavel

Wenjing

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;
The Enterprise PostgreSQL Company

Attachments:

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

Thanks for review.
This parameter should support all types of writing of the bool type like
parameter autovacuum_enabled.
So I fixed in global_temporary_table_v24-pg13.patch.

Thank you Wenjing for the new patch with the fix and the "VACUUM FULL GTT"
support.
I have verified the above issue now its resolved.

Please check the below findings on VACUUM FULL.

postgres=# create global temporary table gtt(c1 int) on commit preserve
rows;
CREATE TABLE
postgres=# vacuum FULL ;
WARNING: global temp table oldest FrozenXid is far in the past
HINT: please truncate them or kill those sessions that use them.
VACUUM

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

#230Erik Rijkers
er@xs4all.nl
In reply to: 曾文旌 (#228)
Re: [Proposal] Global temporary tables

On 2020-04-07 10:57, 曾文旌 wrote:

[global_temporary_table_v24-pg13.patch ]

Hi,

With gcc 9.3.0 (debian stretch), I see some low-key protests during the
build:

index.c: In function ‘index_drop’:
index.c:2051:8: warning: variable ‘rel_persistence’ set but not used
[-Wunused-but-set-variable]
2051 | char rel_persistence;
| ^~~~~~~~~~~~~~~
storage_gtt.c: In function ‘gtt_force_enable_index’:
storage_gtt.c:1252:6: warning: unused variable ‘indexOid’
[-Wunused-variable]
1252 | Oid indexOid = RelationGetRelid(index);
| ^~~~~~~~
cluster.c: In function ‘copy_table_data’:
cluster.c:780:2: warning: this ‘if’ clause does not guard...
[-Wmisleading-indentation]
780 | if (RELATION_IS_GLOBAL_TEMP(OldHeap));
| ^~
cluster.c:781:3: note: ...this statement, but the latter is misleadingly
indented as if it were guarded by the ‘if’
781 | is_gtt = true;
| ^~~~~~

Erik

#231tushar
tushar.ahuja@enterprisedb.com
In reply to: 曾文旌 (#228)
Re: [Proposal] Global temporary tables

On 4/7/20 2:27 PM, 曾文旌 wrote:

Vacuum full GTT, cluster GTT is already
supported in global_temporary_table_v24-pg13.patch.

Here , it is skipping GTT

postgres=# \c foo
You are now connected to database "foo" as user "tushar".
foo=# create global temporary table  g123( c1 int) ;
CREATE TABLE
foo=# \q
[tushar@localhost bin]$ ./vacuumdb --full  foo
vacuumdb: vacuuming database "foo"
WARNING:  skipping vacuum global temp table "g123" because storage is
not initialized for current session

--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company

#232曾文旌
wenjing.zwj@alibaba-inc.com
In reply to: Prabhat Sahu (#229)
1 attachment(s)
Re: [Proposal] Global temporary tables

2020年4月7日 下午6:22,Prabhat Sahu <prabhat.sahu@enterprisedb.com> 写道:

Thanks for review.
This parameter should support all types of writing of the bool type like parameter autovacuum_enabled.
So I fixed in global_temporary_table_v24-pg13.patch.

Thank you Wenjing for the new patch with the fix and the "VACUUM FULL GTT" support.
I have verified the above issue now its resolved.

Please check the below findings on VACUUM FULL.

postgres=# create global temporary table gtt(c1 int) on commit preserve rows;
CREATE TABLE
postgres=# vacuum FULL ;
WARNING: global temp table oldest FrozenXid is far in the past
HINT: please truncate them or kill those sessions that use them.
VACUUM

This is expected,
This represents that the GTT FrozenXid is the oldest in the entire db, and dba should vacuum the GTT if he want to push the db datfrozenxid.
Also he can use function pg_list_gtt_relfrozenxids() to check which session has "too old” data and truncate them or kill the sessions.

Show quoted text

--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

Attachments:

smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#233tushar
tushar.ahuja@enterprisedb.com
In reply to: 曾文旌 (#228)
Re: [Proposal] Global temporary tables

On 4/7/20 2:27 PM, 曾文旌 wrote:

Vacuum full GTT, cluster GTT is already
supported in global_temporary_table_v24-pg13.patch.

Please refer this below scenario , where pg_upgrade is failing
1)Server is up and running (./pg_ctl -D data status)
2)Stop the server ( ./pg_ctl -D data stop)
3)Connect to server using single user mode ( ./postgres --single -D data
postgres) and create a global temp table
[tushar@localhost bin]$ ./postgres --single -D data1233 postgres

PostgreSQL stand-alone backend 13devel
backend> create global temp table t(n int);

--Press Ctl+D to exit

4)Perform initdb ( ./initdb -D data123)
5.Run pg_upgrade
[tushar@localhost bin]$ ./pg_upgrade -d data -D data123 -b . -B .
--
--
--
Restoring database schemas in the new cluster
  postgres
*failure*
Consult the last few lines of "pg_upgrade_dump_13592.log" for
the probable cause of the failure.
Failure, exiting

log file content  -

[tushar@localhost bin]$ tail -20   pg_upgrade_dump_13592.log
pg_restore: error: could not execute query: ERROR:  pg_type array OID
value not set when in binary upgrade mode
Command was:
-- For binary upgrade, must preserve pg_type oid
SELECT
pg_catalog.binary_upgrade_set_next_pg_type_oid('13594'::pg_catalog.oid);

-- For binary upgrade, must preserve pg_class oids
SELECT
pg_catalog.binary_upgrade_set_next_heap_pg_class_oid('13593'::pg_catalog.oid);

CREATE GLOBAL TEMPORARY TABLE "public"."t" (
    "n" integer
)
WITH ("on_commit_delete_rows"='false');

-- For binary upgrade, set heap's relfrozenxid and relminmxid
UPDATE pg_catalog.pg_class
SET relfrozenxid = '0', relminmxid = '0'
WHERE oid = '"public"."t"'::pg_catalog.regclass;

--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company

#234Prabhat Sahu
prabhat.sahu@enterprisedb.com
In reply to: 曾文旌 (#232)
Re: [Proposal] Global temporary tables

On Wed, Apr 8, 2020 at 1:48 PM 曾文旌 <wenjing.zwj@alibaba-inc.com> wrote:

2020年4月7日 下午6:22,Prabhat Sahu <prabhat.sahu@enterprisedb.com> 写道:

Thanks for review.

This parameter should support all types of writing of the bool type like
parameter autovacuum_enabled.
So I fixed in global_temporary_table_v24-pg13.patch.

Thank you Wenjing for the new patch with the fix and the "VACUUM FULL GTT"
support.
I have verified the above issue now its resolved.

Please check the below findings on VACUUM FULL.

postgres=# create global temporary table gtt(c1 int) on commit preserve
rows;
CREATE TABLE
postgres=# vacuum FULL ;
WARNING: global temp table oldest FrozenXid is far in the past
HINT: please truncate them or kill those sessions that use them.
VACUUM

This is expected,
This represents that the GTT FrozenXid is the oldest in the entire db, and
dba should vacuum the GTT if he want to push the db datfrozenxid.
Also he can use function pg_list_gtt_relfrozenxids() to check which
session has "too old” data and truncate them or kill the sessions.

Again as per the HINT given, as "HINT: please truncate them or kill those
sessions that use them."
There is only a single session.
If we try "TRUNCATE" and "VACUUM FULL" still the behavior is same as below.

postgres=# truncate gtt ;
TRUNCATE TABLE
postgres=# vacuum full;
WARNING: global temp table oldest FrozenXid is far in the past
HINT: please truncate them or kill those sessions that use them.
VACUUM

I have one more finding related to "CLUSTER table USING index", Please
check the below issue.
postgres=# create global temporary table gtt(c1 int) on commit preserve
rows;
CREATE TABLE
postgres=# create index idx1 ON gtt (c1);
CREATE INDEX

-- exit and re-connect the psql prompt
postgres=# \q
[edb@localhost bin]$ ./psql postgres
psql (13devel)
Type "help" for help.

postgres=# cluster gtt using idx1;
WARNING: relcache reference leak: relation "gtt" not closed
CLUSTER

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

#235tushar
tushar.ahuja@enterprisedb.com
In reply to: 曾文旌 (#228)
Re: [Proposal] Global temporary tables

On 4/7/20 2:27 PM, 曾文旌 wrote:

Vacuum full GTT, cluster GTT is already
supported in global_temporary_table_v24-pg13.patch.

Hi Wenjing,

Please refer this scenario , where reindex   message is not coming next
time ( after reconnecting to database) for GTT

A)
--normal table
postgres=# create table nt(n int primary key);
CREATE TABLE
--GTT table
postgres=# create global temp table gtt(n int primary key);
CREATE TABLE
B)
--Reindex  , normal table
postgres=# REINDEX (VERBOSE) TABLE  nt;
INFO:  index "nt_pkey" was reindexed
DETAIL:  CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s
REINDEX
--reindex GTT table
postgres=# REINDEX (VERBOSE) TABLE  gtt;
INFO:  index "gtt_pkey" was reindexed
DETAIL:  CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s
REINDEX
C)
--Reconnect  to database
postgres=# \c
You are now connected to database "postgres" as user "tushar".
D) again perform step B)

postgres=# REINDEX (VERBOSE) TABLE  nt;
INFO:  index "nt_pkey" was reindexed
DETAIL:  CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s
REINDEX
postgres=# REINDEX (VERBOSE) TABLE  gtt;   <-- message  not coming
REINDEX

--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company

#236曾文旌
wenjing.zwj@alibaba-inc.com
In reply to: Prabhat Sahu (#234)
2 attachment(s)
Re: [Proposal] Global temporary tables

2020年4月8日 下午6:55,Prabhat Sahu <prabhat.sahu@enterprisedb.com> 写道:

On Wed, Apr 8, 2020 at 1:48 PM 曾文旌 <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> wrote:

2020年4月7日 下午6:22,Prabhat Sahu <prabhat.sahu@enterprisedb.com <mailto:prabhat.sahu@enterprisedb.com>> 写道:

Thanks for review.
This parameter should support all types of writing of the bool type like parameter autovacuum_enabled.
So I fixed in global_temporary_table_v24-pg13.patch.

Thank you Wenjing for the new patch with the fix and the "VACUUM FULL GTT" support.
I have verified the above issue now its resolved.

Please check the below findings on VACUUM FULL.

postgres=# create global temporary table gtt(c1 int) on commit preserve rows;
CREATE TABLE
postgres=# vacuum FULL ;
WARNING: global temp table oldest FrozenXid is far in the past
HINT: please truncate them or kill those sessions that use them.
VACUUM

This is expected,
This represents that the GTT FrozenXid is the oldest in the entire db, and dba should vacuum the GTT if he want to push the db datfrozenxid.
Also he can use function pg_list_gtt_relfrozenxids() to check which session has "too old” data and truncate them or kill the sessions.

Again as per the HINT given, as "HINT: please truncate them or kill those sessions that use them."
There is only a single session.
If we try "TRUNCATE" and "VACUUM FULL" still the behavior is same as below.

postgres=# truncate gtt ;
TRUNCATE TABLE
postgres=# vacuum full;
WARNING: global temp table oldest FrozenXid is far in the past
HINT: please truncate them or kill those sessions that use them.
VACUUM

If the GTT is vacuumed in the all table first, it will still receive a warning message,
So I improved the error message to make it more reasonable.

I have one more finding related to "CLUSTER table USING index", Please check the below issue.
postgres=# create global temporary table gtt(c1 int) on commit preserve rows;
CREATE TABLE
postgres=# create index idx1 ON gtt (c1);
CREATE INDEX

-- exit and re-connect the psql prompt
postgres=# \q
[edb@localhost bin]$ ./psql postgres
psql (13devel)
Type "help" for help.

postgres=# cluster gtt using idx1;
WARNING: relcache reference leak: relation "gtt" not closed
CLUSTER

It is a bug, I fixed ,please check global_temporary_table_v25-pg13.patch.

Wenjing

Show quoted text

--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

Attachments:

global_temporary_table_v25-pg13.patchapplication/octet-stream; name=global_temporary_table_v25-pg13.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 8ccc228..b061de9 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -168,6 +168,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * For global temp table only
+	 * use ShareUpdateExclusiveLock for ensure safety
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temp table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},	
 	/* list terminator */
 	{{NULL}}
 };
@@ -1817,6 +1830,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1961,13 +1976,18 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	/*
 	 * There are no options for partitioned tables yet, but this is able to do
 	 * some validation.
 	 */
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index 765329b..4761fdc 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1011,7 +1011,9 @@ gistproperty(Oid index_oid, int attno,
 XLogRecPtr
 gistGetFakeLSN(Relation rel)
 {
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	/* global temp is same as local temp table */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 3ec6d52..1222594 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -150,7 +150,9 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	/* global temp table is same as local temp table */
+	if (!(index->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		index->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 56b3562..d3443ec 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -587,7 +587,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -640,7 +640,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index f3382d3..7a0746a 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -62,6 +62,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -431,9 +432,9 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
 	Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
-	/* not every AM requires these to be valid, but heap does */
-	Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
-	Assert(MultiXactIdIsValid(onerel->rd_rel->relminmxid));
+	/* not every AM requires these to be valid, but regular heap does */
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ MultiXactIdIsValid(onerel->rd_rel->relminmxid));
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 39b8f17..abb76dc 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -601,6 +602,10 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index c38bc14..e82a9cf 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6422,6 +6422,10 @@ StartupXLOG(void)
 	else
 		recoveryTargetTLI = ControlFile->checkPointCopy.ThisTimeLineID;
 
+	/* clean temp relation files */
+	if (max_active_gtt > 0)
+		RemovePgTempFiles();
+
 	/*
 	 * Check for signal files, and if so set up state for offline recovery
 	 */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 9499bb3..ae47364 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -43,6 +43,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 7d6acae..3be8d63 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -393,7 +393,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/* global temp table is same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 632c058..01cf990 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -427,7 +429,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -959,6 +961,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -997,8 +1000,18 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/* global temp table not remember transaction info in catalog */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1360,6 +1373,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1940,6 +1954,14 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/* We allow to drop global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			elog(ERROR, "can not drop relation %s when other backend attached this global temp table",
+						RelationGetRelationName(rel));
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3167,7 +3189,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3179,7 +3201,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3225,8 +3247,13 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		/* truncate global temp table only need RowExclusiveLock */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3259,6 +3286,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3267,23 +3295,37 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (!gtt_storage_attached(RelationGetRelid(rel)))
+			return;
+
+		/*
+		 * Truncate global temp table only need RowExclusiveLock
+		 */
+		lockmode = RowExclusiveLock;
+	}
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(rel, 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index bd7ec92..747cda0 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -53,6 +53,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -881,6 +882,19 @@ index_create(Relation heapRelation,
 						indexRelationName, RelationGetRelationName(heapRelation))));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temp table use concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot reindex global temporary tables concurrently")));
+
+		/* if global temp table not init storage, then skip build index */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -2041,7 +2055,8 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 * lock (see comments in RemoveRelations), and a non-concurrent DROP is
 	 * more efficient.
 	 */
-	Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+	Assert(!(get_rel_persistence(indexId) == RELPERSISTENCE_TEMP ||
+			 get_rel_persistence(indexId) == RELPERSISTENCE_GLOBAL_TEMP) ||
 		   (!concurrent && !concurrent_lock_mode));
 
 	/*
@@ -2073,6 +2088,14 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/* We allow to drop index on global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+			elog(ERROR, "can not drop index %s when other backend attached this global temp table.",
+						RelationGetRelationName(userHeapRelation));
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2681,6 +2704,11 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	/* update index stats into localhash and rel_rd_rel for global temp table */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		is_gtt = true;
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2766,21 +2794,35 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
+		if (is_gtt)
+			rel->rd_rel->relpages = (int32) relpages;
+		else if (rd_rel->relpages != (int32) relpages)
 		{
 			rd_rel->relpages = (int32) relpages;
 			dirty = true;
 		}
-		if (rd_rel->reltuples != (float4) reltuples)
+
+		if (is_gtt)
+			rel->rd_rel->reltuples = (float4) reltuples;
+		else if (rd_rel->reltuples != (float4) reltuples)
 		{
 			rd_rel->reltuples = (float4) reltuples;
 			dirty = true;
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+
+		if (is_gtt)
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+		else if (rd_rel->relallvisible != (int32) relallvisible)
 		{
 			rd_rel->relallvisible = (int32) relallvisible;
 			dirty = true;
 		}
+
+		if (is_gtt)
+		{
+			up_gtt_relstats(rel, relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
+		}
 	}
 
 	/*
@@ -2894,6 +2936,15 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, index, val);
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			gtt_force_enable_index(indexRelation);
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3442,6 +3493,10 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	PGRUsage	ru0;
 	bool		progress = (options & REINDEXOPT_REPORT_PROGRESS) != 0;
 
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		!gtt_storage_attached(indexId))
+		return;
+
 	pg_rusage_init(&ru0);
 
 	/*
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 2ec2301..1b6061d 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -655,6 +655,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		/* global temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp relations in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index d713d5c..afcf004 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,6 +27,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -61,6 +62,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			relOid;			/* InvalidOid if not a global temp rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -115,7 +117,7 @@ AddPendingSync(const RelFileNode *rnode)
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -127,6 +129,8 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		/* global temp table use same storage strategy as local temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -154,6 +158,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->relOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
@@ -165,6 +170,13 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		AddPendingSync(&rnode);
 	}
 
+	/* remember global temp table storage info to localhash */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel)
+	{
+		pending->relOid = RelationGetRelid(rel);
+		remember_gtt_storage_info(rnode, rel);
+	}
+
 	return srel;
 }
 
@@ -201,11 +213,15 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->relOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->relOid = RelationGetRelid(rel);
+
 	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
@@ -602,6 +618,7 @@ smgrDoPendingDeletes(bool isCommit)
 				i = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -631,14 +648,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->relOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -651,9 +672,18 @@ smgrDoPendingDeletes(bool isCommit)
 		smgrdounlinkall(srels, nrels, false);
 
 		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* clean global temp table flags when transaction commit or rollback */
+			if (SmgrIsTemp(srels[i]) &&
+				reloids[i] != InvalidOid &&
+				gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..be6a37b
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1489 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  code to create and destroy physical storage for global temparary table
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_relstats_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+typedef struct gtt_ctl_data
+{
+	LWLock			lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+	Oid			relfilenode;
+	Oid			spcnode;
+
+	/* pg_class stat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+
+	/* pg_statistic */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+
+	Oid			oldrelid;			/* remember the source of relid, before the switch relfilenode. */
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_reset_statistics(gtt_local_hash_entry *entry);
+static void gtt_free_statistics(gtt_local_hash_entry *entry);
+static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok);
+static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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();
+	}
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(gtt_fnode);
+	info.entrysize = action_gtt_shared_hash_entry_size();
+	active_gtt_shared_hash =
+		ShmemInitHash("active gtt shared hash",
+						gtt_shared_ctl->max_entry,
+						gtt_shared_ctl->max_entry,
+						&info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			found;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *)&(fnode), 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_global_temporary_table.")));
+	}
+
+	if (found == false)
+	{
+		int			wordnum;
+
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode				fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	if (!skiplock)
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		if (!skiplock)
+			LWLockRelease(&gtt_shared_ctl->lock);
+
+		if (isCommit)
+			elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+	bms_del_member(entry->map, MyBackendId);
+
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+
+	if (!skiplock)
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+	return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	Bitmapset	*map_copy = NULL;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool		in_use = false;
+	int			num_use = 0;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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;
+	gtt_relfilenode			*new_node = NULL;
+	Oid						relid = RelationGetRelid(rel);
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		/* First time through: initialize the hash table */
+		HASHCTL		ctl;
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_relstats_context =
+			AllocSetContextCreate(CacheMemoryContext,
+							"gtt relstats context",
+							ALLOCSET_DEFAULT_SIZES);
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+	entry = gtt_search_by_relid(relid, true);
+	if (!entry)
+	{
+		bool		found = false;
+		int 		natts = 0;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash,
+						(void *) &relid, HASH_ENTER, &found);
+
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+		entry->natts = 0;
+		entry->attnum = NULL;
+		entry->att_stat_tups = NULL;
+		entry->oldrelid = InvalidOid;
+
+		natts = RelationGetNumberOfAttributes(rel);
+		entry->attnum = palloc0(sizeof(int) * natts);
+		entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+		entry->natts = natts;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			gtt_storage_checkin(relid);
+		}
+	}
+
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* only heap contain transaction information */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	gtt_reset_statistics(entry);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	gtt_relfilenode *d_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+		return;
+	}
+
+	d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+		else if (entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+			gtt_relfilenode *gttnode2 = NULL;
+
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+			Assert(gttnode2->relfilenode == rnode.relNode);
+			Assert(list_length(entry->relfilenode_list) == 1);
+			/* rollback switch relfilenode */
+			gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+									   entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+									   false);
+			/* clean up footprint */
+			entry2->oldrelid = InvalidOid;
+			d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+			Assert(d_rnode);
+		}
+		else
+		{
+			if (entry->relfilenode_list == NIL)
+			{
+				if (entry->relkind == RELKIND_RELATION ||
+					entry->relkind == RELKIND_SEQUENCE)
+					gtt_storage_checkout(relid, false, isCommit);
+
+				gtt_free_statistics(entry);
+				hash_search(gtt_storage_local_hash,
+						(void *) &(relid), HASH_REMOVE, NULL);
+			}
+
+			return;
+		}
+	}
+
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	pfree(d_rnode);
+	if (entry->relfilenode_list == NIL)
+	{
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(relid, false, isCommit);
+
+		if (isCommit && entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			/* clean up footprint */
+			entry2->oldrelid = InvalidOid;
+		}
+
+		gtt_free_statistics(entry);
+		hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_REMOVE, NULL);
+	}
+	else
+		gtt_reset_statistics(entry);
+
+	return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool found = false;
+	gtt_local_hash_entry *entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry)
+		found = true;
+
+	return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS				status;
+	gtt_local_hash_entry		*entry;
+	SMgrRelation	*srels = NULL;
+	Oid				*relids = NULL;
+	char			*relkinds = NULL;
+	int			nrels = 0,
+				nfiles = 0,
+				maxrels = 0,
+				maxfiles = 0,
+				i = 0;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel;
+			RelFileNode 	rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel = smgropen(rnode, MyBackendId);
+
+			if (maxfiles == 0)
+			{
+				maxfiles = 32;
+				srels = palloc(sizeof(SMgrRelation) * maxfiles);
+			}
+			else if (maxfiles <= nfiles)
+			{
+				maxfiles *= 2;
+				srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+			}
+
+			srels[nfiles++] = srel;
+		}
+
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			relids  = palloc(sizeof(Oid) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			relids  = repalloc(relids , sizeof(Oid) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		relkinds[nrels] = entry->relkind;
+		relids[nrels] = entry->relid;
+		nrels++;
+	}
+
+	if (nfiles > 0)
+	{
+		smgrdounlinkall(srels, nfiles, false);
+		for (i = 0; i < nfiles; i++)
+			smgrclose(srels[i]);
+
+		pfree(srels);
+	}
+
+	if (nrels)
+	{
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+		for (i = 0; i < nrels; i++)
+		{
+			if (relkinds[i] == RELKIND_RELATION ||
+				relkinds[i] == RELKIND_SEQUENCE)
+				gtt_storage_checkout(relids[i], true, false);
+		}
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+		pfree(relids);
+		pfree(relkinds);
+	}
+
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+	return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible) 
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+					BlockNumber num_pages,
+					double num_tuples,
+					BlockNumber num_all_visible_pages,
+					TransactionId relfrozenxid,
+					TransactionId relminmxid)
+{
+	Oid		relid = RelationGetRelid(relation);
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (num_pages >= 0 &&
+		gtt_rnode->relpages != (int32)num_pages)
+		gtt_rnode->relpages = (int32)num_pages;
+
+	if (num_tuples >= 0 &&
+		gtt_rnode->reltuples != (float4)num_tuples)
+		gtt_rnode->reltuples = (float4)num_tuples;
+
+	/* only heap contain transaction information and relallvisible */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (gtt_rnode->relallvisible >= 0 &&
+			gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+		{
+			gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+		}
+
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNewTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible) 
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	MemoryContext			oldcontext;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return;
+
+	if (entry->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	Assert(entry->relid == reloid);
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == 0)
+		{
+			entry->attnum[i] = attnum;
+			break;
+		}
+		else if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < entry->natts);
+	Assert(entry->att_stat_tups[i] == NULL);
+	entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return NULL;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			return entry->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int			i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	HeapTuple	tuple;
+	Relation	rel = NULL;
+	int		attnum = PG_GETARG_INT32(1);
+	Oid		reloid = PG_GETARG_OID(0);
+	char		rel_persistence;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	  tupdesc;
+	MemoryContext oldcontext;
+	Tuplestorestate *tupstore;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+
+	relation_close(rel, NoLock);
+	relation_close(pg_tatistic, AccessShareLock);
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate	*tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Oid			relnode = 0;
+	Relation	rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[6];
+		bool	isnull[6];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(reloid);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(RelationGetRelid(index)));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid relOid = index->rd_index->indrelid;
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	if (gtt_storage_attached(relOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int			i;
+	Oid			toastrelid;
+
+	if (operation != CMD_INSERT)
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+
+		index_build(relation, index, info, true, false);
+	}
+
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid 		indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo 	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+static void
+gtt_reset_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+
+		entry->attnum[i] = 0;
+	}
+
+	return;
+}
+
+static void
+gtt_free_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (entry->attnum)
+		pfree(entry->attnum);
+
+	if (entry->att_stat_tups)
+		pfree(entry->att_stat_tups);
+
+	return;
+}
+
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
+void
+gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint)
+{
+	gtt_local_hash_entry	*entry1;
+	gtt_local_hash_entry	*entry2;
+	gtt_relfilenode			*gtt_rnode1 = NULL;
+	gtt_relfilenode			*gtt_rnode2 = NULL;
+	MemoryContext			oldcontext;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry1 = gtt_search_by_relid(rel1, false);
+	gtt_rnode1 = gtt_search_relfilenode(entry1, relfilenode1, false);
+
+	entry2 = gtt_search_by_relid(rel2, false);
+	gtt_rnode2 = gtt_search_relfilenode(entry2, relfilenode2, false);
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	entry1->relfilenode_list = list_delete_ptr(entry1->relfilenode_list, gtt_rnode1);
+	entry2->relfilenode_list = lappend(entry2->relfilenode_list, gtt_rnode1);
+
+	entry2->relfilenode_list = list_delete_ptr(entry2->relfilenode_list, gtt_rnode2);
+	entry1->relfilenode_list = lappend(entry1->relfilenode_list, gtt_rnode2);
+	MemoryContextSwitchTo(oldcontext);
+
+	if (footprint)
+	{
+		entry1->oldrelid = rel2;
+		entry2->oldrelid = rel1;
+	}
+
+	return;
+}
+
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok)
+{
+	gtt_relfilenode			*rnode = NULL;
+	ListCell				*lc;
+
+	Assert(entry);
+
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == relfilenode)
+		{
+			rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (!missing_ok && rnode == NULL)
+		elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+	return rnode;
+}
+
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry == NULL && !missing_ok)
+		elog(ERROR, "relid %u not found in local hash", relid);
+
+	return entry;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index d406ea8..88be041 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM 
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 924ef37..db12eef 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -184,6 +185,13 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -586,14 +594,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 		 * pg_statistic for columns we didn't process, we leave them alone.)
 		 */
 		update_attstats(RelationGetRelid(onerel), inh,
-						attr_cnt, vacattrstats);
+						attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
 
 		for (ind = 0; ind < nindexes; ind++)
 		{
 			AnlIndexData *thisdata = &indexdata[ind];
 
 			update_attstats(RelationGetRelid(Irel[ind]), false,
-							thisdata->attr_cnt, thisdata->vacattrstats);
+							thisdata->attr_cnt, thisdata->vacattrstats,
+							RelationGetRelPersistence(Irel[ind]));
 		}
 
 		/*
@@ -1456,7 +1465,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
  *		by taking a self-exclusive lock on the relation in analyze_rel().
  */
 static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
 {
 	Relation	sd;
 	int			attno;
@@ -1558,31 +1567,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/* Update column statistic to localhash, not catalog */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 04d12a7..f47035e 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/toasting.h"
 #include "commands/cluster.h"
 #include "commands/progress.h"
@@ -72,6 +73,12 @@ static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
 							bool verbose, bool *pSwapToastByContent,
 							TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
 static List *get_tables_to_cluster(MemoryContext cluster_context);
+static void gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables);
 
 
 /*---------------------------------------------------------------------------
@@ -366,6 +373,14 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap) &&
+		!gtt_storage_attached(RelationGetRelid(OldHeap)))
+	{
+		relation_close(OldHeap, AccessExclusiveLock);
+		pgstat_progress_end_command();
+		return;
+	}
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
@@ -750,6 +765,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	BlockNumber num_pages;
 	int			elevel = verbose ? INFO : DEBUG2;
 	PGRUsage	ru0;
+	bool		is_gtt = false;
+	uint32		gtt_relfrozenxid = 0;
+	uint32		gtt_relminmxid = 0;
 
 	pg_rusage_init(&ru0);
 
@@ -763,6 +781,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	else
 		OldIndex = NULL;
 
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		is_gtt = true;
+
 	/*
 	 * Their tuple descriptors should be exactly alike, but here we only need
 	 * assume that they have the same number of columns.
@@ -830,20 +851,37 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 						  &OldestXmin, &FreezeXid, NULL, &MultiXactCutoff,
 						  NULL);
 
-	/*
-	 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
-	 * backwards, so take the max.
-	 */
-	if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
-		TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
-		FreezeXid = OldHeap->rd_rel->relfrozenxid;
+	if (is_gtt)
+	{
+		get_gtt_relstats(OIDOldHeap,
+					NULL, NULL, NULL,
+					&gtt_relfrozenxid, &gtt_relminmxid);
 
-	/*
-	 * MultiXactCutoff, similarly, shouldn't go backwards either.
-	 */
-	if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
-		MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
-		MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+		if (TransactionIdIsValid(gtt_relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, gtt_relfrozenxid))
+			FreezeXid = gtt_relfrozenxid;
+
+		if (MultiXactIdIsValid(gtt_relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, gtt_relminmxid))
+			MultiXactCutoff = gtt_relminmxid;
+	}
+	else
+	{
+		/*
+		 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
+		 * backwards, so take the max.
+		 */
+		if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
+			FreezeXid = OldHeap->rd_rel->relfrozenxid;
+
+		/*
+		 * MultiXactCutoff, similarly, shouldn't go backwards either.
+		 */
+		if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
+			MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+	}
 
 	/*
 	 * Decide whether to use an indexscan or seqscan-and-optional-sort to scan
@@ -911,6 +949,12 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	table_close(OldHeap, NoLock);
 	table_close(NewHeap, NoLock);
 
+	if (is_gtt)
+	{
+		CommandCounterIncrement();
+		return;
+	}
+
 	/* Update pg_class to reflect the correct values of pages and tuples. */
 	relRelation = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1346,10 +1390,20 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 	 * Swap the contents of the heap relations (including any toast tables).
 	 * Also set old heap's relfrozenxid to frozenXid.
 	 */
-	swap_relation_files(OIDOldHeap, OIDNewHeap,
+	if (newrelpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		gtt_swap_relation_files(OIDOldHeap, OIDNewHeap,
+								(OIDOldHeap == RelationRelationId),
+								swap_toast_by_content, is_internal,
+								frozenXid, cutoffMulti, mapped_tables);
+	}
+	else
+	{
+		swap_relation_files(OIDOldHeap, OIDNewHeap,
 						(OIDOldHeap == RelationRelationId),
 						swap_toast_by_content, is_internal,
 						frozenXid, cutoffMulti, mapped_tables);
+	}
 
 	/*
 	 * If it's a system catalog, queue a sinval message to flush all catcaches
@@ -1557,3 +1611,141 @@ get_tables_to_cluster(MemoryContext cluster_context)
 
 	return rvs;
 }
+
+static void
+gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables)
+{
+	Relation	relRelation;
+	Oid			relfilenode1,
+				relfilenode2;
+	Relation	rel1;
+	Relation	rel2;
+
+	relRelation = table_open(RelationRelationId, RowExclusiveLock);
+
+	rel1 = relation_open(r1, AccessExclusiveLock);
+	rel2 = relation_open(r2, AccessExclusiveLock);
+
+	relfilenode1 = gtt_fetch_current_relfilenode(r1);
+	relfilenode2 = gtt_fetch_current_relfilenode(r2);
+
+	Assert(OidIsValid(relfilenode1) && OidIsValid(relfilenode2));
+	gtt_switch_rel_relfilenode(r1, relfilenode1, r2, relfilenode2, true);
+
+	CacheInvalidateRelcache(rel1);
+	CacheInvalidateRelcache(rel2);
+
+	InvokeObjectPostAlterHookArg(RelationRelationId, r1, 0,
+								 InvalidOid, is_internal);
+	InvokeObjectPostAlterHookArg(RelationRelationId, r2, 0,
+								 InvalidOid, true);
+
+	if (rel1->rd_rel->reltoastrelid || rel2->rd_rel->reltoastrelid)
+	{
+		if (swap_toast_by_content)
+		{
+			if (rel1->rd_rel->reltoastrelid && rel2->rd_rel->reltoastrelid)
+			{
+				gtt_swap_relation_files(rel1->rd_rel->reltoastrelid,
+									rel2->rd_rel->reltoastrelid,
+									target_is_pg_class,
+									swap_toast_by_content,
+									is_internal,
+									frozenXid,
+									cutoffMulti,
+									mapped_tables);
+			}
+			else
+				elog(ERROR, "cannot swap toast files by content when there's only one");
+		}
+		else
+		{
+			ObjectAddress baseobject,
+						toastobject;
+			long		count;
+
+			if (IsSystemRelation(rel1))
+				elog(ERROR, "cannot swap toast files by links for system catalogs");
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel1->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel2->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+
+			/* Register new dependencies */
+			baseobject.classId = RelationRelationId;
+			baseobject.objectSubId = 0;
+			toastobject.classId = RelationRelationId;
+			toastobject.objectSubId = 0;
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r1;
+				toastobject.objectId = rel1->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r2;
+				toastobject.objectId = rel2->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+		}
+	}
+
+	if (swap_toast_by_content &&
+		rel1->rd_rel->relkind == RELKIND_TOASTVALUE &&
+		rel2->rd_rel->relkind == RELKIND_TOASTVALUE)
+	{
+		Oid			toastIndex1,
+					toastIndex2;
+
+		/* Get valid index for each relation */
+		toastIndex1 = toast_get_valid_index(r1,
+											AccessExclusiveLock);
+		toastIndex2 = toast_get_valid_index(r2,
+											AccessExclusiveLock);
+
+		gtt_swap_relation_files(toastIndex1,
+							toastIndex2,
+							target_is_pg_class,
+							swap_toast_by_content,
+							is_internal,
+							InvalidTransactionId,
+							InvalidMultiXactId,
+							mapped_tables);
+	}
+
+	relation_close(rel1, NoLock);
+	relation_close(rel2, NoLock);
+
+	table_close(relRelation, RowExclusiveLock);
+
+	RelationCloseSmgrByOid(r1);
+	RelationCloseSmgrByOid(r2);
+
+	CommandCounterIncrement();
+}
+
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index ac07f75..655184d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -27,6 +27,7 @@
 #include "catalog/dependency.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -1063,7 +1064,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, stmt->filename, stmt->is_program,
@@ -2794,6 +2795,8 @@ CopyFrom(CopyState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	estate->es_result_relations = resultRelInfo;
 	estate->es_num_result_relations = 1;
 	estate->es_result_relation_info = resultRelInfo;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 2baca12..5338db9 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -521,6 +521,7 @@ DefineIndex(Oid relationId,
 	Snapshot	snapshot;
 	int			save_nestlevel = -1;
 	int			i;
+	char		rel_persistence;
 
 	/*
 	 * Some callers need us to run with an empty default_tablespace; this is a
@@ -542,7 +543,9 @@ DefineIndex(Oid relationId,
 	 * is more efficient.  Do this before any use of the concurrent option is
 	 * done.
 	 */
-	if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+	rel_persistence = get_rel_persistence(relationId);
+	if (stmt->concurrent &&
+		!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		concurrent = true;
 	else
 		concurrent = false;
@@ -2460,7 +2463,8 @@ ReindexIndex(RangeVar *indexRelation, int options, bool concurrent)
 	persistence = irel->rd_rel->relpersistence;
 	index_close(irel, NoLock);
 
-	if (concurrent && persistence != RELPERSISTENCE_TEMP)
+	if (concurrent &&
+		!(persistence == RELPERSISTENCE_TEMP || persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		ReindexRelationConcurrently(indOid, options);
 	else
 		reindex_index(indOid, false, persistence,
@@ -2546,6 +2550,7 @@ ReindexTable(RangeVar *relation, int options, bool concurrent)
 {
 	Oid			heapOid;
 	bool		result;
+	char		rel_persistence;
 
 	/*
 	 * The lock level used here should match reindex_relation().
@@ -2560,7 +2565,9 @@ ReindexTable(RangeVar *relation, int options, bool concurrent)
 									   0,
 									   RangeVarCallbackOwnsTable, NULL);
 
-	if (concurrent && get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+	rel_persistence = get_rel_persistence(heapOid);
+	if (concurrent &&
+		!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 	{
 		result = ReindexRelationConcurrently(heapOid, options);
 
@@ -2761,12 +2768,15 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 	foreach(l, relids)
 	{
 		Oid			relid = lfirst_oid(l);
+		char		rel_persistence;
 
 		StartTransactionCommand();
 		/* functions in indexes may want a snapshot set */
 		PushActiveSnapshot(GetTransactionSnapshot());
 
-		if (concurrent && get_rel_persistence(relid) != RELPERSISTENCE_TEMP)
+		rel_persistence = get_rel_persistence(relid);
+		if (concurrent &&
+			!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		{
 			(void) ReindexRelationConcurrently(relid, options);
 			/* ReindexRelationConcurrently() does the verbose output */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..a45863a 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -94,7 +96,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -222,7 +225,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* now initialize the sequence's data */
 	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -327,7 +330,7 @@ ResetSequence(Oid seq_relid)
 	/*
 	 * Insert the modified tuple into the new storage file.
 	 */
-	fill_seq_with_data(seq_rel, tuple);
+	fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -340,17 +343,21 @@ ResetSequence(Oid seq_relid)
  * Initialize a sequence's relation with the specified tuple as content
  */
 static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 	sequence_magic *sm;
 	OffsetNumber offnum;
+	bool lockBuffer = false;
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ReadBuffer(rel, P_NEW);
-	Assert(BufferGetBlockNumber(buf) == 0);
+	if (buf == InvalidBuffer)
+	{
+		buf = ReadBuffer(rel, P_NEW);
+		Assert(BufferGetBlockNumber(buf) == 0);
+		lockBuffer = true;
+	}
 
 	page = BufferGetPage(buf);
 
@@ -360,7 +367,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* Now insert sequence tuple */
 
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+	if (lockBuffer)
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +418,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	END_CRIT_SECTION();
 
-	UnlockReleaseBuffer(buf);
+	if (lockBuffer)
+		UnlockReleaseBuffer(buf);
 }
 
 /*
@@ -451,6 +460,13 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 
 	init_sequence(relid, &elm, &seqrel);
 
+	if (RELATION_IS_GLOBAL_TEMP(seqrel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(seqrel)))
+			elog(ERROR, "cannot alter global temp sequence %s when other backend attached it",
+						RelationGetRelationName(seqrel));
+	}
+
 	rel = table_open(SequenceRelationId, RowExclusiveLock);
 	seqtuple = SearchSysCacheCopy1(SEQRELID,
 								   ObjectIdGetDatum(relid));
@@ -502,7 +518,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * Insert the modified tuple into the new storage file.
 		 */
-		fill_seq_with_data(seqrel, newdatatuple);
+		fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
 	}
 
 	/* process OWNED BY if given */
@@ -611,7 +627,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +952,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1154,6 +1170,13 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1954,3 +1977,46 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+void
+gtt_init_seq(Relation rel)
+{
+	/* Initialize sequence for global temporary tables */
+	Datum		value[SEQ_COL_LASTCOL] = {0};
+	bool		null[SEQ_COL_LASTCOL] = {false};
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 6162fb0..e825c04 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -555,6 +556,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 								  Relation partitionTbl);
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static OnCommitAction gtt_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -600,6 +602,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -610,8 +613,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Check consistency of arguments
 	 */
+	/* global temp table same as local temp table */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !(stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+			 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -641,7 +646,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	/* global temp table same as local temp table */
+	if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+		 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -742,6 +749,55 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* global temp table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* check parent table*/
+		if (inheritOids)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			table_close(relation, NoLock);
+		}
+
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temp table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1329,6 +1385,7 @@ RemoveRelations(DropStmt *drop)
 		Oid			relOid;
 		ObjectAddress obj;
 		struct DropRelationCallbackState state;
+		char		rel_persistence;
 
 		/*
 		 * These next few steps are a great deal like relation_openrv, but we
@@ -1362,8 +1419,9 @@ RemoveRelations(DropStmt *drop)
 		 * Decide if concurrent mode needs to be used here or not.  The
 		 * relation persistence cannot be known without its OID.
 		 */
+		rel_persistence = get_rel_persistence(relOid);
 		if (drop->concurrent &&
-			get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+			!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		{
 			Assert(list_length(drop->objects) == 1 &&
 				   drop->removeType == OBJECT_INDEX);
@@ -1818,6 +1876,10 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
 		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 			continue;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			continue;
+
 		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
@@ -3569,6 +3631,14 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			elog(ERROR, "can not alter table %s when other backend attached this global temp table",
+						RelationGetRelationName(rel));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -4846,6 +4916,12 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot rewrite temporary tables of other sessions")));
 
+			if (RELATION_IS_GLOBAL_TEMP(OldHeap) &&
+				tab->chgPersistence)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot change global temporary table persistence setting")));
+
 			/*
 			 * Select destination tablespace (same as original unless user
 			 * requested a change)
@@ -8183,6 +8259,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -12739,6 +12821,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -12941,6 +13026,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temp table");
+
 	/*
 	 * No work if no change in tablespace.
 	 */
@@ -13315,7 +13403,7 @@ index_copy_data(Relation rel, RelFileNode newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
@@ -14722,7 +14810,9 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
 	 */
 	switch (rel->rd_rel->relpersistence)
 	{
+		/* global temp table same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -17315,3 +17405,36 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
 		table_close(rel, NoLock);
 	}
 }
+
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			bool	res = false;
+			char	*sval = defGetString(def);
+
+			if (!parse_bool(sval, &res))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
+
+			if (res)
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 3a89f8f..d3e894a 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/vacuum.h"
@@ -1217,6 +1218,17 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	 /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		is_gtt = true;
+		up_gtt_relstats(relation,
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1230,17 +1242,26 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relpages = (int32) num_pages;
+	else if (pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (is_gtt)
+		relation->rd_rel->reltuples = (float4) num_tuples;
+	else if (pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	else if (pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1285,7 +1306,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1296,7 +1318,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1394,6 +1417,10 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/* global temp table relstats not in pg_class */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1451,6 +1478,27 @@ 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 relfrozenxid %u is the oldest in the entire db",
+						oldest_gtt_frozenxid),
+				 errdetail("The oldest relfrozenxid in pg_class is %u", newFrozenXid),
+				 errhint("If they differ greatly, please consider cleaning up the data in global temp table.")));
+			newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1778,6 +1826,15 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6e65103..04706ee 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -530,6 +530,11 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 4fdffad..0aa7559 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index fb6ce49..06d9237 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
@@ -545,6 +546,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	init_gtt_storage(mtstate->operation, leaf_part_rri);
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d71c0a4..0900907 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,6 +42,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -2372,6 +2373,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			ExecOpenIndices(resultRelInfo,
 							node->onConflictAction != ONCONFLICT_NONE);
 
+		init_gtt_storage(operation, resultRelInfo);
+
 		/*
 		 * If this is an UPDATE and a BEFORE UPDATE trigger is present, the
 		 * trigger itself might modify the partition-key values. So arrange
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 255f56b..d94d6f0 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 b31c524..a70b11b 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6421,7 +6421,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
+	/* global temp table is same as local temp table */
 	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 2554502..04fb5d4 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -28,6 +28,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temp table */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index eee9c33..1891c53 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2594,6 +2594,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 1219ac8..70e5a93 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3302,17 +3302,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; }
@@ -11638,19 +11632,13 @@ OptTempTableName:
 				}
 			| GLOBAL TEMPORARY opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED opt_table qualified_name
 				{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b875a50..123945b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 350959a..38df309 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
@@ -3089,6 +3092,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		cxt.isforeign = false;
 	}
 	cxt.relation = stmt->relation;
+	cxt.relation->relpersistence = RelationGetRelPersistence(rel);
 	cxt.rel = rel;
 	cxt.inhRelations = NIL;
 	cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 7e97ffa..1f2ac58 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2089,6 +2089,11 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* autovacuum skip vacuum global temp table */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2155,7 +2160,9 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		/* autovacuum skip vacuum global temp table */
+		if (classForm->relpersistence == RELPERSISTENCE_TEMP || 
+			classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index f9980cf..406a25c 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -53,6 +54,7 @@
 #include "utils/rel.h"
 #include "utils/resowner_private.h"
 #include "utils/timestamp.h"
+#include "utils/guc.h"
 
 
 /* Note: these two macros only work on shared buffers, not local ones! */
@@ -2819,6 +2821,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d5..7adb969 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -21,6 +21,7 @@
 #include "access/nbtree.h"
 #include "access/subtrans.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, active_gtt_shared_hash_size());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* global temporary table */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 3630006..0c8ef80 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -63,6 +63,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -4086,3 +4087,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 5aa19d3..b3bc455 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 8406644..0d99321 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -1008,6 +1008,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 4fdcb07..58fc8de 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -112,6 +112,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -4731,12 +4732,25 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -4861,15 +4875,27 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6251,6 +6277,7 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	{
 		/* Simple variable --- look to stats for the underlying table */
 		RangeTblEntry *rte = planner_rt_fetch(index->rel->relid, root);
+		char	rel_persistence = get_rel_persistence(rte->relid);
 
 		Assert(rte->rtekind == RTE_RELATION);
 		relid = rte->relid;
@@ -6268,6 +6295,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6279,6 +6313,8 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	}
 	else
 	{
+		char	rel_persistence = get_rel_persistence(index->indexoid);
+
 		/* Expression --- maybe there are stats for the index itself */
 		relid = index->indexoid;
 		colnum = 1;
@@ -6294,6 +6330,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7212,6 +7255,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		/* attempt to lookup stats in relation for this index column */
 		if (attnum != 0)
 		{
+			char	rel_persistence = get_rel_persistence(rte->relid);
+
 			/* Simple variable -- look to stats for the underlying table */
 			if (get_relation_stats_hook &&
 				(*get_relation_stats_hook) (root, rte, attnum, &vardata))
@@ -7224,6 +7269,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7236,6 +7289,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		}
 		else
 		{
+			char	rel_persistence = get_rel_persistence(index->indexoid);
+
 			/*
 			 * Looks like we've found an expression column in the index. Let's
 			 * see if there's any stats for it.
@@ -7255,6 +7310,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index a7d63f1..d8a52cb 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -2939,6 +2940,18 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 9f1f11d..aeb2a78 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -65,6 +65,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1133,6 +1134,24 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1187,6 +1206,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			gtt_fix_index_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -1317,7 +1337,17 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+			if (newrelnode != InvalidOid &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2246,6 +2276,8 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		gtt_fix_index_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3472,6 +3504,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3579,28 +3615,34 @@ void
 RelationSetNewRelfilenode(Relation relation, char persistence)
 {
 	Oid			newrelfilenode;
-	Relation	pg_class;
-	HeapTuple	tuple;
+	Relation	pg_class = NULL;
+	HeapTuple	tuple = NULL;
 	Form_pg_class classform;
 	MultiXactId minmulti = InvalidMultiXactId;
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
+	bool		modify_pg_class = !RELATION_IS_GLOBAL_TEMP(relation);
 
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
 
-	/*
-	 * Get a writable copy of the pg_class tuple for the given relation.
-	 */
-	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+	if (modify_pg_class)
+	{
+		/*
+		 * Get a writable copy of the pg_class tuple for the given relation.
+		 */
+		pg_class = table_open(RelationRelationId, RowExclusiveLock);
 
-	tuple = SearchSysCacheCopy1(RELOID,
-								ObjectIdGetDatum(RelationGetRelid(relation)));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "could not find tuple for relation %u",
-			 RelationGetRelid(relation));
-	classform = (Form_pg_class) GETSTRUCT(tuple);
+		tuple = SearchSysCacheCopy1(RELOID,
+									ObjectIdGetDatum(RelationGetRelid(relation)));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u",
+				 RelationGetRelid(relation));
+		classform = (Form_pg_class) GETSTRUCT(tuple);
+	}
+	else
+		memset(&classform, 0, sizeof(classform));
 
 	/*
 	 * Schedule unlinking of the old storage at transaction commit.
@@ -3626,7 +3668,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;
@@ -3646,6 +3688,15 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 			break;
 	}
 
+	if (!modify_pg_class)
+	{
+		Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+		Assert(RELATION_IS_GLOBAL_TEMP(relation));
+		Assert(!RelationIsMapped(relation));
+		relation->rd_node.relNode = relnode;
+		CacheInvalidateRelcache(relation);
+	}
 	/*
 	 * If we're dealing with a mapped index, pg_class.relfilenode doesn't
 	 * change; instead we have to send the update to the relation mapper.
@@ -3655,7 +3706,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	 * possibly-inaccurate values of relpages etc, but those will be fixed up
 	 * later.
 	 */
-	if (RelationIsMapped(relation))
+	else if (RelationIsMapped(relation))
 	{
 		/* This case is only supported for indexes */
 		Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3701,9 +3752,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 		CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 	}
 
-	heap_freetuple(tuple);
+	if (modify_pg_class)
+	{
+		heap_freetuple(tuple);
 
-	table_close(pg_class, RowExclusiveLock);
+		table_close(pg_class, RowExclusiveLock);
+	}
 
 	/*
 	 * Make the pg_class row change or relation map change visible.  This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 5bdc02f..7747dde 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -142,6 +142,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temp table feature.
+ * global temp table define can still storage in catalog
+ * just can not use.
+ * num > 0 means database can management num active global temp table.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2070,6 +2082,15 @@ static struct config_bool ConfigureNamesBool[] =
 static struct config_int ConfigureNamesInt[] =
 {
 	{
+		{"max_active_global_temporary_table", PGC_POSTMASTER, UNGROUPED,
+			gettext_noop("max active global temporary table."),
+			NULL
+		},
+		&max_active_gtt,
+		DEFAULT_NUM_ACTIVE_GTT, MIN_NUM_ACTIVE_GTT, MAX_NUM_ACTIVE_GTT,
+		NULL, NULL, NULL
+	},
+	{
 		{"archive_timeout", PGC_SIGHUP, WAL_ARCHIVING,
 			gettext_noop("Forces a switch to the next WAL file if a "
 						 "new file has not been started within N seconds."),
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index c579227..e03557b 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2424,6 +2424,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
 		dopt->no_unlogged_table_data)
 		return;
 
+	/* Don't dump data in global temp table/sequence */
+	if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		return;
+
 	/* Check that the data is not explicitly excluded */
 	if (simple_oid_list_member(&tabledata_exclude_oids,
 							   tbinfo->dobj.catId.oid))
@@ -15772,6 +15776,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
 		char	   *foreign = "";
+		char	   *table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15825,9 +15830,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);
 
@@ -16221,7 +16232,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
-			 tbinfo->relkind == RELKIND_MATVIEW))
+			 tbinfo->relkind == RELKIND_MATVIEW) &&
+			 tbinfo->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
 		{
 			appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
 			appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
@@ -17126,6 +17138,7 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	PQExpBuffer query = createPQExpBuffer();
 	PQExpBuffer delqry = createPQExpBuffer();
 	char	   *qseqname;
+	bool	   global_temp_seq = false;
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
@@ -17135,9 +17148,11 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
-						  "FROM pg_catalog.pg_sequence "
-						  "WHERE seqrelid = '%u'::oid",
+						  "seqcache, seqcycle, c.relpersistence "
+						  "FROM pg_catalog.pg_sequence s, "
+						  "pg_catalog.pg_class c "
+						  "WHERE seqrelid = '%u'::oid "
+						  "and s.seqrelid = c.oid",
 						  tbinfo->dobj.catId.oid);
 	}
 	else if (fout->remoteVersion >= 80400)
@@ -17182,6 +17197,9 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
 
+	if (fout->remoteVersion >= 130000)
+		global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
 	if (strcmp(seqtype, "smallint") == 0)
@@ -17256,9 +17274,13 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	}
 	else
 	{
-		appendPQExpBuffer(query,
-						  "CREATE SEQUENCE %s\n",
-						  fmtQualifiedDumpable(tbinfo));
+		appendPQExpBuffer(query, "CREATE ");
+
+		if (global_temp_seq)
+			appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+		appendPQExpBuffer(query, "SEQUENCE %s\n",
+								  fmtQualifiedDumpable(tbinfo));
 
 		if (strcmp(seqtype, "bigint") != 0)
 			appendPQExpBuffer(query, "    AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 00aef85..9ad71c8 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -84,7 +84,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -157,7 +157,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 7e524ea..371168f 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -441,22 +443,46 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 	 * output, so we have to copy that system table.  It's easiest to do that
 	 * by treating it as a user table.
 	 */
-	snprintf(query + strlen(query), sizeof(query) - strlen(query),
-			 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
-			 "  SELECT c.oid, 0::oid, 0::oid "
-			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
-			 "         ON c.relnamespace = n.oid "
-			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
-	/* exclude possible orphaned temp tables */
-			 "    ((n.nspname !~ '^pg_temp_' AND "
-			 "      n.nspname !~ '^pg_toast_temp_' AND "
-			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
-			 "                        'binary_upgrade', 'pg_toast') AND "
-			 "      c.oid >= %u::pg_catalog.oid) OR "
-			 "     (n.nspname = 'pg_catalog' AND "
-			 "      relname IN ('pg_largeobject') ))), ",
-			 FirstNormalObjectId);
+	if (skip_gtt)
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude global temp tables */
+				 "    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
+	else
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
 
 	/*
 	 * Add a CTE that collects OIDs of toast tables belonging to the tables
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 70194eb..8dd9e66 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -411,7 +411,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -642,7 +642,9 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -653,7 +655,9 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 8b90cef..d155205 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -389,7 +389,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index f05e914..19adab9 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3724,7 +3724,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 0e7a373..a47de01 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1023,6 +1023,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2390,6 +2392,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2598,6 +2603,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 78b33b2..4be89f6 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -168,6 +168,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad..c8236a1 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5558,6 +5558,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4388',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4389',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4390',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4391',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 30c38e0..7ff2408 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,7 +22,7 @@
 /* GUC variables */
 extern int	wal_skip_threshold;
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationPreTruncate(Relation rel);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..45fd470
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,45 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Relation relation,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void gtt_force_enable_index(Relation index);
+extern void gtt_fix_index_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+extern void gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index e2638ab..89a5ce4 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 3f88683..f870e9a 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 8fda8e4..172eae9 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_TBM,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_SXACT,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index ae4f573..69cce92 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -117,6 +117,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId session_gtt_frozenxid;	/* session level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 2819282..363fc33 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -282,6 +282,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 0b5957b..0720a4c 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	struct SMgrRelationData *rd_smgr;	/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -305,6 +305,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -570,11 +571,13 @@ typedef struct ViewOptions
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -582,6 +585,7 @@ typedef struct ViewOptions
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -594,6 +598,14 @@ typedef struct ViewOptions
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is either local temp relation of this session
+ * 		or global temp relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -637,6 +649,17 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..ca2d135
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,14 @@
+reset search_path;
+select pg_sleep(5);
+ pg_sleep 
+----------
+ 
+(1 row)
+
+drop schema gtt cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
+drop cascades to table gtt.gtt_with_seq
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..d6a675a
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,383 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+ n 
+---
+ 9
+(1 row)
+
+commit;
+-- 0 row
+select * from gtt6;
+ n 
+---
+(0 rows)
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temp table
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  could not create global temporary table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR:  insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count 
+-------
+     1
+(1 row)
+
+select count(*) from orders;
+ count 
+-------
+     0
+(1 row)
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  2 |  3
+(2 rows)
+
+commit;
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+select * from gtt_s_2 order by c1;
+ c1 | c2 
+----+----
+  1 |  2
+  2 |  4
+(2 rows)
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 1)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 300)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 33 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test_new
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.gtt6
+drop cascades to table gtt_function.foo
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to sequence gtt_function.seq_1
+drop cascades to table gtt_function.gtt_s_1
+drop cascades to table gtt_function.gtt_s_2
+drop cascades to table gtt_function.gt1
+drop cascades to table gtt_function.gtt_test1
+drop cascades to table gtt_function.gtt_test2
+drop cascades to table gtt_function.gtt_test3
+drop cascades to table gtt_function.gtt_test4
+drop cascades to table gtt_function.gtt_test5
+drop cascades to table gtt_function.gtt_test6
+drop cascades to table gtt_function.gtt_test7
+drop cascades to table gtt_function.gtt_test8
+drop cascades to table gtt_function.gtt_test9
+drop cascades to table gtt_function.gtt_test10
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..0646aae
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,90 @@
+set search_path=gtt,sys;
+select nextval('gtt_with_seq_c2_seq');
+ nextval 
+---------
+       1
+(1 row)
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..2f084be
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,343 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+rollback;
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+commit;
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          114688 |                 614400
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |            49152 |         49152 |                  49152
+(2 rows)
+
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2000
+(1 row)
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+ c1 | c2 
+----+----
+  1 |  1
+(1 row)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..8c0c376
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,10 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..4420fdb
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,80 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+ gtt_stats  | gtt_pkey  |        1 |         0 |             0
+(2 rows)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+reindex table gtt;
+reindex index gtt_pkey;
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+ gtt_stats  | gtt_pkey  |       30 |     10000 |             0
+(2 rows)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index ac31840..55b221e 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1349,6 +1349,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 95f1925..76b2374 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..2c8e586
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,8 @@
+
+
+reset search_path;
+
+select pg_sleep(5);
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..81f0bfc
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,253 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+commit;
+-- 0 row
+select * from gtt6;
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+commit;
+select * from gtt_s_1 order by c1;
+select * from gtt_s_2 order by c1;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d05745e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,44 @@
+
+
+set search_path=gtt,sys;
+
+select nextval('gtt_with_seq_c2_seq');
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..39cca5e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,154 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+rollback;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+commit;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+select count(*) from gtt_t_kenyon;
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..dbe84d1
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,19 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..d61b0ff
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,46 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+reindex table gtt;
+
+reindex index gtt_pkey;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#237曾文旌
wenjing.zwj@alibaba-inc.com
In reply to: tushar (#231)
2 attachment(s)
Re: [Proposal] Global temporary tables

2020年4月7日 下午7:25,tushar <tushar.ahuja@enterprisedb.com> 写道:

On 4/7/20 2:27 PM, 曾文旌 wrote:

Vacuum full GTT, cluster GTT is already supported in global_temporary_table_v24-pg13.patch.

Here , it is skipping GTT

postgres=# \c foo
You are now connected to database "foo" as user "tushar".
foo=# create global temporary table g123( c1 int) ;
CREATE TABLE
foo=# \q
[tushar@localhost bin]$ ./vacuumdb --full foo
vacuumdb: vacuuming database "foo"
WARNING: skipping vacuum global temp table "g123" because storage is not initialized for current session

The message was inappropriate at some point, so I removed it.

Wenjing

Attachments:

global_temporary_table_v25-pg13.patchapplication/octet-stream; name=global_temporary_table_v25-pg13.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 8ccc228..b061de9 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -168,6 +168,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * For global temp table only
+	 * use ShareUpdateExclusiveLock for ensure safety
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temp table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},	
 	/* list terminator */
 	{{NULL}}
 };
@@ -1817,6 +1830,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1961,13 +1976,18 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	/*
 	 * There are no options for partitioned tables yet, but this is able to do
 	 * some validation.
 	 */
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index 765329b..4761fdc 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1011,7 +1011,9 @@ gistproperty(Oid index_oid, int attno,
 XLogRecPtr
 gistGetFakeLSN(Relation rel)
 {
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	/* global temp is same as local temp table */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 3ec6d52..1222594 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -150,7 +150,9 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	/* global temp table is same as local temp table */
+	if (!(index->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		index->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 56b3562..d3443ec 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -587,7 +587,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -640,7 +640,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index f3382d3..7a0746a 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -62,6 +62,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -431,9 +432,9 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
 	Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
-	/* not every AM requires these to be valid, but heap does */
-	Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
-	Assert(MultiXactIdIsValid(onerel->rd_rel->relminmxid));
+	/* not every AM requires these to be valid, but regular heap does */
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ MultiXactIdIsValid(onerel->rd_rel->relminmxid));
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 39b8f17..abb76dc 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -601,6 +602,10 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index c38bc14..e82a9cf 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6422,6 +6422,10 @@ StartupXLOG(void)
 	else
 		recoveryTargetTLI = ControlFile->checkPointCopy.ThisTimeLineID;
 
+	/* clean temp relation files */
+	if (max_active_gtt > 0)
+		RemovePgTempFiles();
+
 	/*
 	 * Check for signal files, and if so set up state for offline recovery
 	 */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 9499bb3..ae47364 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -43,6 +43,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 7d6acae..3be8d63 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -393,7 +393,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/* global temp table is same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 632c058..01cf990 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -427,7 +429,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -959,6 +961,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -997,8 +1000,18 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/* global temp table not remember transaction info in catalog */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1360,6 +1373,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1940,6 +1954,14 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/* We allow to drop global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			elog(ERROR, "can not drop relation %s when other backend attached this global temp table",
+						RelationGetRelationName(rel));
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3167,7 +3189,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3179,7 +3201,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3225,8 +3247,13 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		/* truncate global temp table only need RowExclusiveLock */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3259,6 +3286,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3267,23 +3295,37 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (!gtt_storage_attached(RelationGetRelid(rel)))
+			return;
+
+		/*
+		 * Truncate global temp table only need RowExclusiveLock
+		 */
+		lockmode = RowExclusiveLock;
+	}
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(rel, 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index bd7ec92..747cda0 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -53,6 +53,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -881,6 +882,19 @@ index_create(Relation heapRelation,
 						indexRelationName, RelationGetRelationName(heapRelation))));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temp table use concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot reindex global temporary tables concurrently")));
+
+		/* if global temp table not init storage, then skip build index */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -2041,7 +2055,8 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 * lock (see comments in RemoveRelations), and a non-concurrent DROP is
 	 * more efficient.
 	 */
-	Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+	Assert(!(get_rel_persistence(indexId) == RELPERSISTENCE_TEMP ||
+			 get_rel_persistence(indexId) == RELPERSISTENCE_GLOBAL_TEMP) ||
 		   (!concurrent && !concurrent_lock_mode));
 
 	/*
@@ -2073,6 +2088,14 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/* We allow to drop index on global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+			elog(ERROR, "can not drop index %s when other backend attached this global temp table.",
+						RelationGetRelationName(userHeapRelation));
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2681,6 +2704,11 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	/* update index stats into localhash and rel_rd_rel for global temp table */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		is_gtt = true;
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2766,21 +2794,35 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
+		if (is_gtt)
+			rel->rd_rel->relpages = (int32) relpages;
+		else if (rd_rel->relpages != (int32) relpages)
 		{
 			rd_rel->relpages = (int32) relpages;
 			dirty = true;
 		}
-		if (rd_rel->reltuples != (float4) reltuples)
+
+		if (is_gtt)
+			rel->rd_rel->reltuples = (float4) reltuples;
+		else if (rd_rel->reltuples != (float4) reltuples)
 		{
 			rd_rel->reltuples = (float4) reltuples;
 			dirty = true;
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+
+		if (is_gtt)
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+		else if (rd_rel->relallvisible != (int32) relallvisible)
 		{
 			rd_rel->relallvisible = (int32) relallvisible;
 			dirty = true;
 		}
+
+		if (is_gtt)
+		{
+			up_gtt_relstats(rel, relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
+		}
 	}
 
 	/*
@@ -2894,6 +2936,15 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, index, val);
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			gtt_force_enable_index(indexRelation);
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3442,6 +3493,10 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	PGRUsage	ru0;
 	bool		progress = (options & REINDEXOPT_REPORT_PROGRESS) != 0;
 
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		!gtt_storage_attached(indexId))
+		return;
+
 	pg_rusage_init(&ru0);
 
 	/*
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 2ec2301..1b6061d 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -655,6 +655,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		/* global temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp relations in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index d713d5c..afcf004 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,6 +27,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -61,6 +62,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			relOid;			/* InvalidOid if not a global temp rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -115,7 +117,7 @@ AddPendingSync(const RelFileNode *rnode)
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -127,6 +129,8 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		/* global temp table use same storage strategy as local temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -154,6 +158,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->relOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
@@ -165,6 +170,13 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		AddPendingSync(&rnode);
 	}
 
+	/* remember global temp table storage info to localhash */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel)
+	{
+		pending->relOid = RelationGetRelid(rel);
+		remember_gtt_storage_info(rnode, rel);
+	}
+
 	return srel;
 }
 
@@ -201,11 +213,15 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->relOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->relOid = RelationGetRelid(rel);
+
 	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
@@ -602,6 +618,7 @@ smgrDoPendingDeletes(bool isCommit)
 				i = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -631,14 +648,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->relOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -651,9 +672,18 @@ smgrDoPendingDeletes(bool isCommit)
 		smgrdounlinkall(srels, nrels, false);
 
 		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* clean global temp table flags when transaction commit or rollback */
+			if (SmgrIsTemp(srels[i]) &&
+				reloids[i] != InvalidOid &&
+				gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..be6a37b
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1489 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  code to create and destroy physical storage for global temparary table
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_relstats_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+typedef struct gtt_ctl_data
+{
+	LWLock			lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+	Oid			relfilenode;
+	Oid			spcnode;
+
+	/* pg_class stat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+
+	/* pg_statistic */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+
+	Oid			oldrelid;			/* remember the source of relid, before the switch relfilenode. */
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_reset_statistics(gtt_local_hash_entry *entry);
+static void gtt_free_statistics(gtt_local_hash_entry *entry);
+static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok);
+static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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();
+	}
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(gtt_fnode);
+	info.entrysize = action_gtt_shared_hash_entry_size();
+	active_gtt_shared_hash =
+		ShmemInitHash("active gtt shared hash",
+						gtt_shared_ctl->max_entry,
+						gtt_shared_ctl->max_entry,
+						&info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			found;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *)&(fnode), 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_global_temporary_table.")));
+	}
+
+	if (found == false)
+	{
+		int			wordnum;
+
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode				fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	if (!skiplock)
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		if (!skiplock)
+			LWLockRelease(&gtt_shared_ctl->lock);
+
+		if (isCommit)
+			elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+	bms_del_member(entry->map, MyBackendId);
+
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+
+	if (!skiplock)
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+	return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	Bitmapset	*map_copy = NULL;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool		in_use = false;
+	int			num_use = 0;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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;
+	gtt_relfilenode			*new_node = NULL;
+	Oid						relid = RelationGetRelid(rel);
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		/* First time through: initialize the hash table */
+		HASHCTL		ctl;
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_relstats_context =
+			AllocSetContextCreate(CacheMemoryContext,
+							"gtt relstats context",
+							ALLOCSET_DEFAULT_SIZES);
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+	entry = gtt_search_by_relid(relid, true);
+	if (!entry)
+	{
+		bool		found = false;
+		int 		natts = 0;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash,
+						(void *) &relid, HASH_ENTER, &found);
+
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+		entry->natts = 0;
+		entry->attnum = NULL;
+		entry->att_stat_tups = NULL;
+		entry->oldrelid = InvalidOid;
+
+		natts = RelationGetNumberOfAttributes(rel);
+		entry->attnum = palloc0(sizeof(int) * natts);
+		entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+		entry->natts = natts;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			gtt_storage_checkin(relid);
+		}
+	}
+
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* only heap contain transaction information */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	gtt_reset_statistics(entry);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	gtt_relfilenode *d_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+		return;
+	}
+
+	d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+		else if (entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+			gtt_relfilenode *gttnode2 = NULL;
+
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+			Assert(gttnode2->relfilenode == rnode.relNode);
+			Assert(list_length(entry->relfilenode_list) == 1);
+			/* rollback switch relfilenode */
+			gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+									   entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+									   false);
+			/* clean up footprint */
+			entry2->oldrelid = InvalidOid;
+			d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+			Assert(d_rnode);
+		}
+		else
+		{
+			if (entry->relfilenode_list == NIL)
+			{
+				if (entry->relkind == RELKIND_RELATION ||
+					entry->relkind == RELKIND_SEQUENCE)
+					gtt_storage_checkout(relid, false, isCommit);
+
+				gtt_free_statistics(entry);
+				hash_search(gtt_storage_local_hash,
+						(void *) &(relid), HASH_REMOVE, NULL);
+			}
+
+			return;
+		}
+	}
+
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	pfree(d_rnode);
+	if (entry->relfilenode_list == NIL)
+	{
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(relid, false, isCommit);
+
+		if (isCommit && entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			/* clean up footprint */
+			entry2->oldrelid = InvalidOid;
+		}
+
+		gtt_free_statistics(entry);
+		hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_REMOVE, NULL);
+	}
+	else
+		gtt_reset_statistics(entry);
+
+	return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool found = false;
+	gtt_local_hash_entry *entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry)
+		found = true;
+
+	return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS				status;
+	gtt_local_hash_entry		*entry;
+	SMgrRelation	*srels = NULL;
+	Oid				*relids = NULL;
+	char			*relkinds = NULL;
+	int			nrels = 0,
+				nfiles = 0,
+				maxrels = 0,
+				maxfiles = 0,
+				i = 0;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel;
+			RelFileNode 	rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel = smgropen(rnode, MyBackendId);
+
+			if (maxfiles == 0)
+			{
+				maxfiles = 32;
+				srels = palloc(sizeof(SMgrRelation) * maxfiles);
+			}
+			else if (maxfiles <= nfiles)
+			{
+				maxfiles *= 2;
+				srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+			}
+
+			srels[nfiles++] = srel;
+		}
+
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			relids  = palloc(sizeof(Oid) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			relids  = repalloc(relids , sizeof(Oid) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		relkinds[nrels] = entry->relkind;
+		relids[nrels] = entry->relid;
+		nrels++;
+	}
+
+	if (nfiles > 0)
+	{
+		smgrdounlinkall(srels, nfiles, false);
+		for (i = 0; i < nfiles; i++)
+			smgrclose(srels[i]);
+
+		pfree(srels);
+	}
+
+	if (nrels)
+	{
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+		for (i = 0; i < nrels; i++)
+		{
+			if (relkinds[i] == RELKIND_RELATION ||
+				relkinds[i] == RELKIND_SEQUENCE)
+				gtt_storage_checkout(relids[i], true, false);
+		}
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+		pfree(relids);
+		pfree(relkinds);
+	}
+
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+	return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible) 
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+					BlockNumber num_pages,
+					double num_tuples,
+					BlockNumber num_all_visible_pages,
+					TransactionId relfrozenxid,
+					TransactionId relminmxid)
+{
+	Oid		relid = RelationGetRelid(relation);
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (num_pages >= 0 &&
+		gtt_rnode->relpages != (int32)num_pages)
+		gtt_rnode->relpages = (int32)num_pages;
+
+	if (num_tuples >= 0 &&
+		gtt_rnode->reltuples != (float4)num_tuples)
+		gtt_rnode->reltuples = (float4)num_tuples;
+
+	/* only heap contain transaction information and relallvisible */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (gtt_rnode->relallvisible >= 0 &&
+			gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+		{
+			gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+		}
+
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNewTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible) 
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	MemoryContext			oldcontext;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return;
+
+	if (entry->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	Assert(entry->relid == reloid);
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == 0)
+		{
+			entry->attnum[i] = attnum;
+			break;
+		}
+		else if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < entry->natts);
+	Assert(entry->att_stat_tups[i] == NULL);
+	entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return NULL;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			return entry->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int			i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	HeapTuple	tuple;
+	Relation	rel = NULL;
+	int		attnum = PG_GETARG_INT32(1);
+	Oid		reloid = PG_GETARG_OID(0);
+	char		rel_persistence;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	  tupdesc;
+	MemoryContext oldcontext;
+	Tuplestorestate *tupstore;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+
+	relation_close(rel, NoLock);
+	relation_close(pg_tatistic, AccessShareLock);
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate	*tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Oid			relnode = 0;
+	Relation	rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[6];
+		bool	isnull[6];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(reloid);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(RelationGetRelid(index)));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid relOid = index->rd_index->indrelid;
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	if (gtt_storage_attached(relOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int			i;
+	Oid			toastrelid;
+
+	if (operation != CMD_INSERT)
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+
+		index_build(relation, index, info, true, false);
+	}
+
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid 		indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo 	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+static void
+gtt_reset_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+
+		entry->attnum[i] = 0;
+	}
+
+	return;
+}
+
+static void
+gtt_free_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (entry->attnum)
+		pfree(entry->attnum);
+
+	if (entry->att_stat_tups)
+		pfree(entry->att_stat_tups);
+
+	return;
+}
+
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
+void
+gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint)
+{
+	gtt_local_hash_entry	*entry1;
+	gtt_local_hash_entry	*entry2;
+	gtt_relfilenode			*gtt_rnode1 = NULL;
+	gtt_relfilenode			*gtt_rnode2 = NULL;
+	MemoryContext			oldcontext;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry1 = gtt_search_by_relid(rel1, false);
+	gtt_rnode1 = gtt_search_relfilenode(entry1, relfilenode1, false);
+
+	entry2 = gtt_search_by_relid(rel2, false);
+	gtt_rnode2 = gtt_search_relfilenode(entry2, relfilenode2, false);
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	entry1->relfilenode_list = list_delete_ptr(entry1->relfilenode_list, gtt_rnode1);
+	entry2->relfilenode_list = lappend(entry2->relfilenode_list, gtt_rnode1);
+
+	entry2->relfilenode_list = list_delete_ptr(entry2->relfilenode_list, gtt_rnode2);
+	entry1->relfilenode_list = lappend(entry1->relfilenode_list, gtt_rnode2);
+	MemoryContextSwitchTo(oldcontext);
+
+	if (footprint)
+	{
+		entry1->oldrelid = rel2;
+		entry2->oldrelid = rel1;
+	}
+
+	return;
+}
+
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok)
+{
+	gtt_relfilenode			*rnode = NULL;
+	ListCell				*lc;
+
+	Assert(entry);
+
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == relfilenode)
+		{
+			rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (!missing_ok && rnode == NULL)
+		elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+	return rnode;
+}
+
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry == NULL && !missing_ok)
+		elog(ERROR, "relid %u not found in local hash", relid);
+
+	return entry;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index d406ea8..88be041 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM 
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 924ef37..db12eef 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -184,6 +185,13 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -586,14 +594,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 		 * pg_statistic for columns we didn't process, we leave them alone.)
 		 */
 		update_attstats(RelationGetRelid(onerel), inh,
-						attr_cnt, vacattrstats);
+						attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
 
 		for (ind = 0; ind < nindexes; ind++)
 		{
 			AnlIndexData *thisdata = &indexdata[ind];
 
 			update_attstats(RelationGetRelid(Irel[ind]), false,
-							thisdata->attr_cnt, thisdata->vacattrstats);
+							thisdata->attr_cnt, thisdata->vacattrstats,
+							RelationGetRelPersistence(Irel[ind]));
 		}
 
 		/*
@@ -1456,7 +1465,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
  *		by taking a self-exclusive lock on the relation in analyze_rel().
  */
 static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
 {
 	Relation	sd;
 	int			attno;
@@ -1558,31 +1567,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/* Update column statistic to localhash, not catalog */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 04d12a7..f47035e 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/toasting.h"
 #include "commands/cluster.h"
 #include "commands/progress.h"
@@ -72,6 +73,12 @@ static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
 							bool verbose, bool *pSwapToastByContent,
 							TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
 static List *get_tables_to_cluster(MemoryContext cluster_context);
+static void gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables);
 
 
 /*---------------------------------------------------------------------------
@@ -366,6 +373,14 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap) &&
+		!gtt_storage_attached(RelationGetRelid(OldHeap)))
+	{
+		relation_close(OldHeap, AccessExclusiveLock);
+		pgstat_progress_end_command();
+		return;
+	}
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
@@ -750,6 +765,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	BlockNumber num_pages;
 	int			elevel = verbose ? INFO : DEBUG2;
 	PGRUsage	ru0;
+	bool		is_gtt = false;
+	uint32		gtt_relfrozenxid = 0;
+	uint32		gtt_relminmxid = 0;
 
 	pg_rusage_init(&ru0);
 
@@ -763,6 +781,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	else
 		OldIndex = NULL;
 
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		is_gtt = true;
+
 	/*
 	 * Their tuple descriptors should be exactly alike, but here we only need
 	 * assume that they have the same number of columns.
@@ -830,20 +851,37 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 						  &OldestXmin, &FreezeXid, NULL, &MultiXactCutoff,
 						  NULL);
 
-	/*
-	 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
-	 * backwards, so take the max.
-	 */
-	if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
-		TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
-		FreezeXid = OldHeap->rd_rel->relfrozenxid;
+	if (is_gtt)
+	{
+		get_gtt_relstats(OIDOldHeap,
+					NULL, NULL, NULL,
+					&gtt_relfrozenxid, &gtt_relminmxid);
 
-	/*
-	 * MultiXactCutoff, similarly, shouldn't go backwards either.
-	 */
-	if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
-		MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
-		MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+		if (TransactionIdIsValid(gtt_relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, gtt_relfrozenxid))
+			FreezeXid = gtt_relfrozenxid;
+
+		if (MultiXactIdIsValid(gtt_relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, gtt_relminmxid))
+			MultiXactCutoff = gtt_relminmxid;
+	}
+	else
+	{
+		/*
+		 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
+		 * backwards, so take the max.
+		 */
+		if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
+			FreezeXid = OldHeap->rd_rel->relfrozenxid;
+
+		/*
+		 * MultiXactCutoff, similarly, shouldn't go backwards either.
+		 */
+		if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
+			MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+	}
 
 	/*
 	 * Decide whether to use an indexscan or seqscan-and-optional-sort to scan
@@ -911,6 +949,12 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	table_close(OldHeap, NoLock);
 	table_close(NewHeap, NoLock);
 
+	if (is_gtt)
+	{
+		CommandCounterIncrement();
+		return;
+	}
+
 	/* Update pg_class to reflect the correct values of pages and tuples. */
 	relRelation = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1346,10 +1390,20 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 	 * Swap the contents of the heap relations (including any toast tables).
 	 * Also set old heap's relfrozenxid to frozenXid.
 	 */
-	swap_relation_files(OIDOldHeap, OIDNewHeap,
+	if (newrelpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		gtt_swap_relation_files(OIDOldHeap, OIDNewHeap,
+								(OIDOldHeap == RelationRelationId),
+								swap_toast_by_content, is_internal,
+								frozenXid, cutoffMulti, mapped_tables);
+	}
+	else
+	{
+		swap_relation_files(OIDOldHeap, OIDNewHeap,
 						(OIDOldHeap == RelationRelationId),
 						swap_toast_by_content, is_internal,
 						frozenXid, cutoffMulti, mapped_tables);
+	}
 
 	/*
 	 * If it's a system catalog, queue a sinval message to flush all catcaches
@@ -1557,3 +1611,141 @@ get_tables_to_cluster(MemoryContext cluster_context)
 
 	return rvs;
 }
+
+static void
+gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables)
+{
+	Relation	relRelation;
+	Oid			relfilenode1,
+				relfilenode2;
+	Relation	rel1;
+	Relation	rel2;
+
+	relRelation = table_open(RelationRelationId, RowExclusiveLock);
+
+	rel1 = relation_open(r1, AccessExclusiveLock);
+	rel2 = relation_open(r2, AccessExclusiveLock);
+
+	relfilenode1 = gtt_fetch_current_relfilenode(r1);
+	relfilenode2 = gtt_fetch_current_relfilenode(r2);
+
+	Assert(OidIsValid(relfilenode1) && OidIsValid(relfilenode2));
+	gtt_switch_rel_relfilenode(r1, relfilenode1, r2, relfilenode2, true);
+
+	CacheInvalidateRelcache(rel1);
+	CacheInvalidateRelcache(rel2);
+
+	InvokeObjectPostAlterHookArg(RelationRelationId, r1, 0,
+								 InvalidOid, is_internal);
+	InvokeObjectPostAlterHookArg(RelationRelationId, r2, 0,
+								 InvalidOid, true);
+
+	if (rel1->rd_rel->reltoastrelid || rel2->rd_rel->reltoastrelid)
+	{
+		if (swap_toast_by_content)
+		{
+			if (rel1->rd_rel->reltoastrelid && rel2->rd_rel->reltoastrelid)
+			{
+				gtt_swap_relation_files(rel1->rd_rel->reltoastrelid,
+									rel2->rd_rel->reltoastrelid,
+									target_is_pg_class,
+									swap_toast_by_content,
+									is_internal,
+									frozenXid,
+									cutoffMulti,
+									mapped_tables);
+			}
+			else
+				elog(ERROR, "cannot swap toast files by content when there's only one");
+		}
+		else
+		{
+			ObjectAddress baseobject,
+						toastobject;
+			long		count;
+
+			if (IsSystemRelation(rel1))
+				elog(ERROR, "cannot swap toast files by links for system catalogs");
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel1->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel2->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+
+			/* Register new dependencies */
+			baseobject.classId = RelationRelationId;
+			baseobject.objectSubId = 0;
+			toastobject.classId = RelationRelationId;
+			toastobject.objectSubId = 0;
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r1;
+				toastobject.objectId = rel1->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r2;
+				toastobject.objectId = rel2->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+		}
+	}
+
+	if (swap_toast_by_content &&
+		rel1->rd_rel->relkind == RELKIND_TOASTVALUE &&
+		rel2->rd_rel->relkind == RELKIND_TOASTVALUE)
+	{
+		Oid			toastIndex1,
+					toastIndex2;
+
+		/* Get valid index for each relation */
+		toastIndex1 = toast_get_valid_index(r1,
+											AccessExclusiveLock);
+		toastIndex2 = toast_get_valid_index(r2,
+											AccessExclusiveLock);
+
+		gtt_swap_relation_files(toastIndex1,
+							toastIndex2,
+							target_is_pg_class,
+							swap_toast_by_content,
+							is_internal,
+							InvalidTransactionId,
+							InvalidMultiXactId,
+							mapped_tables);
+	}
+
+	relation_close(rel1, NoLock);
+	relation_close(rel2, NoLock);
+
+	table_close(relRelation, RowExclusiveLock);
+
+	RelationCloseSmgrByOid(r1);
+	RelationCloseSmgrByOid(r2);
+
+	CommandCounterIncrement();
+}
+
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index ac07f75..655184d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -27,6 +27,7 @@
 #include "catalog/dependency.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -1063,7 +1064,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, stmt->filename, stmt->is_program,
@@ -2794,6 +2795,8 @@ CopyFrom(CopyState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	estate->es_result_relations = resultRelInfo;
 	estate->es_num_result_relations = 1;
 	estate->es_result_relation_info = resultRelInfo;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 2baca12..5338db9 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -521,6 +521,7 @@ DefineIndex(Oid relationId,
 	Snapshot	snapshot;
 	int			save_nestlevel = -1;
 	int			i;
+	char		rel_persistence;
 
 	/*
 	 * Some callers need us to run with an empty default_tablespace; this is a
@@ -542,7 +543,9 @@ DefineIndex(Oid relationId,
 	 * is more efficient.  Do this before any use of the concurrent option is
 	 * done.
 	 */
-	if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+	rel_persistence = get_rel_persistence(relationId);
+	if (stmt->concurrent &&
+		!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		concurrent = true;
 	else
 		concurrent = false;
@@ -2460,7 +2463,8 @@ ReindexIndex(RangeVar *indexRelation, int options, bool concurrent)
 	persistence = irel->rd_rel->relpersistence;
 	index_close(irel, NoLock);
 
-	if (concurrent && persistence != RELPERSISTENCE_TEMP)
+	if (concurrent &&
+		!(persistence == RELPERSISTENCE_TEMP || persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		ReindexRelationConcurrently(indOid, options);
 	else
 		reindex_index(indOid, false, persistence,
@@ -2546,6 +2550,7 @@ ReindexTable(RangeVar *relation, int options, bool concurrent)
 {
 	Oid			heapOid;
 	bool		result;
+	char		rel_persistence;
 
 	/*
 	 * The lock level used here should match reindex_relation().
@@ -2560,7 +2565,9 @@ ReindexTable(RangeVar *relation, int options, bool concurrent)
 									   0,
 									   RangeVarCallbackOwnsTable, NULL);
 
-	if (concurrent && get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+	rel_persistence = get_rel_persistence(heapOid);
+	if (concurrent &&
+		!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 	{
 		result = ReindexRelationConcurrently(heapOid, options);
 
@@ -2761,12 +2768,15 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 	foreach(l, relids)
 	{
 		Oid			relid = lfirst_oid(l);
+		char		rel_persistence;
 
 		StartTransactionCommand();
 		/* functions in indexes may want a snapshot set */
 		PushActiveSnapshot(GetTransactionSnapshot());
 
-		if (concurrent && get_rel_persistence(relid) != RELPERSISTENCE_TEMP)
+		rel_persistence = get_rel_persistence(relid);
+		if (concurrent &&
+			!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		{
 			(void) ReindexRelationConcurrently(relid, options);
 			/* ReindexRelationConcurrently() does the verbose output */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..a45863a 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -94,7 +96,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -222,7 +225,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* now initialize the sequence's data */
 	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -327,7 +330,7 @@ ResetSequence(Oid seq_relid)
 	/*
 	 * Insert the modified tuple into the new storage file.
 	 */
-	fill_seq_with_data(seq_rel, tuple);
+	fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -340,17 +343,21 @@ ResetSequence(Oid seq_relid)
  * Initialize a sequence's relation with the specified tuple as content
  */
 static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 	sequence_magic *sm;
 	OffsetNumber offnum;
+	bool lockBuffer = false;
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ReadBuffer(rel, P_NEW);
-	Assert(BufferGetBlockNumber(buf) == 0);
+	if (buf == InvalidBuffer)
+	{
+		buf = ReadBuffer(rel, P_NEW);
+		Assert(BufferGetBlockNumber(buf) == 0);
+		lockBuffer = true;
+	}
 
 	page = BufferGetPage(buf);
 
@@ -360,7 +367,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* Now insert sequence tuple */
 
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+	if (lockBuffer)
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +418,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	END_CRIT_SECTION();
 
-	UnlockReleaseBuffer(buf);
+	if (lockBuffer)
+		UnlockReleaseBuffer(buf);
 }
 
 /*
@@ -451,6 +460,13 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 
 	init_sequence(relid, &elm, &seqrel);
 
+	if (RELATION_IS_GLOBAL_TEMP(seqrel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(seqrel)))
+			elog(ERROR, "cannot alter global temp sequence %s when other backend attached it",
+						RelationGetRelationName(seqrel));
+	}
+
 	rel = table_open(SequenceRelationId, RowExclusiveLock);
 	seqtuple = SearchSysCacheCopy1(SEQRELID,
 								   ObjectIdGetDatum(relid));
@@ -502,7 +518,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * Insert the modified tuple into the new storage file.
 		 */
-		fill_seq_with_data(seqrel, newdatatuple);
+		fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
 	}
 
 	/* process OWNED BY if given */
@@ -611,7 +627,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +952,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1154,6 +1170,13 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1954,3 +1977,46 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+void
+gtt_init_seq(Relation rel)
+{
+	/* Initialize sequence for global temporary tables */
+	Datum		value[SEQ_COL_LASTCOL] = {0};
+	bool		null[SEQ_COL_LASTCOL] = {false};
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 6162fb0..e825c04 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -555,6 +556,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 								  Relation partitionTbl);
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static OnCommitAction gtt_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -600,6 +602,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -610,8 +613,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Check consistency of arguments
 	 */
+	/* global temp table same as local temp table */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !(stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+			 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -641,7 +646,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	/* global temp table same as local temp table */
+	if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+		 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -742,6 +749,55 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* global temp table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* check parent table*/
+		if (inheritOids)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			table_close(relation, NoLock);
+		}
+
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temp table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1329,6 +1385,7 @@ RemoveRelations(DropStmt *drop)
 		Oid			relOid;
 		ObjectAddress obj;
 		struct DropRelationCallbackState state;
+		char		rel_persistence;
 
 		/*
 		 * These next few steps are a great deal like relation_openrv, but we
@@ -1362,8 +1419,9 @@ RemoveRelations(DropStmt *drop)
 		 * Decide if concurrent mode needs to be used here or not.  The
 		 * relation persistence cannot be known without its OID.
 		 */
+		rel_persistence = get_rel_persistence(relOid);
 		if (drop->concurrent &&
-			get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+			!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		{
 			Assert(list_length(drop->objects) == 1 &&
 				   drop->removeType == OBJECT_INDEX);
@@ -1818,6 +1876,10 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
 		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 			continue;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			continue;
+
 		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
@@ -3569,6 +3631,14 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			elog(ERROR, "can not alter table %s when other backend attached this global temp table",
+						RelationGetRelationName(rel));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -4846,6 +4916,12 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot rewrite temporary tables of other sessions")));
 
+			if (RELATION_IS_GLOBAL_TEMP(OldHeap) &&
+				tab->chgPersistence)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot change global temporary table persistence setting")));
+
 			/*
 			 * Select destination tablespace (same as original unless user
 			 * requested a change)
@@ -8183,6 +8259,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -12739,6 +12821,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -12941,6 +13026,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temp table");
+
 	/*
 	 * No work if no change in tablespace.
 	 */
@@ -13315,7 +13403,7 @@ index_copy_data(Relation rel, RelFileNode newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
@@ -14722,7 +14810,9 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
 	 */
 	switch (rel->rd_rel->relpersistence)
 	{
+		/* global temp table same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -17315,3 +17405,36 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
 		table_close(rel, NoLock);
 	}
 }
+
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			bool	res = false;
+			char	*sval = defGetString(def);
+
+			if (!parse_bool(sval, &res))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
+
+			if (res)
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 3a89f8f..d3e894a 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/vacuum.h"
@@ -1217,6 +1218,17 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	 /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		is_gtt = true;
+		up_gtt_relstats(relation,
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1230,17 +1242,26 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relpages = (int32) num_pages;
+	else if (pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (is_gtt)
+		relation->rd_rel->reltuples = (float4) num_tuples;
+	else if (pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	else if (pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1285,7 +1306,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1296,7 +1318,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1394,6 +1417,10 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/* global temp table relstats not in pg_class */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1451,6 +1478,27 @@ 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 relfrozenxid %u is the oldest in the entire db",
+						oldest_gtt_frozenxid),
+				 errdetail("The oldest relfrozenxid in pg_class is %u", newFrozenXid),
+				 errhint("If they differ greatly, please consider cleaning up the data in global temp table.")));
+			newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1778,6 +1826,15 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6e65103..04706ee 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -530,6 +530,11 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 4fdffad..0aa7559 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index fb6ce49..06d9237 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
@@ -545,6 +546,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	init_gtt_storage(mtstate->operation, leaf_part_rri);
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d71c0a4..0900907 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,6 +42,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -2372,6 +2373,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			ExecOpenIndices(resultRelInfo,
 							node->onConflictAction != ONCONFLICT_NONE);
 
+		init_gtt_storage(operation, resultRelInfo);
+
 		/*
 		 * If this is an UPDATE and a BEFORE UPDATE trigger is present, the
 		 * trigger itself might modify the partition-key values. So arrange
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 255f56b..d94d6f0 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 b31c524..a70b11b 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6421,7 +6421,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
+	/* global temp table is same as local temp table */
 	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 2554502..04fb5d4 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -28,6 +28,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temp table */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index eee9c33..1891c53 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2594,6 +2594,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 1219ac8..70e5a93 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3302,17 +3302,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; }
@@ -11638,19 +11632,13 @@ OptTempTableName:
 				}
 			| GLOBAL TEMPORARY opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED opt_table qualified_name
 				{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b875a50..123945b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 350959a..38df309 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
@@ -3089,6 +3092,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		cxt.isforeign = false;
 	}
 	cxt.relation = stmt->relation;
+	cxt.relation->relpersistence = RelationGetRelPersistence(rel);
 	cxt.rel = rel;
 	cxt.inhRelations = NIL;
 	cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 7e97ffa..1f2ac58 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2089,6 +2089,11 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* autovacuum skip vacuum global temp table */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2155,7 +2160,9 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		/* autovacuum skip vacuum global temp table */
+		if (classForm->relpersistence == RELPERSISTENCE_TEMP || 
+			classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index f9980cf..406a25c 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -53,6 +54,7 @@
 #include "utils/rel.h"
 #include "utils/resowner_private.h"
 #include "utils/timestamp.h"
+#include "utils/guc.h"
 
 
 /* Note: these two macros only work on shared buffers, not local ones! */
@@ -2819,6 +2821,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d5..7adb969 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -21,6 +21,7 @@
 #include "access/nbtree.h"
 #include "access/subtrans.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, active_gtt_shared_hash_size());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* global temporary table */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 3630006..0c8ef80 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -63,6 +63,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -4086,3 +4087,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 5aa19d3..b3bc455 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 8406644..0d99321 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -1008,6 +1008,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 4fdcb07..58fc8de 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -112,6 +112,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -4731,12 +4732,25 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -4861,15 +4875,27 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6251,6 +6277,7 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	{
 		/* Simple variable --- look to stats for the underlying table */
 		RangeTblEntry *rte = planner_rt_fetch(index->rel->relid, root);
+		char	rel_persistence = get_rel_persistence(rte->relid);
 
 		Assert(rte->rtekind == RTE_RELATION);
 		relid = rte->relid;
@@ -6268,6 +6295,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6279,6 +6313,8 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	}
 	else
 	{
+		char	rel_persistence = get_rel_persistence(index->indexoid);
+
 		/* Expression --- maybe there are stats for the index itself */
 		relid = index->indexoid;
 		colnum = 1;
@@ -6294,6 +6330,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7212,6 +7255,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		/* attempt to lookup stats in relation for this index column */
 		if (attnum != 0)
 		{
+			char	rel_persistence = get_rel_persistence(rte->relid);
+
 			/* Simple variable -- look to stats for the underlying table */
 			if (get_relation_stats_hook &&
 				(*get_relation_stats_hook) (root, rte, attnum, &vardata))
@@ -7224,6 +7269,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7236,6 +7289,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		}
 		else
 		{
+			char	rel_persistence = get_rel_persistence(index->indexoid);
+
 			/*
 			 * Looks like we've found an expression column in the index. Let's
 			 * see if there's any stats for it.
@@ -7255,6 +7310,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index a7d63f1..d8a52cb 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -2939,6 +2940,18 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 9f1f11d..aeb2a78 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -65,6 +65,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1133,6 +1134,24 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1187,6 +1206,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			gtt_fix_index_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -1317,7 +1337,17 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+			if (newrelnode != InvalidOid &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2246,6 +2276,8 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		gtt_fix_index_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3472,6 +3504,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3579,28 +3615,34 @@ void
 RelationSetNewRelfilenode(Relation relation, char persistence)
 {
 	Oid			newrelfilenode;
-	Relation	pg_class;
-	HeapTuple	tuple;
+	Relation	pg_class = NULL;
+	HeapTuple	tuple = NULL;
 	Form_pg_class classform;
 	MultiXactId minmulti = InvalidMultiXactId;
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
+	bool		modify_pg_class = !RELATION_IS_GLOBAL_TEMP(relation);
 
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
 
-	/*
-	 * Get a writable copy of the pg_class tuple for the given relation.
-	 */
-	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+	if (modify_pg_class)
+	{
+		/*
+		 * Get a writable copy of the pg_class tuple for the given relation.
+		 */
+		pg_class = table_open(RelationRelationId, RowExclusiveLock);
 
-	tuple = SearchSysCacheCopy1(RELOID,
-								ObjectIdGetDatum(RelationGetRelid(relation)));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "could not find tuple for relation %u",
-			 RelationGetRelid(relation));
-	classform = (Form_pg_class) GETSTRUCT(tuple);
+		tuple = SearchSysCacheCopy1(RELOID,
+									ObjectIdGetDatum(RelationGetRelid(relation)));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u",
+				 RelationGetRelid(relation));
+		classform = (Form_pg_class) GETSTRUCT(tuple);
+	}
+	else
+		memset(&classform, 0, sizeof(classform));
 
 	/*
 	 * Schedule unlinking of the old storage at transaction commit.
@@ -3626,7 +3668,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;
@@ -3646,6 +3688,15 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 			break;
 	}
 
+	if (!modify_pg_class)
+	{
+		Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+		Assert(RELATION_IS_GLOBAL_TEMP(relation));
+		Assert(!RelationIsMapped(relation));
+		relation->rd_node.relNode = relnode;
+		CacheInvalidateRelcache(relation);
+	}
 	/*
 	 * If we're dealing with a mapped index, pg_class.relfilenode doesn't
 	 * change; instead we have to send the update to the relation mapper.
@@ -3655,7 +3706,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	 * possibly-inaccurate values of relpages etc, but those will be fixed up
 	 * later.
 	 */
-	if (RelationIsMapped(relation))
+	else if (RelationIsMapped(relation))
 	{
 		/* This case is only supported for indexes */
 		Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3701,9 +3752,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 		CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 	}
 
-	heap_freetuple(tuple);
+	if (modify_pg_class)
+	{
+		heap_freetuple(tuple);
 
-	table_close(pg_class, RowExclusiveLock);
+		table_close(pg_class, RowExclusiveLock);
+	}
 
 	/*
 	 * Make the pg_class row change or relation map change visible.  This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 5bdc02f..7747dde 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -142,6 +142,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temp table feature.
+ * global temp table define can still storage in catalog
+ * just can not use.
+ * num > 0 means database can management num active global temp table.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2070,6 +2082,15 @@ static struct config_bool ConfigureNamesBool[] =
 static struct config_int ConfigureNamesInt[] =
 {
 	{
+		{"max_active_global_temporary_table", PGC_POSTMASTER, UNGROUPED,
+			gettext_noop("max active global temporary table."),
+			NULL
+		},
+		&max_active_gtt,
+		DEFAULT_NUM_ACTIVE_GTT, MIN_NUM_ACTIVE_GTT, MAX_NUM_ACTIVE_GTT,
+		NULL, NULL, NULL
+	},
+	{
 		{"archive_timeout", PGC_SIGHUP, WAL_ARCHIVING,
 			gettext_noop("Forces a switch to the next WAL file if a "
 						 "new file has not been started within N seconds."),
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index c579227..e03557b 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2424,6 +2424,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
 		dopt->no_unlogged_table_data)
 		return;
 
+	/* Don't dump data in global temp table/sequence */
+	if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		return;
+
 	/* Check that the data is not explicitly excluded */
 	if (simple_oid_list_member(&tabledata_exclude_oids,
 							   tbinfo->dobj.catId.oid))
@@ -15772,6 +15776,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
 		char	   *foreign = "";
+		char	   *table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15825,9 +15830,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);
 
@@ -16221,7 +16232,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
-			 tbinfo->relkind == RELKIND_MATVIEW))
+			 tbinfo->relkind == RELKIND_MATVIEW) &&
+			 tbinfo->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
 		{
 			appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
 			appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
@@ -17126,6 +17138,7 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	PQExpBuffer query = createPQExpBuffer();
 	PQExpBuffer delqry = createPQExpBuffer();
 	char	   *qseqname;
+	bool	   global_temp_seq = false;
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
@@ -17135,9 +17148,11 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
-						  "FROM pg_catalog.pg_sequence "
-						  "WHERE seqrelid = '%u'::oid",
+						  "seqcache, seqcycle, c.relpersistence "
+						  "FROM pg_catalog.pg_sequence s, "
+						  "pg_catalog.pg_class c "
+						  "WHERE seqrelid = '%u'::oid "
+						  "and s.seqrelid = c.oid",
 						  tbinfo->dobj.catId.oid);
 	}
 	else if (fout->remoteVersion >= 80400)
@@ -17182,6 +17197,9 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
 
+	if (fout->remoteVersion >= 130000)
+		global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
 	if (strcmp(seqtype, "smallint") == 0)
@@ -17256,9 +17274,13 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	}
 	else
 	{
-		appendPQExpBuffer(query,
-						  "CREATE SEQUENCE %s\n",
-						  fmtQualifiedDumpable(tbinfo));
+		appendPQExpBuffer(query, "CREATE ");
+
+		if (global_temp_seq)
+			appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+		appendPQExpBuffer(query, "SEQUENCE %s\n",
+								  fmtQualifiedDumpable(tbinfo));
 
 		if (strcmp(seqtype, "bigint") != 0)
 			appendPQExpBuffer(query, "    AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 00aef85..9ad71c8 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -84,7 +84,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -157,7 +157,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 7e524ea..371168f 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -441,22 +443,46 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 	 * output, so we have to copy that system table.  It's easiest to do that
 	 * by treating it as a user table.
 	 */
-	snprintf(query + strlen(query), sizeof(query) - strlen(query),
-			 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
-			 "  SELECT c.oid, 0::oid, 0::oid "
-			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
-			 "         ON c.relnamespace = n.oid "
-			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
-	/* exclude possible orphaned temp tables */
-			 "    ((n.nspname !~ '^pg_temp_' AND "
-			 "      n.nspname !~ '^pg_toast_temp_' AND "
-			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
-			 "                        'binary_upgrade', 'pg_toast') AND "
-			 "      c.oid >= %u::pg_catalog.oid) OR "
-			 "     (n.nspname = 'pg_catalog' AND "
-			 "      relname IN ('pg_largeobject') ))), ",
-			 FirstNormalObjectId);
+	if (skip_gtt)
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude global temp tables */
+				 "    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
+	else
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
 
 	/*
 	 * Add a CTE that collects OIDs of toast tables belonging to the tables
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 70194eb..8dd9e66 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -411,7 +411,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -642,7 +642,9 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -653,7 +655,9 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 8b90cef..d155205 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -389,7 +389,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index f05e914..19adab9 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3724,7 +3724,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 0e7a373..a47de01 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1023,6 +1023,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2390,6 +2392,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2598,6 +2603,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 78b33b2..4be89f6 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -168,6 +168,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad..c8236a1 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5558,6 +5558,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4388',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4389',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4390',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4391',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 30c38e0..7ff2408 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,7 +22,7 @@
 /* GUC variables */
 extern int	wal_skip_threshold;
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationPreTruncate(Relation rel);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..45fd470
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,45 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Relation relation,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void gtt_force_enable_index(Relation index);
+extern void gtt_fix_index_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+extern void gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index e2638ab..89a5ce4 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 3f88683..f870e9a 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 8fda8e4..172eae9 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_TBM,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_SXACT,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index ae4f573..69cce92 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -117,6 +117,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId session_gtt_frozenxid;	/* session level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 2819282..363fc33 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -282,6 +282,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 0b5957b..0720a4c 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	struct SMgrRelationData *rd_smgr;	/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -305,6 +305,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -570,11 +571,13 @@ typedef struct ViewOptions
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -582,6 +585,7 @@ typedef struct ViewOptions
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -594,6 +598,14 @@ typedef struct ViewOptions
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is either local temp relation of this session
+ * 		or global temp relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -637,6 +649,17 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..ca2d135
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,14 @@
+reset search_path;
+select pg_sleep(5);
+ pg_sleep 
+----------
+ 
+(1 row)
+
+drop schema gtt cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
+drop cascades to table gtt.gtt_with_seq
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..d6a675a
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,383 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+ n 
+---
+ 9
+(1 row)
+
+commit;
+-- 0 row
+select * from gtt6;
+ n 
+---
+(0 rows)
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temp table
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  could not create global temporary table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR:  insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count 
+-------
+     1
+(1 row)
+
+select count(*) from orders;
+ count 
+-------
+     0
+(1 row)
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  2 |  3
+(2 rows)
+
+commit;
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+select * from gtt_s_2 order by c1;
+ c1 | c2 
+----+----
+  1 |  2
+  2 |  4
+(2 rows)
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 1)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 300)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 33 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test_new
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.gtt6
+drop cascades to table gtt_function.foo
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to sequence gtt_function.seq_1
+drop cascades to table gtt_function.gtt_s_1
+drop cascades to table gtt_function.gtt_s_2
+drop cascades to table gtt_function.gt1
+drop cascades to table gtt_function.gtt_test1
+drop cascades to table gtt_function.gtt_test2
+drop cascades to table gtt_function.gtt_test3
+drop cascades to table gtt_function.gtt_test4
+drop cascades to table gtt_function.gtt_test5
+drop cascades to table gtt_function.gtt_test6
+drop cascades to table gtt_function.gtt_test7
+drop cascades to table gtt_function.gtt_test8
+drop cascades to table gtt_function.gtt_test9
+drop cascades to table gtt_function.gtt_test10
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..0646aae
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,90 @@
+set search_path=gtt,sys;
+select nextval('gtt_with_seq_c2_seq');
+ nextval 
+---------
+       1
+(1 row)
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..2f084be
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,343 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+rollback;
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+commit;
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          114688 |                 614400
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |            49152 |         49152 |                  49152
+(2 rows)
+
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2000
+(1 row)
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+ c1 | c2 
+----+----
+  1 |  1
+(1 row)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..8c0c376
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,10 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..4420fdb
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,80 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+ gtt_stats  | gtt_pkey  |        1 |         0 |             0
+(2 rows)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+reindex table gtt;
+reindex index gtt_pkey;
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+ gtt_stats  | gtt_pkey  |       30 |     10000 |             0
+(2 rows)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index ac31840..55b221e 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1349,6 +1349,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 95f1925..76b2374 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..2c8e586
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,8 @@
+
+
+reset search_path;
+
+select pg_sleep(5);
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..81f0bfc
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,253 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+commit;
+-- 0 row
+select * from gtt6;
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+commit;
+select * from gtt_s_1 order by c1;
+select * from gtt_s_2 order by c1;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d05745e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,44 @@
+
+
+set search_path=gtt,sys;
+
+select nextval('gtt_with_seq_c2_seq');
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..39cca5e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,154 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+rollback;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+commit;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+select count(*) from gtt_t_kenyon;
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..dbe84d1
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,19 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..d61b0ff
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,46 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+reindex table gtt;
+
+reindex index gtt_pkey;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#238曾文旌
wenjing.zwj@alibaba-inc.com
In reply to: tushar (#235)
1 attachment(s)
Re: [Proposal] Global temporary tables

2020年4月9日 下午7:46,tushar <tushar.ahuja@enterprisedb.com> 写道:

On 4/7/20 2:27 PM, 曾文旌 wrote:

Vacuum full GTT, cluster GTT is already supported in global_temporary_table_v24-pg13.patch.

Hi Wenjing,

Please refer this scenario , where reindex message is not coming next time ( after reconnecting to database) for GTT

A)
--normal table
postgres=# create table nt(n int primary key);
CREATE TABLE
--GTT table
postgres=# create global temp table gtt(n int primary key);
CREATE TABLE
B)
--Reindex , normal table
postgres=# REINDEX (VERBOSE) TABLE nt;
INFO: index "nt_pkey" was reindexed
DETAIL: CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s
REINDEX
--reindex GTT table
postgres=# REINDEX (VERBOSE) TABLE gtt;
INFO: index "gtt_pkey" was reindexed
DETAIL: CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s
REINDEX
C)
--Reconnect to database
postgres=# \c
You are now connected to database "postgres" as user "tushar".
D) again perform step B)

postgres=# REINDEX (VERBOSE) TABLE nt;
INFO: index "nt_pkey" was reindexed
DETAIL: CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s
REINDEX
postgres=# REINDEX (VERBOSE) TABLE gtt; <-- message not coming
REINDEX

Yes , Since the newly established connection is on the db, the GTT store file is not initialized, so there is no info message.

Show quoted text

--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company

Attachments:

smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#239曾文旌
wenjing.zwj@alibaba-inc.com
In reply to: Erik Rijkers (#230)
2 attachment(s)
Re: [Proposal] Global temporary tables

2020年4月7日 下午6:40,Erik Rijkers <er@xs4all.nl> 写道:

On 2020-04-07 10:57, 曾文旌 wrote:

[global_temporary_table_v24-pg13.patch ]

Hi,

With gcc 9.3.0 (debian stretch), I see some low-key protests during the build:

index.c: In function ‘index_drop’:
index.c:2051:8: warning: variable ‘rel_persistence’ set but not used [-Wunused-but-set-variable]
2051 | char rel_persistence;
| ^~~~~~~~~~~~~~~
storage_gtt.c: In function ‘gtt_force_enable_index’:
storage_gtt.c:1252:6: warning: unused variable ‘indexOid’ [-Wunused-variable]
1252 | Oid indexOid = RelationGetRelid(index);
| ^~~~~~~~
cluster.c: In function ‘copy_table_data’:
cluster.c:780:2: warning: this ‘if’ clause does not guard... [-Wmisleading-indentation]
780 | if (RELATION_IS_GLOBAL_TEMP(OldHeap));
| ^~
cluster.c:781:3: note: ...this statement, but the latter is misleadingly indented as if it were guarded by the ‘if’
781 | is_gtt = true;
| ^~~~~~

Part of the problem is that some variables are only used by assert statements, and I fixed those alarms.
Please provide your configue parameter, and I will verify it again.

Wenjing

Attachments:

global_temporary_table_v25-pg13.patchapplication/octet-stream; name=global_temporary_table_v25-pg13.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 8ccc228..b061de9 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -168,6 +168,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * For global temp table only
+	 * use ShareUpdateExclusiveLock for ensure safety
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temp table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},	
 	/* list terminator */
 	{{NULL}}
 };
@@ -1817,6 +1830,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1961,13 +1976,18 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	/*
 	 * There are no options for partitioned tables yet, but this is able to do
 	 * some validation.
 	 */
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index 765329b..4761fdc 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1011,7 +1011,9 @@ gistproperty(Oid index_oid, int attno,
 XLogRecPtr
 gistGetFakeLSN(Relation rel)
 {
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	/* global temp is same as local temp table */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 3ec6d52..1222594 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -150,7 +150,9 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	/* global temp table is same as local temp table */
+	if (!(index->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		index->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 56b3562..d3443ec 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -587,7 +587,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -640,7 +640,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index f3382d3..7a0746a 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -62,6 +62,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -431,9 +432,9 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
 	Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
-	/* not every AM requires these to be valid, but heap does */
-	Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
-	Assert(MultiXactIdIsValid(onerel->rd_rel->relminmxid));
+	/* not every AM requires these to be valid, but regular heap does */
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ MultiXactIdIsValid(onerel->rd_rel->relminmxid));
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 39b8f17..abb76dc 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -601,6 +602,10 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index c38bc14..e82a9cf 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6422,6 +6422,10 @@ StartupXLOG(void)
 	else
 		recoveryTargetTLI = ControlFile->checkPointCopy.ThisTimeLineID;
 
+	/* clean temp relation files */
+	if (max_active_gtt > 0)
+		RemovePgTempFiles();
+
 	/*
 	 * Check for signal files, and if so set up state for offline recovery
 	 */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 9499bb3..ae47364 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -43,6 +43,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 7d6acae..3be8d63 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -393,7 +393,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/* global temp table is same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 632c058..01cf990 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -427,7 +429,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -959,6 +961,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -997,8 +1000,18 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/* global temp table not remember transaction info in catalog */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1360,6 +1373,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1940,6 +1954,14 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/* We allow to drop global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			elog(ERROR, "can not drop relation %s when other backend attached this global temp table",
+						RelationGetRelationName(rel));
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3167,7 +3189,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3179,7 +3201,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3225,8 +3247,13 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		/* truncate global temp table only need RowExclusiveLock */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3259,6 +3286,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3267,23 +3295,37 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (!gtt_storage_attached(RelationGetRelid(rel)))
+			return;
+
+		/*
+		 * Truncate global temp table only need RowExclusiveLock
+		 */
+		lockmode = RowExclusiveLock;
+	}
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(rel, 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index bd7ec92..747cda0 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -53,6 +53,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -881,6 +882,19 @@ index_create(Relation heapRelation,
 						indexRelationName, RelationGetRelationName(heapRelation))));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temp table use concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot reindex global temporary tables concurrently")));
+
+		/* if global temp table not init storage, then skip build index */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -2041,7 +2055,8 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 * lock (see comments in RemoveRelations), and a non-concurrent DROP is
 	 * more efficient.
 	 */
-	Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+	Assert(!(get_rel_persistence(indexId) == RELPERSISTENCE_TEMP ||
+			 get_rel_persistence(indexId) == RELPERSISTENCE_GLOBAL_TEMP) ||
 		   (!concurrent && !concurrent_lock_mode));
 
 	/*
@@ -2073,6 +2088,14 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/* We allow to drop index on global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+			elog(ERROR, "can not drop index %s when other backend attached this global temp table.",
+						RelationGetRelationName(userHeapRelation));
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2681,6 +2704,11 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	/* update index stats into localhash and rel_rd_rel for global temp table */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		is_gtt = true;
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2766,21 +2794,35 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
+		if (is_gtt)
+			rel->rd_rel->relpages = (int32) relpages;
+		else if (rd_rel->relpages != (int32) relpages)
 		{
 			rd_rel->relpages = (int32) relpages;
 			dirty = true;
 		}
-		if (rd_rel->reltuples != (float4) reltuples)
+
+		if (is_gtt)
+			rel->rd_rel->reltuples = (float4) reltuples;
+		else if (rd_rel->reltuples != (float4) reltuples)
 		{
 			rd_rel->reltuples = (float4) reltuples;
 			dirty = true;
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+
+		if (is_gtt)
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+		else if (rd_rel->relallvisible != (int32) relallvisible)
 		{
 			rd_rel->relallvisible = (int32) relallvisible;
 			dirty = true;
 		}
+
+		if (is_gtt)
+		{
+			up_gtt_relstats(rel, relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
+		}
 	}
 
 	/*
@@ -2894,6 +2936,15 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, index, val);
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			gtt_force_enable_index(indexRelation);
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3442,6 +3493,10 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	PGRUsage	ru0;
 	bool		progress = (options & REINDEXOPT_REPORT_PROGRESS) != 0;
 
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		!gtt_storage_attached(indexId))
+		return;
+
 	pg_rusage_init(&ru0);
 
 	/*
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 2ec2301..1b6061d 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -655,6 +655,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		/* global temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp relations in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index d713d5c..afcf004 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,6 +27,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -61,6 +62,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			relOid;			/* InvalidOid if not a global temp rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -115,7 +117,7 @@ AddPendingSync(const RelFileNode *rnode)
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -127,6 +129,8 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		/* global temp table use same storage strategy as local temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -154,6 +158,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->relOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
@@ -165,6 +170,13 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		AddPendingSync(&rnode);
 	}
 
+	/* remember global temp table storage info to localhash */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel)
+	{
+		pending->relOid = RelationGetRelid(rel);
+		remember_gtt_storage_info(rnode, rel);
+	}
+
 	return srel;
 }
 
@@ -201,11 +213,15 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->relOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->relOid = RelationGetRelid(rel);
+
 	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
@@ -602,6 +618,7 @@ smgrDoPendingDeletes(bool isCommit)
 				i = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -631,14 +648,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->relOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -651,9 +672,18 @@ smgrDoPendingDeletes(bool isCommit)
 		smgrdounlinkall(srels, nrels, false);
 
 		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* clean global temp table flags when transaction commit or rollback */
+			if (SmgrIsTemp(srels[i]) &&
+				reloids[i] != InvalidOid &&
+				gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..be6a37b
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1489 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  code to create and destroy physical storage for global temparary table
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_relstats_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+typedef struct gtt_ctl_data
+{
+	LWLock			lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+	Oid			relfilenode;
+	Oid			spcnode;
+
+	/* pg_class stat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+
+	/* pg_statistic */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+
+	Oid			oldrelid;			/* remember the source of relid, before the switch relfilenode. */
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_reset_statistics(gtt_local_hash_entry *entry);
+static void gtt_free_statistics(gtt_local_hash_entry *entry);
+static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok);
+static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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();
+	}
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(gtt_fnode);
+	info.entrysize = action_gtt_shared_hash_entry_size();
+	active_gtt_shared_hash =
+		ShmemInitHash("active gtt shared hash",
+						gtt_shared_ctl->max_entry,
+						gtt_shared_ctl->max_entry,
+						&info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			found;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *)&(fnode), 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_global_temporary_table.")));
+	}
+
+	if (found == false)
+	{
+		int			wordnum;
+
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode				fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	if (!skiplock)
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		if (!skiplock)
+			LWLockRelease(&gtt_shared_ctl->lock);
+
+		if (isCommit)
+			elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+	bms_del_member(entry->map, MyBackendId);
+
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+
+	if (!skiplock)
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+	return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	Bitmapset	*map_copy = NULL;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool		in_use = false;
+	int			num_use = 0;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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;
+	gtt_relfilenode			*new_node = NULL;
+	Oid						relid = RelationGetRelid(rel);
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		/* First time through: initialize the hash table */
+		HASHCTL		ctl;
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_relstats_context =
+			AllocSetContextCreate(CacheMemoryContext,
+							"gtt relstats context",
+							ALLOCSET_DEFAULT_SIZES);
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+	entry = gtt_search_by_relid(relid, true);
+	if (!entry)
+	{
+		bool		found = false;
+		int 		natts = 0;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash,
+						(void *) &relid, HASH_ENTER, &found);
+
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+		entry->natts = 0;
+		entry->attnum = NULL;
+		entry->att_stat_tups = NULL;
+		entry->oldrelid = InvalidOid;
+
+		natts = RelationGetNumberOfAttributes(rel);
+		entry->attnum = palloc0(sizeof(int) * natts);
+		entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+		entry->natts = natts;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			gtt_storage_checkin(relid);
+		}
+	}
+
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* only heap contain transaction information */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	gtt_reset_statistics(entry);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	gtt_relfilenode *d_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+		return;
+	}
+
+	d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+		else if (entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+			gtt_relfilenode *gttnode2 = NULL;
+
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+			Assert(gttnode2->relfilenode == rnode.relNode);
+			Assert(list_length(entry->relfilenode_list) == 1);
+			/* rollback switch relfilenode */
+			gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+									   entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+									   false);
+			/* clean up footprint */
+			entry2->oldrelid = InvalidOid;
+			d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+			Assert(d_rnode);
+		}
+		else
+		{
+			if (entry->relfilenode_list == NIL)
+			{
+				if (entry->relkind == RELKIND_RELATION ||
+					entry->relkind == RELKIND_SEQUENCE)
+					gtt_storage_checkout(relid, false, isCommit);
+
+				gtt_free_statistics(entry);
+				hash_search(gtt_storage_local_hash,
+						(void *) &(relid), HASH_REMOVE, NULL);
+			}
+
+			return;
+		}
+	}
+
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	pfree(d_rnode);
+	if (entry->relfilenode_list == NIL)
+	{
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(relid, false, isCommit);
+
+		if (isCommit && entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			/* clean up footprint */
+			entry2->oldrelid = InvalidOid;
+		}
+
+		gtt_free_statistics(entry);
+		hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_REMOVE, NULL);
+	}
+	else
+		gtt_reset_statistics(entry);
+
+	return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool found = false;
+	gtt_local_hash_entry *entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry)
+		found = true;
+
+	return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS				status;
+	gtt_local_hash_entry		*entry;
+	SMgrRelation	*srels = NULL;
+	Oid				*relids = NULL;
+	char			*relkinds = NULL;
+	int			nrels = 0,
+				nfiles = 0,
+				maxrels = 0,
+				maxfiles = 0,
+				i = 0;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel;
+			RelFileNode 	rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel = smgropen(rnode, MyBackendId);
+
+			if (maxfiles == 0)
+			{
+				maxfiles = 32;
+				srels = palloc(sizeof(SMgrRelation) * maxfiles);
+			}
+			else if (maxfiles <= nfiles)
+			{
+				maxfiles *= 2;
+				srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+			}
+
+			srels[nfiles++] = srel;
+		}
+
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			relids  = palloc(sizeof(Oid) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			relids  = repalloc(relids , sizeof(Oid) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		relkinds[nrels] = entry->relkind;
+		relids[nrels] = entry->relid;
+		nrels++;
+	}
+
+	if (nfiles > 0)
+	{
+		smgrdounlinkall(srels, nfiles, false);
+		for (i = 0; i < nfiles; i++)
+			smgrclose(srels[i]);
+
+		pfree(srels);
+	}
+
+	if (nrels)
+	{
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+		for (i = 0; i < nrels; i++)
+		{
+			if (relkinds[i] == RELKIND_RELATION ||
+				relkinds[i] == RELKIND_SEQUENCE)
+				gtt_storage_checkout(relids[i], true, false);
+		}
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+		pfree(relids);
+		pfree(relkinds);
+	}
+
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+	return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible) 
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+					BlockNumber num_pages,
+					double num_tuples,
+					BlockNumber num_all_visible_pages,
+					TransactionId relfrozenxid,
+					TransactionId relminmxid)
+{
+	Oid		relid = RelationGetRelid(relation);
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (num_pages >= 0 &&
+		gtt_rnode->relpages != (int32)num_pages)
+		gtt_rnode->relpages = (int32)num_pages;
+
+	if (num_tuples >= 0 &&
+		gtt_rnode->reltuples != (float4)num_tuples)
+		gtt_rnode->reltuples = (float4)num_tuples;
+
+	/* only heap contain transaction information and relallvisible */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (gtt_rnode->relallvisible >= 0 &&
+			gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+		{
+			gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+		}
+
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNewTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible) 
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	MemoryContext			oldcontext;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return;
+
+	if (entry->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	Assert(entry->relid == reloid);
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == 0)
+		{
+			entry->attnum[i] = attnum;
+			break;
+		}
+		else if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < entry->natts);
+	Assert(entry->att_stat_tups[i] == NULL);
+	entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return NULL;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			return entry->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int			i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	HeapTuple	tuple;
+	Relation	rel = NULL;
+	int		attnum = PG_GETARG_INT32(1);
+	Oid		reloid = PG_GETARG_OID(0);
+	char		rel_persistence;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	  tupdesc;
+	MemoryContext oldcontext;
+	Tuplestorestate *tupstore;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+
+	relation_close(rel, NoLock);
+	relation_close(pg_tatistic, AccessShareLock);
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate	*tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Oid			relnode = 0;
+	Relation	rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[6];
+		bool	isnull[6];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(reloid);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(RelationGetRelid(index)));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid relOid = index->rd_index->indrelid;
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	if (gtt_storage_attached(relOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int			i;
+	Oid			toastrelid;
+
+	if (operation != CMD_INSERT)
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+
+		index_build(relation, index, info, true, false);
+	}
+
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid 		indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo 	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+static void
+gtt_reset_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+
+		entry->attnum[i] = 0;
+	}
+
+	return;
+}
+
+static void
+gtt_free_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (entry->attnum)
+		pfree(entry->attnum);
+
+	if (entry->att_stat_tups)
+		pfree(entry->att_stat_tups);
+
+	return;
+}
+
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
+void
+gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint)
+{
+	gtt_local_hash_entry	*entry1;
+	gtt_local_hash_entry	*entry2;
+	gtt_relfilenode			*gtt_rnode1 = NULL;
+	gtt_relfilenode			*gtt_rnode2 = NULL;
+	MemoryContext			oldcontext;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry1 = gtt_search_by_relid(rel1, false);
+	gtt_rnode1 = gtt_search_relfilenode(entry1, relfilenode1, false);
+
+	entry2 = gtt_search_by_relid(rel2, false);
+	gtt_rnode2 = gtt_search_relfilenode(entry2, relfilenode2, false);
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	entry1->relfilenode_list = list_delete_ptr(entry1->relfilenode_list, gtt_rnode1);
+	entry2->relfilenode_list = lappend(entry2->relfilenode_list, gtt_rnode1);
+
+	entry2->relfilenode_list = list_delete_ptr(entry2->relfilenode_list, gtt_rnode2);
+	entry1->relfilenode_list = lappend(entry1->relfilenode_list, gtt_rnode2);
+	MemoryContextSwitchTo(oldcontext);
+
+	if (footprint)
+	{
+		entry1->oldrelid = rel2;
+		entry2->oldrelid = rel1;
+	}
+
+	return;
+}
+
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok)
+{
+	gtt_relfilenode			*rnode = NULL;
+	ListCell				*lc;
+
+	Assert(entry);
+
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == relfilenode)
+		{
+			rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (!missing_ok && rnode == NULL)
+		elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+	return rnode;
+}
+
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry == NULL && !missing_ok)
+		elog(ERROR, "relid %u not found in local hash", relid);
+
+	return entry;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index d406ea8..88be041 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM 
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 924ef37..db12eef 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -184,6 +185,13 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -586,14 +594,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 		 * pg_statistic for columns we didn't process, we leave them alone.)
 		 */
 		update_attstats(RelationGetRelid(onerel), inh,
-						attr_cnt, vacattrstats);
+						attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
 
 		for (ind = 0; ind < nindexes; ind++)
 		{
 			AnlIndexData *thisdata = &indexdata[ind];
 
 			update_attstats(RelationGetRelid(Irel[ind]), false,
-							thisdata->attr_cnt, thisdata->vacattrstats);
+							thisdata->attr_cnt, thisdata->vacattrstats,
+							RelationGetRelPersistence(Irel[ind]));
 		}
 
 		/*
@@ -1456,7 +1465,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
  *		by taking a self-exclusive lock on the relation in analyze_rel().
  */
 static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
 {
 	Relation	sd;
 	int			attno;
@@ -1558,31 +1567,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/* Update column statistic to localhash, not catalog */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 04d12a7..f47035e 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/toasting.h"
 #include "commands/cluster.h"
 #include "commands/progress.h"
@@ -72,6 +73,12 @@ static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
 							bool verbose, bool *pSwapToastByContent,
 							TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
 static List *get_tables_to_cluster(MemoryContext cluster_context);
+static void gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables);
 
 
 /*---------------------------------------------------------------------------
@@ -366,6 +373,14 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap) &&
+		!gtt_storage_attached(RelationGetRelid(OldHeap)))
+	{
+		relation_close(OldHeap, AccessExclusiveLock);
+		pgstat_progress_end_command();
+		return;
+	}
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
@@ -750,6 +765,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	BlockNumber num_pages;
 	int			elevel = verbose ? INFO : DEBUG2;
 	PGRUsage	ru0;
+	bool		is_gtt = false;
+	uint32		gtt_relfrozenxid = 0;
+	uint32		gtt_relminmxid = 0;
 
 	pg_rusage_init(&ru0);
 
@@ -763,6 +781,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	else
 		OldIndex = NULL;
 
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		is_gtt = true;
+
 	/*
 	 * Their tuple descriptors should be exactly alike, but here we only need
 	 * assume that they have the same number of columns.
@@ -830,20 +851,37 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 						  &OldestXmin, &FreezeXid, NULL, &MultiXactCutoff,
 						  NULL);
 
-	/*
-	 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
-	 * backwards, so take the max.
-	 */
-	if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
-		TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
-		FreezeXid = OldHeap->rd_rel->relfrozenxid;
+	if (is_gtt)
+	{
+		get_gtt_relstats(OIDOldHeap,
+					NULL, NULL, NULL,
+					&gtt_relfrozenxid, &gtt_relminmxid);
 
-	/*
-	 * MultiXactCutoff, similarly, shouldn't go backwards either.
-	 */
-	if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
-		MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
-		MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+		if (TransactionIdIsValid(gtt_relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, gtt_relfrozenxid))
+			FreezeXid = gtt_relfrozenxid;
+
+		if (MultiXactIdIsValid(gtt_relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, gtt_relminmxid))
+			MultiXactCutoff = gtt_relminmxid;
+	}
+	else
+	{
+		/*
+		 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
+		 * backwards, so take the max.
+		 */
+		if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
+			FreezeXid = OldHeap->rd_rel->relfrozenxid;
+
+		/*
+		 * MultiXactCutoff, similarly, shouldn't go backwards either.
+		 */
+		if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
+			MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+	}
 
 	/*
 	 * Decide whether to use an indexscan or seqscan-and-optional-sort to scan
@@ -911,6 +949,12 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	table_close(OldHeap, NoLock);
 	table_close(NewHeap, NoLock);
 
+	if (is_gtt)
+	{
+		CommandCounterIncrement();
+		return;
+	}
+
 	/* Update pg_class to reflect the correct values of pages and tuples. */
 	relRelation = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1346,10 +1390,20 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 	 * Swap the contents of the heap relations (including any toast tables).
 	 * Also set old heap's relfrozenxid to frozenXid.
 	 */
-	swap_relation_files(OIDOldHeap, OIDNewHeap,
+	if (newrelpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		gtt_swap_relation_files(OIDOldHeap, OIDNewHeap,
+								(OIDOldHeap == RelationRelationId),
+								swap_toast_by_content, is_internal,
+								frozenXid, cutoffMulti, mapped_tables);
+	}
+	else
+	{
+		swap_relation_files(OIDOldHeap, OIDNewHeap,
 						(OIDOldHeap == RelationRelationId),
 						swap_toast_by_content, is_internal,
 						frozenXid, cutoffMulti, mapped_tables);
+	}
 
 	/*
 	 * If it's a system catalog, queue a sinval message to flush all catcaches
@@ -1557,3 +1611,141 @@ get_tables_to_cluster(MemoryContext cluster_context)
 
 	return rvs;
 }
+
+static void
+gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables)
+{
+	Relation	relRelation;
+	Oid			relfilenode1,
+				relfilenode2;
+	Relation	rel1;
+	Relation	rel2;
+
+	relRelation = table_open(RelationRelationId, RowExclusiveLock);
+
+	rel1 = relation_open(r1, AccessExclusiveLock);
+	rel2 = relation_open(r2, AccessExclusiveLock);
+
+	relfilenode1 = gtt_fetch_current_relfilenode(r1);
+	relfilenode2 = gtt_fetch_current_relfilenode(r2);
+
+	Assert(OidIsValid(relfilenode1) && OidIsValid(relfilenode2));
+	gtt_switch_rel_relfilenode(r1, relfilenode1, r2, relfilenode2, true);
+
+	CacheInvalidateRelcache(rel1);
+	CacheInvalidateRelcache(rel2);
+
+	InvokeObjectPostAlterHookArg(RelationRelationId, r1, 0,
+								 InvalidOid, is_internal);
+	InvokeObjectPostAlterHookArg(RelationRelationId, r2, 0,
+								 InvalidOid, true);
+
+	if (rel1->rd_rel->reltoastrelid || rel2->rd_rel->reltoastrelid)
+	{
+		if (swap_toast_by_content)
+		{
+			if (rel1->rd_rel->reltoastrelid && rel2->rd_rel->reltoastrelid)
+			{
+				gtt_swap_relation_files(rel1->rd_rel->reltoastrelid,
+									rel2->rd_rel->reltoastrelid,
+									target_is_pg_class,
+									swap_toast_by_content,
+									is_internal,
+									frozenXid,
+									cutoffMulti,
+									mapped_tables);
+			}
+			else
+				elog(ERROR, "cannot swap toast files by content when there's only one");
+		}
+		else
+		{
+			ObjectAddress baseobject,
+						toastobject;
+			long		count;
+
+			if (IsSystemRelation(rel1))
+				elog(ERROR, "cannot swap toast files by links for system catalogs");
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel1->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel2->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+
+			/* Register new dependencies */
+			baseobject.classId = RelationRelationId;
+			baseobject.objectSubId = 0;
+			toastobject.classId = RelationRelationId;
+			toastobject.objectSubId = 0;
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r1;
+				toastobject.objectId = rel1->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r2;
+				toastobject.objectId = rel2->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+		}
+	}
+
+	if (swap_toast_by_content &&
+		rel1->rd_rel->relkind == RELKIND_TOASTVALUE &&
+		rel2->rd_rel->relkind == RELKIND_TOASTVALUE)
+	{
+		Oid			toastIndex1,
+					toastIndex2;
+
+		/* Get valid index for each relation */
+		toastIndex1 = toast_get_valid_index(r1,
+											AccessExclusiveLock);
+		toastIndex2 = toast_get_valid_index(r2,
+											AccessExclusiveLock);
+
+		gtt_swap_relation_files(toastIndex1,
+							toastIndex2,
+							target_is_pg_class,
+							swap_toast_by_content,
+							is_internal,
+							InvalidTransactionId,
+							InvalidMultiXactId,
+							mapped_tables);
+	}
+
+	relation_close(rel1, NoLock);
+	relation_close(rel2, NoLock);
+
+	table_close(relRelation, RowExclusiveLock);
+
+	RelationCloseSmgrByOid(r1);
+	RelationCloseSmgrByOid(r2);
+
+	CommandCounterIncrement();
+}
+
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index ac07f75..655184d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -27,6 +27,7 @@
 #include "catalog/dependency.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -1063,7 +1064,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, stmt->filename, stmt->is_program,
@@ -2794,6 +2795,8 @@ CopyFrom(CopyState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	estate->es_result_relations = resultRelInfo;
 	estate->es_num_result_relations = 1;
 	estate->es_result_relation_info = resultRelInfo;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 2baca12..5338db9 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -521,6 +521,7 @@ DefineIndex(Oid relationId,
 	Snapshot	snapshot;
 	int			save_nestlevel = -1;
 	int			i;
+	char		rel_persistence;
 
 	/*
 	 * Some callers need us to run with an empty default_tablespace; this is a
@@ -542,7 +543,9 @@ DefineIndex(Oid relationId,
 	 * is more efficient.  Do this before any use of the concurrent option is
 	 * done.
 	 */
-	if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+	rel_persistence = get_rel_persistence(relationId);
+	if (stmt->concurrent &&
+		!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		concurrent = true;
 	else
 		concurrent = false;
@@ -2460,7 +2463,8 @@ ReindexIndex(RangeVar *indexRelation, int options, bool concurrent)
 	persistence = irel->rd_rel->relpersistence;
 	index_close(irel, NoLock);
 
-	if (concurrent && persistence != RELPERSISTENCE_TEMP)
+	if (concurrent &&
+		!(persistence == RELPERSISTENCE_TEMP || persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		ReindexRelationConcurrently(indOid, options);
 	else
 		reindex_index(indOid, false, persistence,
@@ -2546,6 +2550,7 @@ ReindexTable(RangeVar *relation, int options, bool concurrent)
 {
 	Oid			heapOid;
 	bool		result;
+	char		rel_persistence;
 
 	/*
 	 * The lock level used here should match reindex_relation().
@@ -2560,7 +2565,9 @@ ReindexTable(RangeVar *relation, int options, bool concurrent)
 									   0,
 									   RangeVarCallbackOwnsTable, NULL);
 
-	if (concurrent && get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+	rel_persistence = get_rel_persistence(heapOid);
+	if (concurrent &&
+		!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 	{
 		result = ReindexRelationConcurrently(heapOid, options);
 
@@ -2761,12 +2768,15 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 	foreach(l, relids)
 	{
 		Oid			relid = lfirst_oid(l);
+		char		rel_persistence;
 
 		StartTransactionCommand();
 		/* functions in indexes may want a snapshot set */
 		PushActiveSnapshot(GetTransactionSnapshot());
 
-		if (concurrent && get_rel_persistence(relid) != RELPERSISTENCE_TEMP)
+		rel_persistence = get_rel_persistence(relid);
+		if (concurrent &&
+			!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		{
 			(void) ReindexRelationConcurrently(relid, options);
 			/* ReindexRelationConcurrently() does the verbose output */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..a45863a 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -94,7 +96,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -222,7 +225,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* now initialize the sequence's data */
 	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -327,7 +330,7 @@ ResetSequence(Oid seq_relid)
 	/*
 	 * Insert the modified tuple into the new storage file.
 	 */
-	fill_seq_with_data(seq_rel, tuple);
+	fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -340,17 +343,21 @@ ResetSequence(Oid seq_relid)
  * Initialize a sequence's relation with the specified tuple as content
  */
 static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 	sequence_magic *sm;
 	OffsetNumber offnum;
+	bool lockBuffer = false;
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ReadBuffer(rel, P_NEW);
-	Assert(BufferGetBlockNumber(buf) == 0);
+	if (buf == InvalidBuffer)
+	{
+		buf = ReadBuffer(rel, P_NEW);
+		Assert(BufferGetBlockNumber(buf) == 0);
+		lockBuffer = true;
+	}
 
 	page = BufferGetPage(buf);
 
@@ -360,7 +367,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* Now insert sequence tuple */
 
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+	if (lockBuffer)
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +418,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	END_CRIT_SECTION();
 
-	UnlockReleaseBuffer(buf);
+	if (lockBuffer)
+		UnlockReleaseBuffer(buf);
 }
 
 /*
@@ -451,6 +460,13 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 
 	init_sequence(relid, &elm, &seqrel);
 
+	if (RELATION_IS_GLOBAL_TEMP(seqrel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(seqrel)))
+			elog(ERROR, "cannot alter global temp sequence %s when other backend attached it",
+						RelationGetRelationName(seqrel));
+	}
+
 	rel = table_open(SequenceRelationId, RowExclusiveLock);
 	seqtuple = SearchSysCacheCopy1(SEQRELID,
 								   ObjectIdGetDatum(relid));
@@ -502,7 +518,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * Insert the modified tuple into the new storage file.
 		 */
-		fill_seq_with_data(seqrel, newdatatuple);
+		fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
 	}
 
 	/* process OWNED BY if given */
@@ -611,7 +627,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +952,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1154,6 +1170,13 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1954,3 +1977,46 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+void
+gtt_init_seq(Relation rel)
+{
+	/* Initialize sequence for global temporary tables */
+	Datum		value[SEQ_COL_LASTCOL] = {0};
+	bool		null[SEQ_COL_LASTCOL] = {false};
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 6162fb0..e825c04 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -555,6 +556,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 								  Relation partitionTbl);
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static OnCommitAction gtt_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -600,6 +602,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -610,8 +613,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Check consistency of arguments
 	 */
+	/* global temp table same as local temp table */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !(stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+			 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -641,7 +646,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	/* global temp table same as local temp table */
+	if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+		 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -742,6 +749,55 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* global temp table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* check parent table*/
+		if (inheritOids)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			table_close(relation, NoLock);
+		}
+
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temp table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1329,6 +1385,7 @@ RemoveRelations(DropStmt *drop)
 		Oid			relOid;
 		ObjectAddress obj;
 		struct DropRelationCallbackState state;
+		char		rel_persistence;
 
 		/*
 		 * These next few steps are a great deal like relation_openrv, but we
@@ -1362,8 +1419,9 @@ RemoveRelations(DropStmt *drop)
 		 * Decide if concurrent mode needs to be used here or not.  The
 		 * relation persistence cannot be known without its OID.
 		 */
+		rel_persistence = get_rel_persistence(relOid);
 		if (drop->concurrent &&
-			get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+			!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		{
 			Assert(list_length(drop->objects) == 1 &&
 				   drop->removeType == OBJECT_INDEX);
@@ -1818,6 +1876,10 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
 		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 			continue;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			continue;
+
 		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
@@ -3569,6 +3631,14 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			elog(ERROR, "can not alter table %s when other backend attached this global temp table",
+						RelationGetRelationName(rel));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -4846,6 +4916,12 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot rewrite temporary tables of other sessions")));
 
+			if (RELATION_IS_GLOBAL_TEMP(OldHeap) &&
+				tab->chgPersistence)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot change global temporary table persistence setting")));
+
 			/*
 			 * Select destination tablespace (same as original unless user
 			 * requested a change)
@@ -8183,6 +8259,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -12739,6 +12821,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -12941,6 +13026,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temp table");
+
 	/*
 	 * No work if no change in tablespace.
 	 */
@@ -13315,7 +13403,7 @@ index_copy_data(Relation rel, RelFileNode newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
@@ -14722,7 +14810,9 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
 	 */
 	switch (rel->rd_rel->relpersistence)
 	{
+		/* global temp table same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -17315,3 +17405,36 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
 		table_close(rel, NoLock);
 	}
 }
+
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			bool	res = false;
+			char	*sval = defGetString(def);
+
+			if (!parse_bool(sval, &res))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
+
+			if (res)
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 3a89f8f..d3e894a 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/vacuum.h"
@@ -1217,6 +1218,17 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	 /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		is_gtt = true;
+		up_gtt_relstats(relation,
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1230,17 +1242,26 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relpages = (int32) num_pages;
+	else if (pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (is_gtt)
+		relation->rd_rel->reltuples = (float4) num_tuples;
+	else if (pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	else if (pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1285,7 +1306,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1296,7 +1318,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1394,6 +1417,10 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/* global temp table relstats not in pg_class */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1451,6 +1478,27 @@ 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 relfrozenxid %u is the oldest in the entire db",
+						oldest_gtt_frozenxid),
+				 errdetail("The oldest relfrozenxid in pg_class is %u", newFrozenXid),
+				 errhint("If they differ greatly, please consider cleaning up the data in global temp table.")));
+			newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1778,6 +1826,15 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6e65103..04706ee 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -530,6 +530,11 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 4fdffad..0aa7559 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index fb6ce49..06d9237 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
@@ -545,6 +546,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	init_gtt_storage(mtstate->operation, leaf_part_rri);
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d71c0a4..0900907 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,6 +42,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -2372,6 +2373,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			ExecOpenIndices(resultRelInfo,
 							node->onConflictAction != ONCONFLICT_NONE);
 
+		init_gtt_storage(operation, resultRelInfo);
+
 		/*
 		 * If this is an UPDATE and a BEFORE UPDATE trigger is present, the
 		 * trigger itself might modify the partition-key values. So arrange
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 255f56b..d94d6f0 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 b31c524..a70b11b 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6421,7 +6421,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
+	/* global temp table is same as local temp table */
 	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 2554502..04fb5d4 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -28,6 +28,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temp table */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index eee9c33..1891c53 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2594,6 +2594,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 1219ac8..70e5a93 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3302,17 +3302,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; }
@@ -11638,19 +11632,13 @@ OptTempTableName:
 				}
 			| GLOBAL TEMPORARY opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED opt_table qualified_name
 				{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b875a50..123945b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 350959a..38df309 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
@@ -3089,6 +3092,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		cxt.isforeign = false;
 	}
 	cxt.relation = stmt->relation;
+	cxt.relation->relpersistence = RelationGetRelPersistence(rel);
 	cxt.rel = rel;
 	cxt.inhRelations = NIL;
 	cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 7e97ffa..1f2ac58 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2089,6 +2089,11 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* autovacuum skip vacuum global temp table */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2155,7 +2160,9 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		/* autovacuum skip vacuum global temp table */
+		if (classForm->relpersistence == RELPERSISTENCE_TEMP || 
+			classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index f9980cf..406a25c 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -53,6 +54,7 @@
 #include "utils/rel.h"
 #include "utils/resowner_private.h"
 #include "utils/timestamp.h"
+#include "utils/guc.h"
 
 
 /* Note: these two macros only work on shared buffers, not local ones! */
@@ -2819,6 +2821,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d5..7adb969 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -21,6 +21,7 @@
 #include "access/nbtree.h"
 #include "access/subtrans.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, active_gtt_shared_hash_size());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* global temporary table */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 3630006..0c8ef80 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -63,6 +63,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -4086,3 +4087,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 5aa19d3..b3bc455 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 8406644..0d99321 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -1008,6 +1008,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 4fdcb07..58fc8de 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -112,6 +112,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -4731,12 +4732,25 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -4861,15 +4875,27 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6251,6 +6277,7 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	{
 		/* Simple variable --- look to stats for the underlying table */
 		RangeTblEntry *rte = planner_rt_fetch(index->rel->relid, root);
+		char	rel_persistence = get_rel_persistence(rte->relid);
 
 		Assert(rte->rtekind == RTE_RELATION);
 		relid = rte->relid;
@@ -6268,6 +6295,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6279,6 +6313,8 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	}
 	else
 	{
+		char	rel_persistence = get_rel_persistence(index->indexoid);
+
 		/* Expression --- maybe there are stats for the index itself */
 		relid = index->indexoid;
 		colnum = 1;
@@ -6294,6 +6330,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7212,6 +7255,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		/* attempt to lookup stats in relation for this index column */
 		if (attnum != 0)
 		{
+			char	rel_persistence = get_rel_persistence(rte->relid);
+
 			/* Simple variable -- look to stats for the underlying table */
 			if (get_relation_stats_hook &&
 				(*get_relation_stats_hook) (root, rte, attnum, &vardata))
@@ -7224,6 +7269,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7236,6 +7289,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		}
 		else
 		{
+			char	rel_persistence = get_rel_persistence(index->indexoid);
+
 			/*
 			 * Looks like we've found an expression column in the index. Let's
 			 * see if there's any stats for it.
@@ -7255,6 +7310,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index a7d63f1..d8a52cb 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -2939,6 +2940,18 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 9f1f11d..aeb2a78 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -65,6 +65,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1133,6 +1134,24 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1187,6 +1206,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			gtt_fix_index_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -1317,7 +1337,17 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+			if (newrelnode != InvalidOid &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2246,6 +2276,8 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		gtt_fix_index_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3472,6 +3504,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3579,28 +3615,34 @@ void
 RelationSetNewRelfilenode(Relation relation, char persistence)
 {
 	Oid			newrelfilenode;
-	Relation	pg_class;
-	HeapTuple	tuple;
+	Relation	pg_class = NULL;
+	HeapTuple	tuple = NULL;
 	Form_pg_class classform;
 	MultiXactId minmulti = InvalidMultiXactId;
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
+	bool		modify_pg_class = !RELATION_IS_GLOBAL_TEMP(relation);
 
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
 
-	/*
-	 * Get a writable copy of the pg_class tuple for the given relation.
-	 */
-	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+	if (modify_pg_class)
+	{
+		/*
+		 * Get a writable copy of the pg_class tuple for the given relation.
+		 */
+		pg_class = table_open(RelationRelationId, RowExclusiveLock);
 
-	tuple = SearchSysCacheCopy1(RELOID,
-								ObjectIdGetDatum(RelationGetRelid(relation)));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "could not find tuple for relation %u",
-			 RelationGetRelid(relation));
-	classform = (Form_pg_class) GETSTRUCT(tuple);
+		tuple = SearchSysCacheCopy1(RELOID,
+									ObjectIdGetDatum(RelationGetRelid(relation)));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u",
+				 RelationGetRelid(relation));
+		classform = (Form_pg_class) GETSTRUCT(tuple);
+	}
+	else
+		memset(&classform, 0, sizeof(classform));
 
 	/*
 	 * Schedule unlinking of the old storage at transaction commit.
@@ -3626,7 +3668,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;
@@ -3646,6 +3688,15 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 			break;
 	}
 
+	if (!modify_pg_class)
+	{
+		Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+		Assert(RELATION_IS_GLOBAL_TEMP(relation));
+		Assert(!RelationIsMapped(relation));
+		relation->rd_node.relNode = relnode;
+		CacheInvalidateRelcache(relation);
+	}
 	/*
 	 * If we're dealing with a mapped index, pg_class.relfilenode doesn't
 	 * change; instead we have to send the update to the relation mapper.
@@ -3655,7 +3706,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	 * possibly-inaccurate values of relpages etc, but those will be fixed up
 	 * later.
 	 */
-	if (RelationIsMapped(relation))
+	else if (RelationIsMapped(relation))
 	{
 		/* This case is only supported for indexes */
 		Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3701,9 +3752,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 		CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 	}
 
-	heap_freetuple(tuple);
+	if (modify_pg_class)
+	{
+		heap_freetuple(tuple);
 
-	table_close(pg_class, RowExclusiveLock);
+		table_close(pg_class, RowExclusiveLock);
+	}
 
 	/*
 	 * Make the pg_class row change or relation map change visible.  This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 5bdc02f..7747dde 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -142,6 +142,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temp table feature.
+ * global temp table define can still storage in catalog
+ * just can not use.
+ * num > 0 means database can management num active global temp table.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2070,6 +2082,15 @@ static struct config_bool ConfigureNamesBool[] =
 static struct config_int ConfigureNamesInt[] =
 {
 	{
+		{"max_active_global_temporary_table", PGC_POSTMASTER, UNGROUPED,
+			gettext_noop("max active global temporary table."),
+			NULL
+		},
+		&max_active_gtt,
+		DEFAULT_NUM_ACTIVE_GTT, MIN_NUM_ACTIVE_GTT, MAX_NUM_ACTIVE_GTT,
+		NULL, NULL, NULL
+	},
+	{
 		{"archive_timeout", PGC_SIGHUP, WAL_ARCHIVING,
 			gettext_noop("Forces a switch to the next WAL file if a "
 						 "new file has not been started within N seconds."),
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index c579227..e03557b 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2424,6 +2424,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
 		dopt->no_unlogged_table_data)
 		return;
 
+	/* Don't dump data in global temp table/sequence */
+	if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		return;
+
 	/* Check that the data is not explicitly excluded */
 	if (simple_oid_list_member(&tabledata_exclude_oids,
 							   tbinfo->dobj.catId.oid))
@@ -15772,6 +15776,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
 		char	   *foreign = "";
+		char	   *table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15825,9 +15830,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);
 
@@ -16221,7 +16232,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
-			 tbinfo->relkind == RELKIND_MATVIEW))
+			 tbinfo->relkind == RELKIND_MATVIEW) &&
+			 tbinfo->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
 		{
 			appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
 			appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
@@ -17126,6 +17138,7 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	PQExpBuffer query = createPQExpBuffer();
 	PQExpBuffer delqry = createPQExpBuffer();
 	char	   *qseqname;
+	bool	   global_temp_seq = false;
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
@@ -17135,9 +17148,11 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
-						  "FROM pg_catalog.pg_sequence "
-						  "WHERE seqrelid = '%u'::oid",
+						  "seqcache, seqcycle, c.relpersistence "
+						  "FROM pg_catalog.pg_sequence s, "
+						  "pg_catalog.pg_class c "
+						  "WHERE seqrelid = '%u'::oid "
+						  "and s.seqrelid = c.oid",
 						  tbinfo->dobj.catId.oid);
 	}
 	else if (fout->remoteVersion >= 80400)
@@ -17182,6 +17197,9 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
 
+	if (fout->remoteVersion >= 130000)
+		global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
 	if (strcmp(seqtype, "smallint") == 0)
@@ -17256,9 +17274,13 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	}
 	else
 	{
-		appendPQExpBuffer(query,
-						  "CREATE SEQUENCE %s\n",
-						  fmtQualifiedDumpable(tbinfo));
+		appendPQExpBuffer(query, "CREATE ");
+
+		if (global_temp_seq)
+			appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+		appendPQExpBuffer(query, "SEQUENCE %s\n",
+								  fmtQualifiedDumpable(tbinfo));
 
 		if (strcmp(seqtype, "bigint") != 0)
 			appendPQExpBuffer(query, "    AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 00aef85..9ad71c8 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -84,7 +84,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -157,7 +157,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 7e524ea..371168f 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -441,22 +443,46 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 	 * output, so we have to copy that system table.  It's easiest to do that
 	 * by treating it as a user table.
 	 */
-	snprintf(query + strlen(query), sizeof(query) - strlen(query),
-			 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
-			 "  SELECT c.oid, 0::oid, 0::oid "
-			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
-			 "         ON c.relnamespace = n.oid "
-			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
-	/* exclude possible orphaned temp tables */
-			 "    ((n.nspname !~ '^pg_temp_' AND "
-			 "      n.nspname !~ '^pg_toast_temp_' AND "
-			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
-			 "                        'binary_upgrade', 'pg_toast') AND "
-			 "      c.oid >= %u::pg_catalog.oid) OR "
-			 "     (n.nspname = 'pg_catalog' AND "
-			 "      relname IN ('pg_largeobject') ))), ",
-			 FirstNormalObjectId);
+	if (skip_gtt)
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude global temp tables */
+				 "    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
+	else
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
 
 	/*
 	 * Add a CTE that collects OIDs of toast tables belonging to the tables
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 70194eb..8dd9e66 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -411,7 +411,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -642,7 +642,9 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -653,7 +655,9 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 8b90cef..d155205 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -389,7 +389,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index f05e914..19adab9 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3724,7 +3724,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 0e7a373..a47de01 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1023,6 +1023,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2390,6 +2392,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2598,6 +2603,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 78b33b2..4be89f6 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -168,6 +168,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad..c8236a1 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5558,6 +5558,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4388',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4389',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4390',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4391',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 30c38e0..7ff2408 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,7 +22,7 @@
 /* GUC variables */
 extern int	wal_skip_threshold;
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationPreTruncate(Relation rel);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..45fd470
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,45 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Relation relation,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void gtt_force_enable_index(Relation index);
+extern void gtt_fix_index_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+extern void gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index e2638ab..89a5ce4 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 3f88683..f870e9a 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 8fda8e4..172eae9 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_TBM,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_SXACT,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index ae4f573..69cce92 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -117,6 +117,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId session_gtt_frozenxid;	/* session level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 2819282..363fc33 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -282,6 +282,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 0b5957b..0720a4c 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	struct SMgrRelationData *rd_smgr;	/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -305,6 +305,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -570,11 +571,13 @@ typedef struct ViewOptions
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -582,6 +585,7 @@ typedef struct ViewOptions
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -594,6 +598,14 @@ typedef struct ViewOptions
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is either local temp relation of this session
+ * 		or global temp relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -637,6 +649,17 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..ca2d135
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,14 @@
+reset search_path;
+select pg_sleep(5);
+ pg_sleep 
+----------
+ 
+(1 row)
+
+drop schema gtt cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
+drop cascades to table gtt.gtt_with_seq
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..d6a675a
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,383 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+ n 
+---
+ 9
+(1 row)
+
+commit;
+-- 0 row
+select * from gtt6;
+ n 
+---
+(0 rows)
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temp table
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  could not create global temporary table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR:  insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count 
+-------
+     1
+(1 row)
+
+select count(*) from orders;
+ count 
+-------
+     0
+(1 row)
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  2 |  3
+(2 rows)
+
+commit;
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+select * from gtt_s_2 order by c1;
+ c1 | c2 
+----+----
+  1 |  2
+  2 |  4
+(2 rows)
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 1)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 300)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 33 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test_new
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.gtt6
+drop cascades to table gtt_function.foo
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to sequence gtt_function.seq_1
+drop cascades to table gtt_function.gtt_s_1
+drop cascades to table gtt_function.gtt_s_2
+drop cascades to table gtt_function.gt1
+drop cascades to table gtt_function.gtt_test1
+drop cascades to table gtt_function.gtt_test2
+drop cascades to table gtt_function.gtt_test3
+drop cascades to table gtt_function.gtt_test4
+drop cascades to table gtt_function.gtt_test5
+drop cascades to table gtt_function.gtt_test6
+drop cascades to table gtt_function.gtt_test7
+drop cascades to table gtt_function.gtt_test8
+drop cascades to table gtt_function.gtt_test9
+drop cascades to table gtt_function.gtt_test10
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..0646aae
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,90 @@
+set search_path=gtt,sys;
+select nextval('gtt_with_seq_c2_seq');
+ nextval 
+---------
+       1
+(1 row)
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..2f084be
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,343 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+rollback;
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+commit;
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          114688 |                 614400
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |            49152 |         49152 |                  49152
+(2 rows)
+
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2000
+(1 row)
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+ c1 | c2 
+----+----
+  1 |  1
+(1 row)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..8c0c376
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,10 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..4420fdb
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,80 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+ gtt_stats  | gtt_pkey  |        1 |         0 |             0
+(2 rows)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+reindex table gtt;
+reindex index gtt_pkey;
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+ gtt_stats  | gtt_pkey  |       30 |     10000 |             0
+(2 rows)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index ac31840..55b221e 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1349,6 +1349,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 95f1925..76b2374 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..2c8e586
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,8 @@
+
+
+reset search_path;
+
+select pg_sleep(5);
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..81f0bfc
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,253 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+commit;
+-- 0 row
+select * from gtt6;
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+commit;
+select * from gtt_s_1 order by c1;
+select * from gtt_s_2 order by c1;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d05745e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,44 @@
+
+
+set search_path=gtt,sys;
+
+select nextval('gtt_with_seq_c2_seq');
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..39cca5e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,154 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+rollback;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+commit;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+select count(*) from gtt_t_kenyon;
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..dbe84d1
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,19 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..d61b0ff
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,46 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+reindex table gtt;
+
+reindex index gtt_pkey;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#240Erik Rijkers
er@xs4all.nl
In reply to: 曾文旌 (#239)
Re: [Proposal] Global temporary tables

On 2020-04-09 15:28, 曾文旌 wrote:

[global_temporary_table_v25-pg13.patch]

Part of the problem is that some variables are only used by assert
statements, and I fixed those alarms.
Please provide your configue parameter, and I will verify it again.

Hi,

Just now I compiled the newer version of your patch (v25), and the
warnings/notes that I saw earlier, are now gone. Thank you.

In case you still want it here is the configure:

-- [2020.04.09 15:06:45 global_temp_tables/1] ./configure
--prefix=/home/aardvark/pg_stuff/pg_installations/pgsql.global_temp_tables
--bindir=/home/aardvark/pg_stuff/pg_installations/pgsql.global_temp_tables/bin.fast
--libdir=/home/aardvark/pg_stuff/pg_installations/pgsql.global_temp_tables/lib.fast
--with-pgport=6975 --quiet --enable-depend --with-openssl --with-perl
--with-libxml --with-libxslt --with-zlib --enable-tap-tests
--with-extra-version=_0409

-- [2020.04.09 15:07:13 global_temp_tables/1] make core: make --quiet -j
4
partbounds.c: In function ‘partition_bounds_merge’:
partbounds.c:1024:21: warning: unused variable ‘inner_binfo’
[-Wunused-variable]
1024 | PartitionBoundInfo inner_binfo = inner_rel->boundinfo;
| ^~~~~~~~~~~
All of PostgreSQL successfully made. Ready to install.

Thanks,

Erik Rijkers

#241曾文旌
wenjing.zwj@alibaba-inc.com
In reply to: tushar (#233)
1 attachment(s)
Re: [Proposal] Global temporary tables

2020年4月8日 下午6:34,tushar <tushar.ahuja@enterprisedb.com> 写道:

On 4/7/20 2:27 PM, 曾文旌 wrote:

Vacuum full GTT, cluster GTT is already supported in global_temporary_table_v24-pg13.patch.

Please refer this below scenario , where pg_upgrade is failing
1)Server is up and running (./pg_ctl -D data status)
2)Stop the server ( ./pg_ctl -D data stop)
3)Connect to server using single user mode ( ./postgres --single -D data postgres) and create a global temp table
[tushar@localhost bin]$ ./postgres --single -D data1233 postgres

PostgreSQL stand-alone backend 13devel
backend> create global temp table t(n int);

--Press Ctl+D to exit

4)Perform initdb ( ./initdb -D data123)
5.Run pg_upgrade
[tushar@localhost bin]$ ./pg_upgrade -d data -D data123 -b . -B .
--
--
--
Restoring database schemas in the new cluster
postgres
*failure*
Consult the last few lines of "pg_upgrade_dump_13592.log" for
the probable cause of the failure.
Failure, exiting

log file content -

[tushar@localhost bin]$ tail -20 pg_upgrade_dump_13592.log
pg_restore: error: could not execute query: ERROR: pg_type array OID value not set when in binary upgrade mode

I found that the regular table also has this problem, I am very unfamiliar with this part, so I opened another email to consult this problem.

Show quoted text

Command was:
-- For binary upgrade, must preserve pg_type oid
SELECT pg_catalog.binary_upgrade_set_next_pg_type_oid('13594'::pg_catalog.oid);

-- For binary upgrade, must preserve pg_class oids
SELECT pg_catalog.binary_upgrade_set_next_heap_pg_class_oid('13593'::pg_catalog.oid);

CREATE GLOBAL TEMPORARY TABLE "public"."t" (
"n" integer
)
WITH ("on_commit_delete_rows"='false');

-- For binary upgrade, set heap's relfrozenxid and relminmxid
UPDATE pg_catalog.pg_class
SET relfrozenxid = '0', relminmxid = '0'
WHERE oid = '"public"."t"'::pg_catalog.regclass;

--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company

Attachments:

smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#242tushar
tushar.ahuja@enterprisedb.com
In reply to: 曾文旌 (#241)
Re: [Proposal] Global temporary tables

On 4/13/20 1:57 PM, 曾文旌 wrote:

[tushar@localhost bin]$ tail -20 pg_upgrade_dump_13592.log
pg_restore: error: could not execute query: ERROR: pg_type array OID value not set when in binary upgrade mode

I found that the regular table also has this problem, I am very unfamiliar with this part, so I opened another email to consult this problem.

ohh. Thanks.

--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company

#243tushar
tushar.ahuja@enterprisedb.com
In reply to: 曾文旌 (#237)
Re: [Proposal] Global temporary tables

On 4/9/20 6:26 PM, 曾文旌 wrote:

On 4/7/20 2:27 PM, 曾文旌 wrote:

Vacuum full GTT, cluster GTT is already supported in global_temporary_table_v24-pg13.patch.

Here , it is skipping GTT

postgres=# \c foo
You are now connected to database "foo" as user "tushar".
foo=# create global temporary table g123( c1 int) ;
CREATE TABLE
foo=# \q
[tushar@localhost bin]$ ./vacuumdb --full foo
vacuumdb: vacuuming database "foo"
WARNING: skipping vacuum global temp table "g123" because storage is not initialized for current session

The message was inappropriate at some point, so I removed it.

Thanks Wenjing. Please see -if this below behavior is correct

X terminal -

postgres=# create global temp table foo1(n int);
CREATE TABLE
postgres=# insert into foo1 values (generate_series(1,10));
INSERT 0 10
postgres=# vacuum full ;
VACUUM

Y Terminal -

[tushar@localhost bin]$ ./vacuumdb -f  postgres
vacuumdb: vacuuming database "postgres"
WARNING:  global temp table oldest relfrozenxid 3276 is the oldest in
the entire db
DETAIL:  The oldest relfrozenxid in pg_class is 3277
HINT:  If they differ greatly, please consider cleaning up the data in
global temp table.
WARNING:  global temp table oldest relfrozenxid 3276 is the oldest in
the entire db
DETAIL:  The oldest relfrozenxid in pg_class is 3277
HINT:  If they differ greatly, please consider cleaning up the data in
global temp table.

--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company

#244曾文旌
wenjing.zwj@alibaba-inc.com
In reply to: tushar (#243)
2 attachment(s)
Re: [Proposal] Global temporary tables

2020年4月13日 下午6:32,tushar <tushar.ahuja@enterprisedb.com> 写道:

On 4/9/20 6:26 PM, 曾文旌 wrote:

On 4/7/20 2:27 PM, 曾文旌 wrote:

Vacuum full GTT, cluster GTT is already supported in global_temporary_table_v24-pg13.patch.

Here , it is skipping GTT

postgres=# \c foo
You are now connected to database "foo" as user "tushar".
foo=# create global temporary table g123( c1 int) ;
CREATE TABLE
foo=# \q
[tushar@localhost bin]$ ./vacuumdb --full foo
vacuumdb: vacuuming database "foo"
WARNING: skipping vacuum global temp table "g123" because storage is not initialized for current session

The message was inappropriate at some point, so I removed it.

Thanks Wenjing. Please see -if this below behavior is correct

X terminal -

postgres=# create global temp table foo1(n int);
CREATE TABLE
postgres=# insert into foo1 values (generate_series(1,10));
INSERT 0 10
postgres=# vacuum full ;
VACUUM

Y Terminal -

[tushar@localhost bin]$ ./vacuumdb -f postgres
vacuumdb: vacuuming database "postgres"
WARNING: global temp table oldest relfrozenxid 3276 is the oldest in the entire db
DETAIL: The oldest relfrozenxid in pg_class is 3277
HINT: If they differ greatly, please consider cleaning up the data in global temp table.
WARNING: global temp table oldest relfrozenxid 3276 is the oldest in the entire db
DETAIL: The oldest relfrozenxid in pg_class is 3277
HINT: If they differ greatly, please consider cleaning up the data in global temp table.

I improved the logic of the warning message so that when the gap between relfrozenxid of GTT is small,
it will no longer be alarmed message.

Wenjing

Show quoted text

--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/ <https://www.enterprisedb.com/&gt;
The Enterprise PostgreSQL Company

Attachments:

global_temporary_table_v26-pg13.patchapplication/octet-stream; name=global_temporary_table_v26-pg13.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 8ccc228..b061de9 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -168,6 +168,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * For global temp table only
+	 * use ShareUpdateExclusiveLock for ensure safety
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temp table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},	
 	/* list terminator */
 	{{NULL}}
 };
@@ -1817,6 +1830,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1961,13 +1976,18 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	/*
 	 * There are no options for partitioned tables yet, but this is able to do
 	 * some validation.
 	 */
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index 765329b..4761fdc 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1011,7 +1011,9 @@ gistproperty(Oid index_oid, int attno,
 XLogRecPtr
 gistGetFakeLSN(Relation rel)
 {
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	/* global temp is same as local temp table */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 3ec6d52..1222594 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -150,7 +150,9 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	/* global temp table is same as local temp table */
+	if (!(index->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		index->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 56b3562..d3443ec 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -587,7 +587,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -640,7 +640,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 3c18db2..b08a1fc 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -62,6 +62,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -431,9 +432,9 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
 	Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
-	/* not every AM requires these to be valid, but heap does */
-	Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
-	Assert(MultiXactIdIsValid(onerel->rd_rel->relminmxid));
+	/* not every AM requires these to be valid, but regular heap does */
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ MultiXactIdIsValid(onerel->rd_rel->relminmxid));
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 39b8f17..abb76dc 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -601,6 +602,10 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 11e3273..32694c9 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6422,6 +6422,10 @@ StartupXLOG(void)
 	else
 		recoveryTargetTLI = ControlFile->checkPointCopy.ThisTimeLineID;
 
+	/* clean temp relation files */
+	if (max_active_gtt > 0)
+		RemovePgTempFiles();
+
 	/*
 	 * Check for signal files, and if so set up state for offline recovery
 	 */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 9499bb3..ae47364 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -43,6 +43,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 7d6acae..3be8d63 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -393,7 +393,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/* global temp table is same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 632c058..01cf990 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -427,7 +429,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -959,6 +961,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -997,8 +1000,18 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/* global temp table not remember transaction info in catalog */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1360,6 +1373,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1940,6 +1954,14 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/* We allow to drop global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			elog(ERROR, "can not drop relation %s when other backend attached this global temp table",
+						RelationGetRelationName(rel));
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3167,7 +3189,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3179,7 +3201,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3225,8 +3247,13 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		/* truncate global temp table only need RowExclusiveLock */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3259,6 +3286,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3267,23 +3295,37 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (!gtt_storage_attached(RelationGetRelid(rel)))
+			return;
+
+		/*
+		 * Truncate global temp table only need RowExclusiveLock
+		 */
+		lockmode = RowExclusiveLock;
+	}
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(rel, 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index bd7ec92..747cda0 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -53,6 +53,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -881,6 +882,19 @@ index_create(Relation heapRelation,
 						indexRelationName, RelationGetRelationName(heapRelation))));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temp table use concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot reindex global temporary tables concurrently")));
+
+		/* if global temp table not init storage, then skip build index */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -2041,7 +2055,8 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 * lock (see comments in RemoveRelations), and a non-concurrent DROP is
 	 * more efficient.
 	 */
-	Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+	Assert(!(get_rel_persistence(indexId) == RELPERSISTENCE_TEMP ||
+			 get_rel_persistence(indexId) == RELPERSISTENCE_GLOBAL_TEMP) ||
 		   (!concurrent && !concurrent_lock_mode));
 
 	/*
@@ -2073,6 +2088,14 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/* We allow to drop index on global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+			elog(ERROR, "can not drop index %s when other backend attached this global temp table.",
+						RelationGetRelationName(userHeapRelation));
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2681,6 +2704,11 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	/* update index stats into localhash and rel_rd_rel for global temp table */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		is_gtt = true;
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2766,21 +2794,35 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
+		if (is_gtt)
+			rel->rd_rel->relpages = (int32) relpages;
+		else if (rd_rel->relpages != (int32) relpages)
 		{
 			rd_rel->relpages = (int32) relpages;
 			dirty = true;
 		}
-		if (rd_rel->reltuples != (float4) reltuples)
+
+		if (is_gtt)
+			rel->rd_rel->reltuples = (float4) reltuples;
+		else if (rd_rel->reltuples != (float4) reltuples)
 		{
 			rd_rel->reltuples = (float4) reltuples;
 			dirty = true;
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+
+		if (is_gtt)
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+		else if (rd_rel->relallvisible != (int32) relallvisible)
 		{
 			rd_rel->relallvisible = (int32) relallvisible;
 			dirty = true;
 		}
+
+		if (is_gtt)
+		{
+			up_gtt_relstats(rel, relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
+		}
 	}
 
 	/*
@@ -2894,6 +2936,15 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, index, val);
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			gtt_force_enable_index(indexRelation);
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3442,6 +3493,10 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	PGRUsage	ru0;
 	bool		progress = (options & REINDEXOPT_REPORT_PROGRESS) != 0;
 
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		!gtt_storage_attached(indexId))
+		return;
+
 	pg_rusage_init(&ru0);
 
 	/*
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 2ec2301..1b6061d 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -655,6 +655,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		/* global temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp relations in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index d713d5c..afcf004 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,6 +27,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -61,6 +62,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			relOid;			/* InvalidOid if not a global temp rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -115,7 +117,7 @@ AddPendingSync(const RelFileNode *rnode)
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -127,6 +129,8 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		/* global temp table use same storage strategy as local temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -154,6 +158,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->relOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
@@ -165,6 +170,13 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		AddPendingSync(&rnode);
 	}
 
+	/* remember global temp table storage info to localhash */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel)
+	{
+		pending->relOid = RelationGetRelid(rel);
+		remember_gtt_storage_info(rnode, rel);
+	}
+
 	return srel;
 }
 
@@ -201,11 +213,15 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->relOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->relOid = RelationGetRelid(rel);
+
 	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
@@ -602,6 +618,7 @@ smgrDoPendingDeletes(bool isCommit)
 				i = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -631,14 +648,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->relOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -651,9 +672,18 @@ smgrDoPendingDeletes(bool isCommit)
 		smgrdounlinkall(srels, nrels, false);
 
 		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* clean global temp table flags when transaction commit or rollback */
+			if (SmgrIsTemp(srels[i]) &&
+				reloids[i] != InvalidOid &&
+				gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..5e85d1e
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1491 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  code to create and destroy physical storage for global temparary table
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_relstats_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+int		vacuum_gtt_defer_check_age = 0;
+
+typedef struct gtt_ctl_data
+{
+	LWLock			lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+	Oid			relfilenode;
+	Oid			spcnode;
+
+	/* pg_class stat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+
+	/* pg_statistic */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+
+	Oid			oldrelid;			/* remember the source of relid, before the switch relfilenode. */
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_reset_statistics(gtt_local_hash_entry *entry);
+static void gtt_free_statistics(gtt_local_hash_entry *entry);
+static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok);
+static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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();
+	}
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(gtt_fnode);
+	info.entrysize = action_gtt_shared_hash_entry_size();
+	active_gtt_shared_hash =
+		ShmemInitHash("active gtt shared hash",
+						gtt_shared_ctl->max_entry,
+						gtt_shared_ctl->max_entry,
+						&info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			found;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *)&(fnode), 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_global_temporary_table.")));
+	}
+
+	if (found == false)
+	{
+		int			wordnum;
+
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode				fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	if (!skiplock)
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		if (!skiplock)
+			LWLockRelease(&gtt_shared_ctl->lock);
+
+		if (isCommit)
+			elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+	bms_del_member(entry->map, MyBackendId);
+
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+
+	if (!skiplock)
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+	return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	Bitmapset	*map_copy = NULL;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool		in_use = false;
+	int			num_use = 0;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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;
+	gtt_relfilenode			*new_node = NULL;
+	Oid						relid = RelationGetRelid(rel);
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		/* First time through: initialize the hash table */
+		HASHCTL		ctl;
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_relstats_context =
+			AllocSetContextCreate(CacheMemoryContext,
+							"gtt relstats context",
+							ALLOCSET_DEFAULT_SIZES);
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+	entry = gtt_search_by_relid(relid, true);
+	if (!entry)
+	{
+		bool		found = false;
+		int 		natts = 0;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash,
+						(void *) &relid, HASH_ENTER, &found);
+
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+		entry->natts = 0;
+		entry->attnum = NULL;
+		entry->att_stat_tups = NULL;
+		entry->oldrelid = InvalidOid;
+
+		natts = RelationGetNumberOfAttributes(rel);
+		entry->attnum = palloc0(sizeof(int) * natts);
+		entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+		entry->natts = natts;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			gtt_storage_checkin(relid);
+		}
+	}
+
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* only heap contain transaction information */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	gtt_reset_statistics(entry);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	gtt_relfilenode *d_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+		return;
+	}
+
+	d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+		else if (entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+			gtt_relfilenode *gttnode2 = NULL;
+
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+			Assert(gttnode2->relfilenode == rnode.relNode);
+			Assert(list_length(entry->relfilenode_list) == 1);
+			/* rollback switch relfilenode */
+			gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+									   entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+									   false);
+			/* clean up footprint */
+			entry2->oldrelid = InvalidOid;
+			d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+			Assert(d_rnode);
+		}
+		else
+		{
+			if (entry->relfilenode_list == NIL)
+			{
+				if (entry->relkind == RELKIND_RELATION ||
+					entry->relkind == RELKIND_SEQUENCE)
+					gtt_storage_checkout(relid, false, isCommit);
+
+				gtt_free_statistics(entry);
+				hash_search(gtt_storage_local_hash,
+						(void *) &(relid), HASH_REMOVE, NULL);
+			}
+
+			return;
+		}
+	}
+
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	pfree(d_rnode);
+	if (entry->relfilenode_list == NIL)
+	{
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(relid, false, isCommit);
+
+		if (isCommit && entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			/* clean up footprint */
+			entry2->oldrelid = InvalidOid;
+		}
+
+		gtt_free_statistics(entry);
+		hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_REMOVE, NULL);
+	}
+	else
+		gtt_reset_statistics(entry);
+
+	return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool found = false;
+	gtt_local_hash_entry *entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry)
+		found = true;
+
+	return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS				status;
+	gtt_local_hash_entry		*entry;
+	SMgrRelation	*srels = NULL;
+	Oid				*relids = NULL;
+	char			*relkinds = NULL;
+	int			nrels = 0,
+				nfiles = 0,
+				maxrels = 0,
+				maxfiles = 0,
+				i = 0;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel;
+			RelFileNode 	rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel = smgropen(rnode, MyBackendId);
+
+			if (maxfiles == 0)
+			{
+				maxfiles = 32;
+				srels = palloc(sizeof(SMgrRelation) * maxfiles);
+			}
+			else if (maxfiles <= nfiles)
+			{
+				maxfiles *= 2;
+				srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+			}
+
+			srels[nfiles++] = srel;
+		}
+
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			relids  = palloc(sizeof(Oid) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			relids  = repalloc(relids , sizeof(Oid) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		relkinds[nrels] = entry->relkind;
+		relids[nrels] = entry->relid;
+		nrels++;
+	}
+
+	if (nfiles > 0)
+	{
+		smgrdounlinkall(srels, nfiles, false);
+		for (i = 0; i < nfiles; i++)
+			smgrclose(srels[i]);
+
+		pfree(srels);
+	}
+
+	if (nrels)
+	{
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+		for (i = 0; i < nrels; i++)
+		{
+			if (relkinds[i] == RELKIND_RELATION ||
+				relkinds[i] == RELKIND_SEQUENCE)
+				gtt_storage_checkout(relids[i], true, false);
+		}
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+		pfree(relids);
+		pfree(relkinds);
+	}
+
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+	return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible) 
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+					BlockNumber num_pages,
+					double num_tuples,
+					BlockNumber num_all_visible_pages,
+					TransactionId relfrozenxid,
+					TransactionId relminmxid)
+{
+	Oid		relid = RelationGetRelid(relation);
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (num_pages >= 0 &&
+		gtt_rnode->relpages != (int32)num_pages)
+		gtt_rnode->relpages = (int32)num_pages;
+
+	if (num_tuples >= 0 &&
+		gtt_rnode->reltuples != (float4)num_tuples)
+		gtt_rnode->reltuples = (float4)num_tuples;
+
+	/* only heap contain transaction information and relallvisible */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (gtt_rnode->relallvisible >= 0 &&
+			gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+		{
+			gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+		}
+
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNewTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible) 
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	MemoryContext			oldcontext;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return;
+
+	if (entry->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	Assert(entry->relid == reloid);
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == 0)
+		{
+			entry->attnum[i] = attnum;
+			break;
+		}
+		else if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < entry->natts);
+	Assert(entry->att_stat_tups[i] == NULL);
+	entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return NULL;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			return entry->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int			i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	HeapTuple	tuple;
+	Relation	rel = NULL;
+	int		attnum = PG_GETARG_INT32(1);
+	Oid		reloid = PG_GETARG_OID(0);
+	char		rel_persistence;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	  tupdesc;
+	MemoryContext oldcontext;
+	Tuplestorestate *tupstore;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+
+	relation_close(rel, NoLock);
+	relation_close(pg_tatistic, AccessShareLock);
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate	*tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Oid			relnode = 0;
+	Relation	rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[6];
+		bool	isnull[6];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(reloid);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(RelationGetRelid(index)));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid relOid = index->rd_index->indrelid;
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	if (gtt_storage_attached(relOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int			i;
+	Oid			toastrelid;
+
+	if (operation != CMD_INSERT)
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+
+		index_build(relation, index, info, true, false);
+	}
+
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid 		indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo 	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+static void
+gtt_reset_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+
+		entry->attnum[i] = 0;
+	}
+
+	return;
+}
+
+static void
+gtt_free_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (entry->attnum)
+		pfree(entry->attnum);
+
+	if (entry->att_stat_tups)
+		pfree(entry->att_stat_tups);
+
+	return;
+}
+
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
+void
+gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint)
+{
+	gtt_local_hash_entry	*entry1;
+	gtt_local_hash_entry	*entry2;
+	gtt_relfilenode			*gtt_rnode1 = NULL;
+	gtt_relfilenode			*gtt_rnode2 = NULL;
+	MemoryContext			oldcontext;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry1 = gtt_search_by_relid(rel1, false);
+	gtt_rnode1 = gtt_search_relfilenode(entry1, relfilenode1, false);
+
+	entry2 = gtt_search_by_relid(rel2, false);
+	gtt_rnode2 = gtt_search_relfilenode(entry2, relfilenode2, false);
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	entry1->relfilenode_list = list_delete_ptr(entry1->relfilenode_list, gtt_rnode1);
+	entry2->relfilenode_list = lappend(entry2->relfilenode_list, gtt_rnode1);
+
+	entry2->relfilenode_list = list_delete_ptr(entry2->relfilenode_list, gtt_rnode2);
+	entry1->relfilenode_list = lappend(entry1->relfilenode_list, gtt_rnode2);
+	MemoryContextSwitchTo(oldcontext);
+
+	if (footprint)
+	{
+		entry1->oldrelid = rel2;
+		entry2->oldrelid = rel1;
+	}
+
+	return;
+}
+
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok)
+{
+	gtt_relfilenode			*rnode = NULL;
+	ListCell				*lc;
+
+	Assert(entry);
+
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == relfilenode)
+		{
+			rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (!missing_ok && rnode == NULL)
+		elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+	return rnode;
+}
+
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry == NULL && !missing_ok)
+		elog(ERROR, "relid %u not found in local hash", relid);
+
+	return entry;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index d406ea8..88be041 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM 
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 924ef37..db12eef 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -184,6 +185,13 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -586,14 +594,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 		 * pg_statistic for columns we didn't process, we leave them alone.)
 		 */
 		update_attstats(RelationGetRelid(onerel), inh,
-						attr_cnt, vacattrstats);
+						attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
 
 		for (ind = 0; ind < nindexes; ind++)
 		{
 			AnlIndexData *thisdata = &indexdata[ind];
 
 			update_attstats(RelationGetRelid(Irel[ind]), false,
-							thisdata->attr_cnt, thisdata->vacattrstats);
+							thisdata->attr_cnt, thisdata->vacattrstats,
+							RelationGetRelPersistence(Irel[ind]));
 		}
 
 		/*
@@ -1456,7 +1465,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
  *		by taking a self-exclusive lock on the relation in analyze_rel().
  */
 static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
 {
 	Relation	sd;
 	int			attno;
@@ -1558,31 +1567,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/* Update column statistic to localhash, not catalog */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 04d12a7..f47035e 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/toasting.h"
 #include "commands/cluster.h"
 #include "commands/progress.h"
@@ -72,6 +73,12 @@ static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
 							bool verbose, bool *pSwapToastByContent,
 							TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
 static List *get_tables_to_cluster(MemoryContext cluster_context);
+static void gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables);
 
 
 /*---------------------------------------------------------------------------
@@ -366,6 +373,14 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap) &&
+		!gtt_storage_attached(RelationGetRelid(OldHeap)))
+	{
+		relation_close(OldHeap, AccessExclusiveLock);
+		pgstat_progress_end_command();
+		return;
+	}
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
@@ -750,6 +765,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	BlockNumber num_pages;
 	int			elevel = verbose ? INFO : DEBUG2;
 	PGRUsage	ru0;
+	bool		is_gtt = false;
+	uint32		gtt_relfrozenxid = 0;
+	uint32		gtt_relminmxid = 0;
 
 	pg_rusage_init(&ru0);
 
@@ -763,6 +781,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	else
 		OldIndex = NULL;
 
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		is_gtt = true;
+
 	/*
 	 * Their tuple descriptors should be exactly alike, but here we only need
 	 * assume that they have the same number of columns.
@@ -830,20 +851,37 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 						  &OldestXmin, &FreezeXid, NULL, &MultiXactCutoff,
 						  NULL);
 
-	/*
-	 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
-	 * backwards, so take the max.
-	 */
-	if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
-		TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
-		FreezeXid = OldHeap->rd_rel->relfrozenxid;
+	if (is_gtt)
+	{
+		get_gtt_relstats(OIDOldHeap,
+					NULL, NULL, NULL,
+					&gtt_relfrozenxid, &gtt_relminmxid);
 
-	/*
-	 * MultiXactCutoff, similarly, shouldn't go backwards either.
-	 */
-	if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
-		MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
-		MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+		if (TransactionIdIsValid(gtt_relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, gtt_relfrozenxid))
+			FreezeXid = gtt_relfrozenxid;
+
+		if (MultiXactIdIsValid(gtt_relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, gtt_relminmxid))
+			MultiXactCutoff = gtt_relminmxid;
+	}
+	else
+	{
+		/*
+		 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
+		 * backwards, so take the max.
+		 */
+		if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
+			FreezeXid = OldHeap->rd_rel->relfrozenxid;
+
+		/*
+		 * MultiXactCutoff, similarly, shouldn't go backwards either.
+		 */
+		if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
+			MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+	}
 
 	/*
 	 * Decide whether to use an indexscan or seqscan-and-optional-sort to scan
@@ -911,6 +949,12 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	table_close(OldHeap, NoLock);
 	table_close(NewHeap, NoLock);
 
+	if (is_gtt)
+	{
+		CommandCounterIncrement();
+		return;
+	}
+
 	/* Update pg_class to reflect the correct values of pages and tuples. */
 	relRelation = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1346,10 +1390,20 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 	 * Swap the contents of the heap relations (including any toast tables).
 	 * Also set old heap's relfrozenxid to frozenXid.
 	 */
-	swap_relation_files(OIDOldHeap, OIDNewHeap,
+	if (newrelpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		gtt_swap_relation_files(OIDOldHeap, OIDNewHeap,
+								(OIDOldHeap == RelationRelationId),
+								swap_toast_by_content, is_internal,
+								frozenXid, cutoffMulti, mapped_tables);
+	}
+	else
+	{
+		swap_relation_files(OIDOldHeap, OIDNewHeap,
 						(OIDOldHeap == RelationRelationId),
 						swap_toast_by_content, is_internal,
 						frozenXid, cutoffMulti, mapped_tables);
+	}
 
 	/*
 	 * If it's a system catalog, queue a sinval message to flush all catcaches
@@ -1557,3 +1611,141 @@ get_tables_to_cluster(MemoryContext cluster_context)
 
 	return rvs;
 }
+
+static void
+gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables)
+{
+	Relation	relRelation;
+	Oid			relfilenode1,
+				relfilenode2;
+	Relation	rel1;
+	Relation	rel2;
+
+	relRelation = table_open(RelationRelationId, RowExclusiveLock);
+
+	rel1 = relation_open(r1, AccessExclusiveLock);
+	rel2 = relation_open(r2, AccessExclusiveLock);
+
+	relfilenode1 = gtt_fetch_current_relfilenode(r1);
+	relfilenode2 = gtt_fetch_current_relfilenode(r2);
+
+	Assert(OidIsValid(relfilenode1) && OidIsValid(relfilenode2));
+	gtt_switch_rel_relfilenode(r1, relfilenode1, r2, relfilenode2, true);
+
+	CacheInvalidateRelcache(rel1);
+	CacheInvalidateRelcache(rel2);
+
+	InvokeObjectPostAlterHookArg(RelationRelationId, r1, 0,
+								 InvalidOid, is_internal);
+	InvokeObjectPostAlterHookArg(RelationRelationId, r2, 0,
+								 InvalidOid, true);
+
+	if (rel1->rd_rel->reltoastrelid || rel2->rd_rel->reltoastrelid)
+	{
+		if (swap_toast_by_content)
+		{
+			if (rel1->rd_rel->reltoastrelid && rel2->rd_rel->reltoastrelid)
+			{
+				gtt_swap_relation_files(rel1->rd_rel->reltoastrelid,
+									rel2->rd_rel->reltoastrelid,
+									target_is_pg_class,
+									swap_toast_by_content,
+									is_internal,
+									frozenXid,
+									cutoffMulti,
+									mapped_tables);
+			}
+			else
+				elog(ERROR, "cannot swap toast files by content when there's only one");
+		}
+		else
+		{
+			ObjectAddress baseobject,
+						toastobject;
+			long		count;
+
+			if (IsSystemRelation(rel1))
+				elog(ERROR, "cannot swap toast files by links for system catalogs");
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel1->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel2->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+
+			/* Register new dependencies */
+			baseobject.classId = RelationRelationId;
+			baseobject.objectSubId = 0;
+			toastobject.classId = RelationRelationId;
+			toastobject.objectSubId = 0;
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r1;
+				toastobject.objectId = rel1->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r2;
+				toastobject.objectId = rel2->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+		}
+	}
+
+	if (swap_toast_by_content &&
+		rel1->rd_rel->relkind == RELKIND_TOASTVALUE &&
+		rel2->rd_rel->relkind == RELKIND_TOASTVALUE)
+	{
+		Oid			toastIndex1,
+					toastIndex2;
+
+		/* Get valid index for each relation */
+		toastIndex1 = toast_get_valid_index(r1,
+											AccessExclusiveLock);
+		toastIndex2 = toast_get_valid_index(r2,
+											AccessExclusiveLock);
+
+		gtt_swap_relation_files(toastIndex1,
+							toastIndex2,
+							target_is_pg_class,
+							swap_toast_by_content,
+							is_internal,
+							InvalidTransactionId,
+							InvalidMultiXactId,
+							mapped_tables);
+	}
+
+	relation_close(rel1, NoLock);
+	relation_close(rel2, NoLock);
+
+	table_close(relRelation, RowExclusiveLock);
+
+	RelationCloseSmgrByOid(r1);
+	RelationCloseSmgrByOid(r2);
+
+	CommandCounterIncrement();
+}
+
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index ac07f75..655184d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -27,6 +27,7 @@
 #include "catalog/dependency.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -1063,7 +1064,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, stmt->filename, stmt->is_program,
@@ -2794,6 +2795,8 @@ CopyFrom(CopyState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	estate->es_result_relations = resultRelInfo;
 	estate->es_num_result_relations = 1;
 	estate->es_result_relation_info = resultRelInfo;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 2baca12..5338db9 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -521,6 +521,7 @@ DefineIndex(Oid relationId,
 	Snapshot	snapshot;
 	int			save_nestlevel = -1;
 	int			i;
+	char		rel_persistence;
 
 	/*
 	 * Some callers need us to run with an empty default_tablespace; this is a
@@ -542,7 +543,9 @@ DefineIndex(Oid relationId,
 	 * is more efficient.  Do this before any use of the concurrent option is
 	 * done.
 	 */
-	if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+	rel_persistence = get_rel_persistence(relationId);
+	if (stmt->concurrent &&
+		!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		concurrent = true;
 	else
 		concurrent = false;
@@ -2460,7 +2463,8 @@ ReindexIndex(RangeVar *indexRelation, int options, bool concurrent)
 	persistence = irel->rd_rel->relpersistence;
 	index_close(irel, NoLock);
 
-	if (concurrent && persistence != RELPERSISTENCE_TEMP)
+	if (concurrent &&
+		!(persistence == RELPERSISTENCE_TEMP || persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		ReindexRelationConcurrently(indOid, options);
 	else
 		reindex_index(indOid, false, persistence,
@@ -2546,6 +2550,7 @@ ReindexTable(RangeVar *relation, int options, bool concurrent)
 {
 	Oid			heapOid;
 	bool		result;
+	char		rel_persistence;
 
 	/*
 	 * The lock level used here should match reindex_relation().
@@ -2560,7 +2565,9 @@ ReindexTable(RangeVar *relation, int options, bool concurrent)
 									   0,
 									   RangeVarCallbackOwnsTable, NULL);
 
-	if (concurrent && get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+	rel_persistence = get_rel_persistence(heapOid);
+	if (concurrent &&
+		!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 	{
 		result = ReindexRelationConcurrently(heapOid, options);
 
@@ -2761,12 +2768,15 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 	foreach(l, relids)
 	{
 		Oid			relid = lfirst_oid(l);
+		char		rel_persistence;
 
 		StartTransactionCommand();
 		/* functions in indexes may want a snapshot set */
 		PushActiveSnapshot(GetTransactionSnapshot());
 
-		if (concurrent && get_rel_persistence(relid) != RELPERSISTENCE_TEMP)
+		rel_persistence = get_rel_persistence(relid);
+		if (concurrent &&
+			!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		{
 			(void) ReindexRelationConcurrently(relid, options);
 			/* ReindexRelationConcurrently() does the verbose output */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..a45863a 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -94,7 +96,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -222,7 +225,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* now initialize the sequence's data */
 	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -327,7 +330,7 @@ ResetSequence(Oid seq_relid)
 	/*
 	 * Insert the modified tuple into the new storage file.
 	 */
-	fill_seq_with_data(seq_rel, tuple);
+	fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -340,17 +343,21 @@ ResetSequence(Oid seq_relid)
  * Initialize a sequence's relation with the specified tuple as content
  */
 static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 	sequence_magic *sm;
 	OffsetNumber offnum;
+	bool lockBuffer = false;
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ReadBuffer(rel, P_NEW);
-	Assert(BufferGetBlockNumber(buf) == 0);
+	if (buf == InvalidBuffer)
+	{
+		buf = ReadBuffer(rel, P_NEW);
+		Assert(BufferGetBlockNumber(buf) == 0);
+		lockBuffer = true;
+	}
 
 	page = BufferGetPage(buf);
 
@@ -360,7 +367,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* Now insert sequence tuple */
 
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+	if (lockBuffer)
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +418,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	END_CRIT_SECTION();
 
-	UnlockReleaseBuffer(buf);
+	if (lockBuffer)
+		UnlockReleaseBuffer(buf);
 }
 
 /*
@@ -451,6 +460,13 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 
 	init_sequence(relid, &elm, &seqrel);
 
+	if (RELATION_IS_GLOBAL_TEMP(seqrel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(seqrel)))
+			elog(ERROR, "cannot alter global temp sequence %s when other backend attached it",
+						RelationGetRelationName(seqrel));
+	}
+
 	rel = table_open(SequenceRelationId, RowExclusiveLock);
 	seqtuple = SearchSysCacheCopy1(SEQRELID,
 								   ObjectIdGetDatum(relid));
@@ -502,7 +518,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * Insert the modified tuple into the new storage file.
 		 */
-		fill_seq_with_data(seqrel, newdatatuple);
+		fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
 	}
 
 	/* process OWNED BY if given */
@@ -611,7 +627,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +952,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1154,6 +1170,13 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1954,3 +1977,46 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+void
+gtt_init_seq(Relation rel)
+{
+	/* Initialize sequence for global temporary tables */
+	Datum		value[SEQ_COL_LASTCOL] = {0};
+	bool		null[SEQ_COL_LASTCOL] = {false};
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 037d457..eab9c70 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -555,6 +556,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 								  Relation partitionTbl);
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static OnCommitAction gtt_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -600,6 +602,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -610,8 +613,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Check consistency of arguments
 	 */
+	/* global temp table same as local temp table */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !(stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+			 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -641,7 +646,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	/* global temp table same as local temp table */
+	if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+		 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -742,6 +749,55 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* global temp table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* check parent table*/
+		if (inheritOids)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			table_close(relation, NoLock);
+		}
+
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temp table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1329,6 +1385,7 @@ RemoveRelations(DropStmt *drop)
 		Oid			relOid;
 		ObjectAddress obj;
 		struct DropRelationCallbackState state;
+		char		rel_persistence;
 
 		/*
 		 * These next few steps are a great deal like relation_openrv, but we
@@ -1362,8 +1419,9 @@ RemoveRelations(DropStmt *drop)
 		 * Decide if concurrent mode needs to be used here or not.  The
 		 * relation persistence cannot be known without its OID.
 		 */
+		rel_persistence = get_rel_persistence(relOid);
 		if (drop->concurrent &&
-			get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+			!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		{
 			Assert(list_length(drop->objects) == 1 &&
 				   drop->removeType == OBJECT_INDEX);
@@ -1818,6 +1876,10 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
 		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 			continue;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			continue;
+
 		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
@@ -3569,6 +3631,14 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			elog(ERROR, "can not alter table %s when other backend attached this global temp table",
+						RelationGetRelationName(rel));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -4846,6 +4916,12 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot rewrite temporary tables of other sessions")));
 
+			if (RELATION_IS_GLOBAL_TEMP(OldHeap) &&
+				tab->chgPersistence)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot change global temporary table persistence setting")));
+
 			/*
 			 * Select destination tablespace (same as original unless user
 			 * requested a change)
@@ -8183,6 +8259,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -12739,6 +12821,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -12941,6 +13026,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temp table");
+
 	/*
 	 * No work if no change in tablespace.
 	 */
@@ -13315,7 +13403,7 @@ index_copy_data(Relation rel, RelFileNode newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
@@ -14722,7 +14810,9 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
 	 */
 	switch (rel->rd_rel->relpersistence)
 	{
+		/* global temp table same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -17315,3 +17405,36 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
 		table_close(rel, NoLock);
 	}
 }
+
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			bool	res = false;
+			char	*sval = defGetString(def);
+
+			if (!parse_bool(sval, &res))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
+
+			if (res)
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 5a110ed..e35bbeb 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"
@@ -1215,6 +1216,17 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	 /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		is_gtt = true;
+		up_gtt_relstats(relation,
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1228,17 +1240,26 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relpages = (int32) num_pages;
+	else if (pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (is_gtt)
+		relation->rd_rel->reltuples = (float4) num_tuples;
+	else if (pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	else if (pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1283,7 +1304,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1294,7 +1316,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1392,6 +1415,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
@@ -1449,6 +1476,41 @@ 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	safe_age;
+
+		TransactionId oldest_gtt_frozenxid =
+			list_all_session_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		safe_age = oldest_gtt_frozenxid + vacuum_gtt_defer_check_age;
+		if (safe_age < FirstNormalTransactionId)
+			safe_age += FirstNormalTransactionId;
+
+		/*
+		 * We tolerate that the minimum age of gtt is less than
+		 * the minimum age of conventional tables, otherwise it will
+		 * throw warning message.
+		 */
+		if (TransactionIdIsNormal(safe_age) &&
+			TransactionIdPrecedes(safe_age, newFrozenXid))
+		{
+			ereport(WARNING,
+				(errmsg("global temp table oldest relfrozenxid %u is the oldest in the entire db",
+						oldest_gtt_frozenxid),
+				 errdetail("The oldest relfrozenxid in pg_class is %u", newFrozenXid),
+				 errhint("If they differ greatly, please consider cleaning up the data in global temp table.")));
+		}
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid) &&
+			TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+			newFrozenXid = oldest_gtt_frozenxid;
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1776,6 +1838,15 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6e65103..04706ee 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -530,6 +530,11 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 4fdffad..0aa7559 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index fb6ce49..06d9237 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
@@ -545,6 +546,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	init_gtt_storage(mtstate->operation, leaf_part_rri);
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d71c0a4..0900907 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,6 +42,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -2372,6 +2373,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			ExecOpenIndices(resultRelInfo,
 							node->onConflictAction != ONCONFLICT_NONE);
 
+		init_gtt_storage(operation, resultRelInfo);
+
 		/*
 		 * If this is an UPDATE and a BEFORE UPDATE trigger is present, the
 		 * trigger itself might modify the partition-key values. So arrange
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 255f56b..d94d6f0 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 e664eb1..e03bfa0 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6419,7 +6419,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
+	/* global temp table is same as local temp table */
 	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 2554502..04fb5d4 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -28,6 +28,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temp table */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index eee9c33..1891c53 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2594,6 +2594,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 1219ac8..70e5a93 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3302,17 +3302,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; }
@@ -11638,19 +11632,13 @@ OptTempTableName:
 				}
 			| GLOBAL TEMPORARY opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED opt_table qualified_name
 				{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b875a50..123945b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 75c122f..97213ed 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
@@ -3108,6 +3111,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		cxt.isforeign = false;
 	}
 	cxt.relation = stmt->relation;
+	cxt.relation->relpersistence = RelationGetRelPersistence(rel);
 	cxt.rel = rel;
 	cxt.inhRelations = NIL;
 	cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 7e97ffa..1f2ac58 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2089,6 +2089,11 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* autovacuum skip vacuum global temp table */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2155,7 +2160,9 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		/* autovacuum skip vacuum global temp table */
+		if (classForm->relpersistence == RELPERSISTENCE_TEMP || 
+			classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index f9980cf..406a25c 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -53,6 +54,7 @@
 #include "utils/rel.h"
 #include "utils/resowner_private.h"
 #include "utils/timestamp.h"
+#include "utils/guc.h"
 
 
 /* Note: these two macros only work on shared buffers, not local ones! */
@@ -2819,6 +2821,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d5..7adb969 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -21,6 +21,7 @@
 #include "access/nbtree.h"
 #include "access/subtrans.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, active_gtt_shared_hash_size());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* global temporary table */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 3630006..0c8ef80 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -63,6 +63,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -4086,3 +4087,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 5aa19d3..b3bc455 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 8406644..0d99321 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -1008,6 +1008,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 4fdcb07..58fc8de 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -112,6 +112,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -4731,12 +4732,25 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -4861,15 +4875,27 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6251,6 +6277,7 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	{
 		/* Simple variable --- look to stats for the underlying table */
 		RangeTblEntry *rte = planner_rt_fetch(index->rel->relid, root);
+		char	rel_persistence = get_rel_persistence(rte->relid);
 
 		Assert(rte->rtekind == RTE_RELATION);
 		relid = rte->relid;
@@ -6268,6 +6295,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6279,6 +6313,8 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	}
 	else
 	{
+		char	rel_persistence = get_rel_persistence(index->indexoid);
+
 		/* Expression --- maybe there are stats for the index itself */
 		relid = index->indexoid;
 		colnum = 1;
@@ -6294,6 +6330,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7212,6 +7255,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		/* attempt to lookup stats in relation for this index column */
 		if (attnum != 0)
 		{
+			char	rel_persistence = get_rel_persistence(rte->relid);
+
 			/* Simple variable -- look to stats for the underlying table */
 			if (get_relation_stats_hook &&
 				(*get_relation_stats_hook) (root, rte, attnum, &vardata))
@@ -7224,6 +7269,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7236,6 +7289,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		}
 		else
 		{
+			char	rel_persistence = get_rel_persistence(index->indexoid);
+
 			/*
 			 * Looks like we've found an expression column in the index. Let's
 			 * see if there's any stats for it.
@@ -7255,6 +7310,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index a7d63f1..d8a52cb 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -2939,6 +2940,18 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 9f1f11d..aeb2a78 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -65,6 +65,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1133,6 +1134,24 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1187,6 +1206,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			gtt_fix_index_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -1317,7 +1337,17 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+			if (newrelnode != InvalidOid &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2246,6 +2276,8 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		gtt_fix_index_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3472,6 +3504,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3579,28 +3615,34 @@ void
 RelationSetNewRelfilenode(Relation relation, char persistence)
 {
 	Oid			newrelfilenode;
-	Relation	pg_class;
-	HeapTuple	tuple;
+	Relation	pg_class = NULL;
+	HeapTuple	tuple = NULL;
 	Form_pg_class classform;
 	MultiXactId minmulti = InvalidMultiXactId;
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
+	bool		modify_pg_class = !RELATION_IS_GLOBAL_TEMP(relation);
 
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
 
-	/*
-	 * Get a writable copy of the pg_class tuple for the given relation.
-	 */
-	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+	if (modify_pg_class)
+	{
+		/*
+		 * Get a writable copy of the pg_class tuple for the given relation.
+		 */
+		pg_class = table_open(RelationRelationId, RowExclusiveLock);
 
-	tuple = SearchSysCacheCopy1(RELOID,
-								ObjectIdGetDatum(RelationGetRelid(relation)));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "could not find tuple for relation %u",
-			 RelationGetRelid(relation));
-	classform = (Form_pg_class) GETSTRUCT(tuple);
+		tuple = SearchSysCacheCopy1(RELOID,
+									ObjectIdGetDatum(RelationGetRelid(relation)));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u",
+				 RelationGetRelid(relation));
+		classform = (Form_pg_class) GETSTRUCT(tuple);
+	}
+	else
+		memset(&classform, 0, sizeof(classform));
 
 	/*
 	 * Schedule unlinking of the old storage at transaction commit.
@@ -3626,7 +3668,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;
@@ -3646,6 +3688,15 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 			break;
 	}
 
+	if (!modify_pg_class)
+	{
+		Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+		Assert(RELATION_IS_GLOBAL_TEMP(relation));
+		Assert(!RelationIsMapped(relation));
+		relation->rd_node.relNode = relnode;
+		CacheInvalidateRelcache(relation);
+	}
 	/*
 	 * If we're dealing with a mapped index, pg_class.relfilenode doesn't
 	 * change; instead we have to send the update to the relation mapper.
@@ -3655,7 +3706,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	 * possibly-inaccurate values of relpages etc, but those will be fixed up
 	 * later.
 	 */
-	if (RelationIsMapped(relation))
+	else if (RelationIsMapped(relation))
 	{
 		/* This case is only supported for indexes */
 		Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3701,9 +3752,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 		CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 	}
 
-	heap_freetuple(tuple);
+	if (modify_pg_class)
+	{
+		heap_freetuple(tuple);
 
-	table_close(pg_class, RowExclusiveLock);
+		table_close(pg_class, RowExclusiveLock);
+	}
 
 	/*
 	 * Make the pg_class row change or relation map change visible.  This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 5bdc02f..b52b51d 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -37,6 +37,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "commands/prepare.h"
 #include "commands/trigger.h"
@@ -142,6 +143,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temp table feature.
+ * global temp table define can still storage in catalog
+ * just can not use.
+ * num > 0 means database can management num active global temp table.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2070,6 +2083,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."),
@@ -2577,6 +2599,16 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"vacuum_gtt_defer_check_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("The defer check age of GTT, used to check expired data after vacuum."),
+			NULL
+		},
+		&vacuum_gtt_defer_check_age,
+		10000, 0, 1000000,
+		NULL, NULL, NULL
+	},
+
 	/*
 	 * See also CheckRequiredParameterValues() if this parameter changes
 	 */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 5db4f57..018a9bf 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2424,6 +2424,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
 		dopt->no_unlogged_table_data)
 		return;
 
+	/* Don't dump data in global temp table/sequence */
+	if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		return;
+
 	/* Check that the data is not explicitly excluded */
 	if (simple_oid_list_member(&tabledata_exclude_oids,
 							   tbinfo->dobj.catId.oid))
@@ -15777,6 +15781,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
 		char	   *foreign = "";
+		char	   *table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15830,9 +15835,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);
 
@@ -16226,7 +16237,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
-			 tbinfo->relkind == RELKIND_MATVIEW))
+			 tbinfo->relkind == RELKIND_MATVIEW) &&
+			 tbinfo->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
 		{
 			appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
 			appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
@@ -17131,6 +17143,7 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	PQExpBuffer query = createPQExpBuffer();
 	PQExpBuffer delqry = createPQExpBuffer();
 	char	   *qseqname;
+	bool	   global_temp_seq = false;
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
@@ -17140,9 +17153,11 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
-						  "FROM pg_catalog.pg_sequence "
-						  "WHERE seqrelid = '%u'::oid",
+						  "seqcache, seqcycle, c.relpersistence "
+						  "FROM pg_catalog.pg_sequence s, "
+						  "pg_catalog.pg_class c "
+						  "WHERE seqrelid = '%u'::oid "
+						  "and s.seqrelid = c.oid",
 						  tbinfo->dobj.catId.oid);
 	}
 	else if (fout->remoteVersion >= 80400)
@@ -17187,6 +17202,9 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
 
+	if (fout->remoteVersion >= 130000)
+		global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
 	if (strcmp(seqtype, "smallint") == 0)
@@ -17261,9 +17279,13 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	}
 	else
 	{
-		appendPQExpBuffer(query,
-						  "CREATE SEQUENCE %s\n",
-						  fmtQualifiedDumpable(tbinfo));
+		appendPQExpBuffer(query, "CREATE ");
+
+		if (global_temp_seq)
+			appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+		appendPQExpBuffer(query, "SEQUENCE %s\n",
+								  fmtQualifiedDumpable(tbinfo));
 
 		if (strcmp(seqtype, "bigint") != 0)
 			appendPQExpBuffer(query, "    AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 00aef85..9ad71c8 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -84,7 +84,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -157,7 +157,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 7e524ea..371168f 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -441,22 +443,46 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 	 * output, so we have to copy that system table.  It's easiest to do that
 	 * by treating it as a user table.
 	 */
-	snprintf(query + strlen(query), sizeof(query) - strlen(query),
-			 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
-			 "  SELECT c.oid, 0::oid, 0::oid "
-			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
-			 "         ON c.relnamespace = n.oid "
-			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
-	/* exclude possible orphaned temp tables */
-			 "    ((n.nspname !~ '^pg_temp_' AND "
-			 "      n.nspname !~ '^pg_toast_temp_' AND "
-			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
-			 "                        'binary_upgrade', 'pg_toast') AND "
-			 "      c.oid >= %u::pg_catalog.oid) OR "
-			 "     (n.nspname = 'pg_catalog' AND "
-			 "      relname IN ('pg_largeobject') ))), ",
-			 FirstNormalObjectId);
+	if (skip_gtt)
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude global temp tables */
+				 "    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
+	else
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
 
 	/*
 	 * Add a CTE that collects OIDs of toast tables belonging to the tables
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 70194eb..8dd9e66 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -411,7 +411,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -642,7 +642,9 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -653,7 +655,9 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 8b90cef..d155205 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -389,7 +389,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index f05e914..19adab9 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3724,7 +3724,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 0e7a373..a47de01 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1023,6 +1023,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2390,6 +2392,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2598,6 +2603,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 78b33b2..4be89f6 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -168,6 +168,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad..c8236a1 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5558,6 +5558,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4388',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4389',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4390',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4391',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 30c38e0..7ff2408 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,7 +22,7 @@
 /* GUC variables */
 extern int	wal_skip_threshold;
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationPreTruncate(Relation rel);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..902375d
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern int		vacuum_gtt_defer_check_age;
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Relation relation,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void gtt_force_enable_index(Relation index);
+extern void gtt_fix_index_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+extern void gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index e2638ab..89a5ce4 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 3f88683..f870e9a 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 8fda8e4..172eae9 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_TBM,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_SXACT,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index ae4f573..69cce92 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -117,6 +117,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId session_gtt_frozenxid;	/* session level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 2819282..363fc33 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -282,6 +282,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 0b5957b..0720a4c 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	struct SMgrRelationData *rd_smgr;	/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -305,6 +305,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -570,11 +571,13 @@ typedef struct ViewOptions
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -582,6 +585,7 @@ typedef struct ViewOptions
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -594,6 +598,14 @@ typedef struct ViewOptions
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is either local temp relation of this session
+ * 		or global temp relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -637,6 +649,17 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..ca2d135
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,14 @@
+reset search_path;
+select pg_sleep(5);
+ pg_sleep 
+----------
+ 
+(1 row)
+
+drop schema gtt cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
+drop cascades to table gtt.gtt_with_seq
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..d6a675a
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,383 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+ n 
+---
+ 9
+(1 row)
+
+commit;
+-- 0 row
+select * from gtt6;
+ n 
+---
+(0 rows)
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temp table
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  could not create global temporary table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR:  insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count 
+-------
+     1
+(1 row)
+
+select count(*) from orders;
+ count 
+-------
+     0
+(1 row)
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  2 |  3
+(2 rows)
+
+commit;
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+select * from gtt_s_2 order by c1;
+ c1 | c2 
+----+----
+  1 |  2
+  2 |  4
+(2 rows)
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 1)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 300)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 33 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test_new
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.gtt6
+drop cascades to table gtt_function.foo
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to sequence gtt_function.seq_1
+drop cascades to table gtt_function.gtt_s_1
+drop cascades to table gtt_function.gtt_s_2
+drop cascades to table gtt_function.gt1
+drop cascades to table gtt_function.gtt_test1
+drop cascades to table gtt_function.gtt_test2
+drop cascades to table gtt_function.gtt_test3
+drop cascades to table gtt_function.gtt_test4
+drop cascades to table gtt_function.gtt_test5
+drop cascades to table gtt_function.gtt_test6
+drop cascades to table gtt_function.gtt_test7
+drop cascades to table gtt_function.gtt_test8
+drop cascades to table gtt_function.gtt_test9
+drop cascades to table gtt_function.gtt_test10
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..0646aae
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,90 @@
+set search_path=gtt,sys;
+select nextval('gtt_with_seq_c2_seq');
+ nextval 
+---------
+       1
+(1 row)
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..2f084be
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,343 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+rollback;
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+commit;
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          114688 |                 614400
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |            49152 |         49152 |                  49152
+(2 rows)
+
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2000
+(1 row)
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+ c1 | c2 
+----+----
+  1 |  1
+(1 row)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..8c0c376
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,10 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..4420fdb
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,80 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+ gtt_stats  | gtt_pkey  |        1 |         0 |             0
+(2 rows)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+reindex table gtt;
+reindex index gtt_pkey;
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+ gtt_stats  | gtt_pkey  |       30 |     10000 |             0
+(2 rows)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index ac31840..55b221e 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1349,6 +1349,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 95f1925..76b2374 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..2c8e586
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,8 @@
+
+
+reset search_path;
+
+select pg_sleep(5);
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..81f0bfc
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,253 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+commit;
+-- 0 row
+select * from gtt6;
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+commit;
+select * from gtt_s_1 order by c1;
+select * from gtt_s_2 order by c1;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d05745e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,44 @@
+
+
+set search_path=gtt,sys;
+
+select nextval('gtt_with_seq_c2_seq');
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..39cca5e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,154 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+rollback;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+commit;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+select count(*) from gtt_t_kenyon;
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..dbe84d1
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,19 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..d61b0ff
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,46 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+reindex table gtt;
+
+reindex index gtt_pkey;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#245Prabhat Sahu
prabhat.sahu@enterprisedb.com
In reply to: 曾文旌 (#244)
Re: [Proposal] Global temporary tables

On Fri, Apr 17, 2020 at 2:44 PM 曾文旌 <wenjing.zwj@alibaba-inc.com> wrote:

I improved the logic of the warning message so that when the gap between
relfrozenxid of GTT is small,
it will no longer be alarmed message.

Hi Wenjing,
Thanks for the patch(v26), I have verified the previous related issues, and
are working fine now.
Please check the below scenario VACUUM from a non-super user.

-- Create user "test_gtt", connect it , create gtt, VACUUM gtt and VACUUM /
VACUUM FULL
postgres=# CREATE USER test_gtt;
CREATE ROLE
postgres=# \c postgres test_gtt
You are now connected to database "postgres" as user "test_gtt".
postgres=> CREATE GLOBAL TEMPORARY TABLE gtt1(c1 int);
CREATE TABLE

-- VACUUM gtt is working fine, whereas we are getting huge WARNING for
VACUUM / VACUUM FULL as below:
postgres=> VACUUM gtt1 ;
VACUUM
postgres=> VACUUM;
WARNING: skipping "pg_statistic" --- only superuser or database owner can
vacuum it
WARNING: skipping "pg_type" --- only superuser or database owner can
vacuum it
WARNING: skipping "pg_toast_2600" --- only table or database owner can
vacuum it
WARNING: skipping "pg_toast_2600_index" --- only table or database owner
can vacuum it

... ...
... ...

WARNING: skipping "_pg_foreign_tables" --- only table or database owner
can vacuum it
WARNING: skipping "foreign_table_options" --- only table or database owner
can vacuum it
WARNING: skipping "user_mapping_options" --- only table or database owner
can vacuum it
WARNING: skipping "user_mappings" --- only table or database owner can
vacuum it
VACUUM

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

#246Prabhat Sahu
prabhat.sahu@enterprisedb.com
In reply to: Prabhat Sahu (#245)
Re: [Proposal] Global temporary tables

Hi Wenjing,

Please check below scenario, we are getting a server crash with "ALTER
TABLE" add column with default value as sequence:

-- Create gtt, exit and re-connect the psql prompt, create sequence, alter
table add a column with sequence.
postgres=# create global temporary table gtt1 (c1 int);
CREATE TABLE
postgres=# \q
[edb@localhost bin]$ ./psql postgres
psql (13devel)
Type "help" for help.

postgres=# create sequence seq;
CREATE SEQUENCE
postgres=# alter table gtt1 add c2 int default nextval('seq');
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.
!?> \q

-- Stack trace:
[edb@localhost bin]$ gdb -q -c data/core.70358 postgres
Reading symbols from
/home/edb/PG/PGsrcNew/postgresql/inst/bin/postgres...done.
[New LWP 70358]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Core was generated by `postgres: edb postgres [local] ALTER TABLE
'.
Program terminated with signal 6, Aborted.
#0 0x00007f150223b337 in raise () from /lib64/libc.so.6
Missing separate debuginfos, use: debuginfo-install
glibc-2.17-292.el7.x86_64 keyutils-libs-1.5.8-3.el7.x86_64
krb5-libs-1.15.1-37.el7_7.2.x86_64 libcom_err-1.42.9-16.el7.x86_64
libgcc-4.8.5-39.el7.x86_64 libselinux-2.5-14.1.el7.x86_64
openssl-libs-1.0.2k-19.el7.x86_64 pcre-8.32-17.el7.x86_64
zlib-1.2.7-18.el7.x86_64
(gdb) bt
#0 0x00007f150223b337 in raise () from /lib64/libc.so.6
#1 0x00007f150223ca28 in abort () from /lib64/libc.so.6
#2 0x0000000000ab2cdd in ExceptionalCondition (conditionName=0xc03ab8
"OidIsValid(relfilenode1) && OidIsValid(relfilenode2)",
errorType=0xc0371f "FailedAssertion", fileName=0xc03492 "cluster.c",
lineNumber=1637) at assert.c:67
#3 0x000000000065e200 in gtt_swap_relation_files (r1=16384, r2=16390,
target_is_pg_class=false, swap_toast_by_content=false, is_internal=true,
frozenXid=490, cutoffMulti=1, mapped_tables=0x7ffd841f7ee0) at
cluster.c:1637
#4 0x000000000065dcd9 in finish_heap_swap (OIDOldHeap=16384,
OIDNewHeap=16390, is_system_catalog=false, swap_toast_by_content=false,
check_constraints=true, is_internal=true, frozenXid=490, cutoffMulti=1,
newrelpersistence=103 'g') at cluster.c:1395
#5 0x00000000006bca18 in ATRewriteTables (parsetree=0x1deab80,
wqueue=0x7ffd841f80c8, lockmode=8, context=0x7ffd841f8260) at
tablecmds.c:4991
#6 0x00000000006ba890 in ATController (parsetree=0x1deab80,
rel=0x7f150378f330, cmds=0x1deab28, recurse=true, lockmode=8,
context=0x7ffd841f8260)
at tablecmds.c:3991
#7 0x00000000006ba4f8 in AlterTable (stmt=0x1deab80, lockmode=8,
context=0x7ffd841f8260) at tablecmds.c:3644
#8 0x000000000093b62a in ProcessUtilitySlow (pstate=0x1e0d6d0,
pstmt=0x1deac48,
queryString=0x1de9b30 "alter table gtt1 add c2 int default
nextval('seq');", context=PROCESS_UTILITY_TOPLEVEL, params=0x0,
queryEnv=0x0, dest=0x1deaf28,
qc=0x7ffd841f8830) at utility.c:1267
#9 0x000000000093b141 in standard_ProcessUtility (pstmt=0x1deac48,
queryString=0x1de9b30 "alter table gtt1 add c2 int default
nextval('seq');",
context=PROCESS_UTILITY_TOPLEVEL, params=0x0, queryEnv=0x0,
dest=0x1deaf28, qc=0x7ffd841f8830) at utility.c:1067
#10 0x000000000093a22b in ProcessUtility (pstmt=0x1deac48,
queryString=0x1de9b30 "alter table gtt1 add c2 int default
nextval('seq');",
context=PROCESS_UTILITY_TOPLEVEL, params=0x0, queryEnv=0x0,
dest=0x1deaf28, qc=0x7ffd841f8830) at utility.c:522
#11 0x000000000093909d in PortalRunUtility (portal=0x1e4fba0,
pstmt=0x1deac48, isTopLevel=true, setHoldSnapshot=false, dest=0x1deaf28,
qc=0x7ffd841f8830)
at pquery.c:1157
#12 0x00000000009392b3 in PortalRunMulti (portal=0x1e4fba0,
isTopLevel=true, setHoldSnapshot=false, dest=0x1deaf28, altdest=0x1deaf28,
qc=0x7ffd841f8830)
at pquery.c:1303
#13 0x00000000009387d1 in PortalRun (portal=0x1e4fba0,
count=9223372036854775807, isTopLevel=true, run_once=true, dest=0x1deaf28,
altdest=0x1deaf28,
qc=0x7ffd841f8830) at pquery.c:779
#14 0x000000000093298b in exec_simple_query (query_string=0x1de9b30 "alter
table gtt1 add c2 int default nextval('seq');") at postgres.c:1239
#15 0x0000000000936997 in PostgresMain (argc=1, argv=0x1e13b80,
dbname=0x1e13a78 "postgres", username=0x1e13a58 "edb") at postgres.c:4315
#16 0x00000000008868b3 in BackendRun (port=0x1e0bb50) at postmaster.c:4510
#17 0x00000000008860a8 in BackendStartup (port=0x1e0bb50) at
postmaster.c:4202
#18 0x0000000000882626 in ServerLoop () at postmaster.c:1727
#19 0x0000000000881efd in PostmasterMain (argc=3, argv=0x1de4460) at
postmaster.c:1400
#20 0x0000000000789288 in main (argc=3, argv=0x1de4460) at main.c:210
(gdb)

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

#247曾文旌
wenjing.zwj@alibaba-inc.com
In reply to: Prabhat Sahu (#246)
2 attachment(s)
Re: [Proposal] Global temporary tables

2020年4月17日 下午8:59,Prabhat Sahu <prabhat.sahu@enterprisedb.com> 写道:

Hi Wenjing,

Please check below scenario, we are getting a server crash with "ALTER TABLE" add column with default value as sequence:

-- Create gtt, exit and re-connect the psql prompt, create sequence, alter table add a column with sequence.
postgres=# create global temporary table gtt1 (c1 int);
CREATE TABLE
postgres=# \q
[edb@localhost bin]$ ./psql postgres
psql (13devel)
Type "help" for help.

postgres=# create sequence seq;
CREATE SEQUENCE
postgres=# alter table gtt1 add c2 int default nextval('seq');
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.
!?> \q

Fixed in global_temporary_table_v27-pg13.patch

Wenjing

Show quoted text

-- Stack trace:
[edb@localhost bin]$ gdb -q -c data/core.70358 postgres
Reading symbols from /home/edb/PG/PGsrcNew/postgresql/inst/bin/postgres...done.
[New LWP 70358]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Core was generated by `postgres: edb postgres [local] ALTER TABLE '.
Program terminated with signal 6, Aborted.
#0 0x00007f150223b337 in raise () from /lib64/libc.so.6
Missing separate debuginfos, use: debuginfo-install glibc-2.17-292.el7.x86_64 keyutils-libs-1.5.8-3.el7.x86_64 krb5-libs-1.15.1-37.el7_7.2.x86_64 libcom_err-1.42.9-16.el7.x86_64 libgcc-4.8.5-39.el7.x86_64 libselinux-2.5-14.1.el7.x86_64 openssl-libs-1.0.2k-19.el7.x86_64 pcre-8.32-17.el7.x86_64 zlib-1.2.7-18.el7.x86_64
(gdb) bt
#0 0x00007f150223b337 in raise () from /lib64/libc.so.6
#1 0x00007f150223ca28 in abort () from /lib64/libc.so.6
#2 0x0000000000ab2cdd in ExceptionalCondition (conditionName=0xc03ab8 "OidIsValid(relfilenode1) && OidIsValid(relfilenode2)",
errorType=0xc0371f "FailedAssertion", fileName=0xc03492 "cluster.c", lineNumber=1637) at assert.c:67
#3 0x000000000065e200 in gtt_swap_relation_files (r1=16384, r2=16390, target_is_pg_class=false, swap_toast_by_content=false, is_internal=true,
frozenXid=490, cutoffMulti=1, mapped_tables=0x7ffd841f7ee0) at cluster.c:1637
#4 0x000000000065dcd9 in finish_heap_swap (OIDOldHeap=16384, OIDNewHeap=16390, is_system_catalog=false, swap_toast_by_content=false,
check_constraints=true, is_internal=true, frozenXid=490, cutoffMulti=1, newrelpersistence=103 'g') at cluster.c:1395
#5 0x00000000006bca18 in ATRewriteTables (parsetree=0x1deab80, wqueue=0x7ffd841f80c8, lockmode=8, context=0x7ffd841f8260) at tablecmds.c:4991
#6 0x00000000006ba890 in ATController (parsetree=0x1deab80, rel=0x7f150378f330, cmds=0x1deab28, recurse=true, lockmode=8, context=0x7ffd841f8260)
at tablecmds.c:3991
#7 0x00000000006ba4f8 in AlterTable (stmt=0x1deab80, lockmode=8, context=0x7ffd841f8260) at tablecmds.c:3644
#8 0x000000000093b62a in ProcessUtilitySlow (pstate=0x1e0d6d0, pstmt=0x1deac48,
queryString=0x1de9b30 "alter table gtt1 add c2 int default nextval('seq');", context=PROCESS_UTILITY_TOPLEVEL, params=0x0, queryEnv=0x0, dest=0x1deaf28,
qc=0x7ffd841f8830) at utility.c:1267
#9 0x000000000093b141 in standard_ProcessUtility (pstmt=0x1deac48, queryString=0x1de9b30 "alter table gtt1 add c2 int default nextval('seq');",
context=PROCESS_UTILITY_TOPLEVEL, params=0x0, queryEnv=0x0, dest=0x1deaf28, qc=0x7ffd841f8830) at utility.c:1067
#10 0x000000000093a22b in ProcessUtility (pstmt=0x1deac48, queryString=0x1de9b30 "alter table gtt1 add c2 int default nextval('seq');",
context=PROCESS_UTILITY_TOPLEVEL, params=0x0, queryEnv=0x0, dest=0x1deaf28, qc=0x7ffd841f8830) at utility.c:522
#11 0x000000000093909d in PortalRunUtility (portal=0x1e4fba0, pstmt=0x1deac48, isTopLevel=true, setHoldSnapshot=false, dest=0x1deaf28, qc=0x7ffd841f8830)
at pquery.c:1157
#12 0x00000000009392b3 in PortalRunMulti (portal=0x1e4fba0, isTopLevel=true, setHoldSnapshot=false, dest=0x1deaf28, altdest=0x1deaf28, qc=0x7ffd841f8830)
at pquery.c:1303
#13 0x00000000009387d1 in PortalRun (portal=0x1e4fba0, count=9223372036854775807, isTopLevel=true, run_once=true, dest=0x1deaf28, altdest=0x1deaf28,
qc=0x7ffd841f8830) at pquery.c:779
#14 0x000000000093298b in exec_simple_query (query_string=0x1de9b30 "alter table gtt1 add c2 int default nextval('seq');") at postgres.c:1239
#15 0x0000000000936997 in PostgresMain (argc=1, argv=0x1e13b80, dbname=0x1e13a78 "postgres", username=0x1e13a58 "edb") at postgres.c:4315
#16 0x00000000008868b3 in BackendRun (port=0x1e0bb50) at postmaster.c:4510
#17 0x00000000008860a8 in BackendStartup (port=0x1e0bb50) at postmaster.c:4202
#18 0x0000000000882626 in ServerLoop () at postmaster.c:1727
#19 0x0000000000881efd in PostmasterMain (argc=3, argv=0x1de4460) at postmaster.c:1400
#20 0x0000000000789288 in main (argc=3, argv=0x1de4460) at main.c:210
(gdb)

--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

Attachments:

global_temporary_table_v27-pg13.patchapplication/octet-stream; name=global_temporary_table_v27-pg13.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 8ccc228..b061de9 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -168,6 +168,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * For global temp table only
+	 * use ShareUpdateExclusiveLock for ensure safety
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temp table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},	
 	/* list terminator */
 	{{NULL}}
 };
@@ -1817,6 +1830,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1961,13 +1976,18 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	/*
 	 * There are no options for partitioned tables yet, but this is able to do
 	 * some validation.
 	 */
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index 765329b..4761fdc 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1011,7 +1011,9 @@ gistproperty(Oid index_oid, int attno,
 XLogRecPtr
 gistGetFakeLSN(Relation rel)
 {
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	/* global temp is same as local temp table */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 3ec6d52..1222594 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -150,7 +150,9 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	/* global temp table is same as local temp table */
+	if (!(index->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		index->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 56b3562..d3443ec 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -587,7 +587,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -640,7 +640,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 3c18db2..b08a1fc 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -62,6 +62,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -431,9 +432,9 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
 	Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
-	/* not every AM requires these to be valid, but heap does */
-	Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
-	Assert(MultiXactIdIsValid(onerel->rd_rel->relminmxid));
+	/* not every AM requires these to be valid, but regular heap does */
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ MultiXactIdIsValid(onerel->rd_rel->relminmxid));
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 39b8f17..abb76dc 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -601,6 +602,10 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 11e3273..32694c9 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6422,6 +6422,10 @@ StartupXLOG(void)
 	else
 		recoveryTargetTLI = ControlFile->checkPointCopy.ThisTimeLineID;
 
+	/* clean temp relation files */
+	if (max_active_gtt > 0)
+		RemovePgTempFiles();
+
 	/*
 	 * Check for signal files, and if so set up state for offline recovery
 	 */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 9499bb3..ae47364 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -43,6 +43,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 7d6acae..3be8d63 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -393,7 +393,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/* global temp table is same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 632c058..01cf990 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -427,7 +429,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -959,6 +961,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -997,8 +1000,18 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/* global temp table not remember transaction info in catalog */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1360,6 +1373,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1940,6 +1954,14 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/* We allow to drop global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			elog(ERROR, "can not drop relation %s when other backend attached this global temp table",
+						RelationGetRelationName(rel));
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3167,7 +3189,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3179,7 +3201,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3225,8 +3247,13 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		/* truncate global temp table only need RowExclusiveLock */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3259,6 +3286,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3267,23 +3295,37 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (!gtt_storage_attached(RelationGetRelid(rel)))
+			return;
+
+		/*
+		 * Truncate global temp table only need RowExclusiveLock
+		 */
+		lockmode = RowExclusiveLock;
+	}
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(rel, 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index bd7ec92..747cda0 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -53,6 +53,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -881,6 +882,19 @@ index_create(Relation heapRelation,
 						indexRelationName, RelationGetRelationName(heapRelation))));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temp table use concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot reindex global temporary tables concurrently")));
+
+		/* if global temp table not init storage, then skip build index */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -2041,7 +2055,8 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 * lock (see comments in RemoveRelations), and a non-concurrent DROP is
 	 * more efficient.
 	 */
-	Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+	Assert(!(get_rel_persistence(indexId) == RELPERSISTENCE_TEMP ||
+			 get_rel_persistence(indexId) == RELPERSISTENCE_GLOBAL_TEMP) ||
 		   (!concurrent && !concurrent_lock_mode));
 
 	/*
@@ -2073,6 +2088,14 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/* We allow to drop index on global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+			elog(ERROR, "can not drop index %s when other backend attached this global temp table.",
+						RelationGetRelationName(userHeapRelation));
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2681,6 +2704,11 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	/* update index stats into localhash and rel_rd_rel for global temp table */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		is_gtt = true;
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2766,21 +2794,35 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
+		if (is_gtt)
+			rel->rd_rel->relpages = (int32) relpages;
+		else if (rd_rel->relpages != (int32) relpages)
 		{
 			rd_rel->relpages = (int32) relpages;
 			dirty = true;
 		}
-		if (rd_rel->reltuples != (float4) reltuples)
+
+		if (is_gtt)
+			rel->rd_rel->reltuples = (float4) reltuples;
+		else if (rd_rel->reltuples != (float4) reltuples)
 		{
 			rd_rel->reltuples = (float4) reltuples;
 			dirty = true;
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+
+		if (is_gtt)
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+		else if (rd_rel->relallvisible != (int32) relallvisible)
 		{
 			rd_rel->relallvisible = (int32) relallvisible;
 			dirty = true;
 		}
+
+		if (is_gtt)
+		{
+			up_gtt_relstats(rel, relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
+		}
 	}
 
 	/*
@@ -2894,6 +2936,15 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, index, val);
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			gtt_force_enable_index(indexRelation);
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3442,6 +3493,10 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	PGRUsage	ru0;
 	bool		progress = (options & REINDEXOPT_REPORT_PROGRESS) != 0;
 
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		!gtt_storage_attached(indexId))
+		return;
+
 	pg_rusage_init(&ru0);
 
 	/*
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 2ec2301..1b6061d 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -655,6 +655,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		/* global temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp relations in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index d713d5c..afcf004 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,6 +27,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -61,6 +62,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			relOid;			/* InvalidOid if not a global temp rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -115,7 +117,7 @@ AddPendingSync(const RelFileNode *rnode)
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -127,6 +129,8 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		/* global temp table use same storage strategy as local temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -154,6 +158,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->relOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
@@ -165,6 +170,13 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		AddPendingSync(&rnode);
 	}
 
+	/* remember global temp table storage info to localhash */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel)
+	{
+		pending->relOid = RelationGetRelid(rel);
+		remember_gtt_storage_info(rnode, rel);
+	}
+
 	return srel;
 }
 
@@ -201,11 +213,15 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->relOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->relOid = RelationGetRelid(rel);
+
 	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
@@ -602,6 +618,7 @@ smgrDoPendingDeletes(bool isCommit)
 				i = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -631,14 +648,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->relOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -651,9 +672,18 @@ smgrDoPendingDeletes(bool isCommit)
 		smgrdounlinkall(srels, nrels, false);
 
 		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* clean global temp table flags when transaction commit or rollback */
+			if (SmgrIsTemp(srels[i]) &&
+				reloids[i] != InvalidOid &&
+				gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..451cf82
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1491 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  code to create and destroy physical storage for global temparary table
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_relstats_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+int		vacuum_gtt_defer_check_age = 0;
+
+typedef struct gtt_ctl_data
+{
+	LWLock			lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+	Oid			relfilenode;
+	Oid			spcnode;
+
+	/* pg_class stat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+
+	/* pg_statistic */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+
+	Oid			oldrelid;			/* remember the source of relid, before the switch relfilenode. */
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_reset_statistics(gtt_local_hash_entry *entry);
+static void gtt_free_statistics(gtt_local_hash_entry *entry);
+static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok);
+static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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();
+	}
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(gtt_fnode);
+	info.entrysize = action_gtt_shared_hash_entry_size();
+	active_gtt_shared_hash =
+		ShmemInitHash("active gtt shared hash",
+						gtt_shared_ctl->max_entry,
+						gtt_shared_ctl->max_entry,
+						&info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			found;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *)&(fnode), 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_global_temporary_table.")));
+	}
+
+	if (found == false)
+	{
+		int			wordnum;
+
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode				fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	if (!skiplock)
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		if (!skiplock)
+			LWLockRelease(&gtt_shared_ctl->lock);
+
+		if (isCommit)
+			elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+	bms_del_member(entry->map, MyBackendId);
+
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+
+	if (!skiplock)
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+	return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	Bitmapset	*map_copy = NULL;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool		in_use = false;
+	int			num_use = 0;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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;
+	gtt_relfilenode			*new_node = NULL;
+	Oid						relid = RelationGetRelid(rel);
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		/* First time through: initialize the hash table */
+		HASHCTL		ctl;
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_relstats_context =
+			AllocSetContextCreate(CacheMemoryContext,
+							"gtt relstats context",
+							ALLOCSET_DEFAULT_SIZES);
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+	entry = gtt_search_by_relid(relid, true);
+	if (!entry)
+	{
+		bool		found = false;
+		int 		natts = 0;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash,
+						(void *) &relid, HASH_ENTER, &found);
+
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+		entry->natts = 0;
+		entry->attnum = NULL;
+		entry->att_stat_tups = NULL;
+		entry->oldrelid = InvalidOid;
+
+		natts = RelationGetNumberOfAttributes(rel);
+		entry->attnum = palloc0(sizeof(int) * natts);
+		entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+		entry->natts = natts;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			gtt_storage_checkin(relid);
+		}
+	}
+
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* only heap contain transaction information */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	gtt_reset_statistics(entry);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	gtt_relfilenode *d_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+		return;
+	}
+
+	d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+		else if (entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+			gtt_relfilenode *gttnode2 = NULL;
+
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+			Assert(gttnode2->relfilenode == rnode.relNode);
+			Assert(list_length(entry->relfilenode_list) == 1);
+			/* rollback switch relfilenode */
+			gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+									   entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+									   false);
+			/* clean up footprint */
+			entry2->oldrelid = InvalidOid;
+			d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+			Assert(d_rnode);
+		}
+		else
+		{
+			if (entry->relfilenode_list == NIL)
+			{
+				if (entry->relkind == RELKIND_RELATION ||
+					entry->relkind == RELKIND_SEQUENCE)
+					gtt_storage_checkout(relid, false, isCommit);
+
+				gtt_free_statistics(entry);
+				hash_search(gtt_storage_local_hash,
+						(void *) &(relid), HASH_REMOVE, NULL);
+			}
+
+			return;
+		}
+	}
+
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	pfree(d_rnode);
+	if (entry->relfilenode_list == NIL)
+	{
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(relid, false, isCommit);
+
+		if (isCommit && entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			/* clean up footprint */
+			entry2->oldrelid = InvalidOid;
+		}
+
+		gtt_free_statistics(entry);
+		hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_REMOVE, NULL);
+	}
+	else
+		gtt_reset_statistics(entry);
+
+	return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool found = false;
+	gtt_local_hash_entry *entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry)
+		found = true;
+
+	return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS				status;
+	gtt_local_hash_entry		*entry;
+	SMgrRelation	*srels = NULL;
+	Oid				*relids = NULL;
+	char			*relkinds = NULL;
+	int			nrels = 0,
+				nfiles = 0,
+				maxrels = 0,
+				maxfiles = 0,
+				i = 0;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel;
+			RelFileNode 	rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel = smgropen(rnode, MyBackendId);
+
+			if (maxfiles == 0)
+			{
+				maxfiles = 32;
+				srels = palloc(sizeof(SMgrRelation) * maxfiles);
+			}
+			else if (maxfiles <= nfiles)
+			{
+				maxfiles *= 2;
+				srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+			}
+
+			srels[nfiles++] = srel;
+		}
+
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			relids  = palloc(sizeof(Oid) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			relids  = repalloc(relids , sizeof(Oid) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		relkinds[nrels] = entry->relkind;
+		relids[nrels] = entry->relid;
+		nrels++;
+	}
+
+	if (nfiles > 0)
+	{
+		smgrdounlinkall(srels, nfiles, false);
+		for (i = 0; i < nfiles; i++)
+			smgrclose(srels[i]);
+
+		pfree(srels);
+	}
+
+	if (nrels)
+	{
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+		for (i = 0; i < nrels; i++)
+		{
+			if (relkinds[i] == RELKIND_RELATION ||
+				relkinds[i] == RELKIND_SEQUENCE)
+				gtt_storage_checkout(relids[i], true, false);
+		}
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+		pfree(relids);
+		pfree(relkinds);
+	}
+
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+	return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible) 
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+					BlockNumber num_pages,
+					double num_tuples,
+					BlockNumber num_all_visible_pages,
+					TransactionId relfrozenxid,
+					TransactionId relminmxid)
+{
+	Oid		relid = RelationGetRelid(relation);
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (num_pages >= 0 &&
+		gtt_rnode->relpages != (int32)num_pages)
+		gtt_rnode->relpages = (int32)num_pages;
+
+	if (num_tuples >= 0 &&
+		gtt_rnode->reltuples != (float4)num_tuples)
+		gtt_rnode->reltuples = (float4)num_tuples;
+
+	/* only heap contain transaction information and relallvisible */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (gtt_rnode->relallvisible >= 0 &&
+			gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+		{
+			gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+		}
+
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNewTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible) 
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	MemoryContext			oldcontext;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return;
+
+	if (entry->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	Assert(entry->relid == reloid);
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == 0)
+		{
+			entry->attnum[i] = attnum;
+			break;
+		}
+		else if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < entry->natts);
+	Assert(entry->att_stat_tups[i] == NULL);
+	entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return NULL;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			return entry->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int			i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	HeapTuple	tuple;
+	Relation	rel = NULL;
+	int		attnum = PG_GETARG_INT32(1);
+	Oid		reloid = PG_GETARG_OID(0);
+	char		rel_persistence;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	  tupdesc;
+	MemoryContext oldcontext;
+	Tuplestorestate *tupstore;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+
+	relation_close(rel, NoLock);
+	relation_close(pg_tatistic, AccessShareLock);
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate	*tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Oid			relnode = 0;
+	Relation	rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[6];
+		bool	isnull[6];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(reloid);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(RelationGetRelid(index)));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid relOid = index->rd_index->indrelid;
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	if (gtt_storage_attached(relOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int			i;
+	Oid			toastrelid;
+
+	if (!(operation == CMD_UTILITY || operation == CMD_INSERT))
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+
+		index_build(relation, index, info, true, false);
+	}
+
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid 		indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo 	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+static void
+gtt_reset_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+
+		entry->attnum[i] = 0;
+	}
+
+	return;
+}
+
+static void
+gtt_free_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (entry->attnum)
+		pfree(entry->attnum);
+
+	if (entry->att_stat_tups)
+		pfree(entry->att_stat_tups);
+
+	return;
+}
+
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
+void
+gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint)
+{
+	gtt_local_hash_entry	*entry1;
+	gtt_local_hash_entry	*entry2;
+	gtt_relfilenode			*gtt_rnode1 = NULL;
+	gtt_relfilenode			*gtt_rnode2 = NULL;
+	MemoryContext			oldcontext;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry1 = gtt_search_by_relid(rel1, false);
+	gtt_rnode1 = gtt_search_relfilenode(entry1, relfilenode1, false);
+
+	entry2 = gtt_search_by_relid(rel2, false);
+	gtt_rnode2 = gtt_search_relfilenode(entry2, relfilenode2, false);
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	entry1->relfilenode_list = list_delete_ptr(entry1->relfilenode_list, gtt_rnode1);
+	entry2->relfilenode_list = lappend(entry2->relfilenode_list, gtt_rnode1);
+
+	entry2->relfilenode_list = list_delete_ptr(entry2->relfilenode_list, gtt_rnode2);
+	entry1->relfilenode_list = lappend(entry1->relfilenode_list, gtt_rnode2);
+	MemoryContextSwitchTo(oldcontext);
+
+	if (footprint)
+	{
+		entry1->oldrelid = rel2;
+		entry2->oldrelid = rel1;
+	}
+
+	return;
+}
+
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok)
+{
+	gtt_relfilenode			*rnode = NULL;
+	ListCell				*lc;
+
+	Assert(entry);
+
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == relfilenode)
+		{
+			rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (!missing_ok && rnode == NULL)
+		elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+	return rnode;
+}
+
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry == NULL && !missing_ok)
+		elog(ERROR, "relid %u not found in local hash", relid);
+
+	return entry;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index d406ea8..88be041 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM 
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 924ef37..db12eef 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -184,6 +185,13 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -586,14 +594,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 		 * pg_statistic for columns we didn't process, we leave them alone.)
 		 */
 		update_attstats(RelationGetRelid(onerel), inh,
-						attr_cnt, vacattrstats);
+						attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
 
 		for (ind = 0; ind < nindexes; ind++)
 		{
 			AnlIndexData *thisdata = &indexdata[ind];
 
 			update_attstats(RelationGetRelid(Irel[ind]), false,
-							thisdata->attr_cnt, thisdata->vacattrstats);
+							thisdata->attr_cnt, thisdata->vacattrstats,
+							RelationGetRelPersistence(Irel[ind]));
 		}
 
 		/*
@@ -1456,7 +1465,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
  *		by taking a self-exclusive lock on the relation in analyze_rel().
  */
 static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
 {
 	Relation	sd;
 	int			attno;
@@ -1558,31 +1567,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/* Update column statistic to localhash, not catalog */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 04d12a7..42c82c8 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/toasting.h"
 #include "commands/cluster.h"
 #include "commands/progress.h"
@@ -72,6 +73,12 @@ static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
 							bool verbose, bool *pSwapToastByContent,
 							TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
 static List *get_tables_to_cluster(MemoryContext cluster_context);
+static void gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables);
 
 
 /*---------------------------------------------------------------------------
@@ -366,6 +373,14 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap) &&
+		!gtt_storage_attached(RelationGetRelid(OldHeap)))
+	{
+		relation_close(OldHeap, AccessExclusiveLock);
+		pgstat_progress_end_command();
+		return;
+	}
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
@@ -750,6 +765,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	BlockNumber num_pages;
 	int			elevel = verbose ? INFO : DEBUG2;
 	PGRUsage	ru0;
+	bool		is_gtt = false;
+	uint32		gtt_relfrozenxid = 0;
+	uint32		gtt_relminmxid = 0;
 
 	pg_rusage_init(&ru0);
 
@@ -763,6 +781,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	else
 		OldIndex = NULL;
 
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		is_gtt = true;
+
 	/*
 	 * Their tuple descriptors should be exactly alike, but here we only need
 	 * assume that they have the same number of columns.
@@ -830,20 +851,37 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 						  &OldestXmin, &FreezeXid, NULL, &MultiXactCutoff,
 						  NULL);
 
-	/*
-	 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
-	 * backwards, so take the max.
-	 */
-	if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
-		TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
-		FreezeXid = OldHeap->rd_rel->relfrozenxid;
+	if (is_gtt)
+	{
+		get_gtt_relstats(OIDOldHeap,
+					NULL, NULL, NULL,
+					&gtt_relfrozenxid, &gtt_relminmxid);
 
-	/*
-	 * MultiXactCutoff, similarly, shouldn't go backwards either.
-	 */
-	if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
-		MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
-		MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+		if (TransactionIdIsValid(gtt_relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, gtt_relfrozenxid))
+			FreezeXid = gtt_relfrozenxid;
+
+		if (MultiXactIdIsValid(gtt_relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, gtt_relminmxid))
+			MultiXactCutoff = gtt_relminmxid;
+	}
+	else
+	{
+		/*
+		 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
+		 * backwards, so take the max.
+		 */
+		if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
+			FreezeXid = OldHeap->rd_rel->relfrozenxid;
+
+		/*
+		 * MultiXactCutoff, similarly, shouldn't go backwards either.
+		 */
+		if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
+			MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+	}
 
 	/*
 	 * Decide whether to use an indexscan or seqscan-and-optional-sort to scan
@@ -911,6 +949,12 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	table_close(OldHeap, NoLock);
 	table_close(NewHeap, NoLock);
 
+	if (is_gtt)
+	{
+		CommandCounterIncrement();
+		return;
+	}
+
 	/* Update pg_class to reflect the correct values of pages and tuples. */
 	relRelation = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1346,10 +1390,21 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 	 * Swap the contents of the heap relations (including any toast tables).
 	 * Also set old heap's relfrozenxid to frozenXid.
 	 */
-	swap_relation_files(OIDOldHeap, OIDNewHeap,
+	if (newrelpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		Assert(!is_system_catalog);
+		gtt_swap_relation_files(OIDOldHeap, OIDNewHeap,
+								(OIDOldHeap == RelationRelationId),
+								swap_toast_by_content, is_internal,
+								frozenXid, cutoffMulti, mapped_tables);
+	}
+	else
+	{
+		swap_relation_files(OIDOldHeap, OIDNewHeap,
 						(OIDOldHeap == RelationRelationId),
 						swap_toast_by_content, is_internal,
 						frozenXid, cutoffMulti, mapped_tables);
+	}
 
 	/*
 	 * If it's a system catalog, queue a sinval message to flush all catcaches
@@ -1557,3 +1612,141 @@ get_tables_to_cluster(MemoryContext cluster_context)
 
 	return rvs;
 }
+
+static void
+gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables)
+{
+	Relation	relRelation;
+	Oid			relfilenode1,
+				relfilenode2;
+	Relation	rel1;
+	Relation	rel2;
+
+	relRelation = table_open(RelationRelationId, RowExclusiveLock);
+
+	rel1 = relation_open(r1, AccessExclusiveLock);
+	rel2 = relation_open(r2, AccessExclusiveLock);
+
+	relfilenode1 = gtt_fetch_current_relfilenode(r1);
+	relfilenode2 = gtt_fetch_current_relfilenode(r2);
+
+	Assert(OidIsValid(relfilenode1) && OidIsValid(relfilenode2));
+	gtt_switch_rel_relfilenode(r1, relfilenode1, r2, relfilenode2, true);
+
+	CacheInvalidateRelcache(rel1);
+	CacheInvalidateRelcache(rel2);
+
+	InvokeObjectPostAlterHookArg(RelationRelationId, r1, 0,
+								 InvalidOid, is_internal);
+	InvokeObjectPostAlterHookArg(RelationRelationId, r2, 0,
+								 InvalidOid, true);
+
+	if (rel1->rd_rel->reltoastrelid || rel2->rd_rel->reltoastrelid)
+	{
+		if (swap_toast_by_content)
+		{
+			if (rel1->rd_rel->reltoastrelid && rel2->rd_rel->reltoastrelid)
+			{
+				gtt_swap_relation_files(rel1->rd_rel->reltoastrelid,
+									rel2->rd_rel->reltoastrelid,
+									target_is_pg_class,
+									swap_toast_by_content,
+									is_internal,
+									frozenXid,
+									cutoffMulti,
+									mapped_tables);
+			}
+			else
+				elog(ERROR, "cannot swap toast files by content when there's only one");
+		}
+		else
+		{
+			ObjectAddress baseobject,
+						toastobject;
+			long		count;
+
+			if (IsSystemRelation(rel1))
+				elog(ERROR, "cannot swap toast files by links for system catalogs");
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel1->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel2->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+
+			/* Register new dependencies */
+			baseobject.classId = RelationRelationId;
+			baseobject.objectSubId = 0;
+			toastobject.classId = RelationRelationId;
+			toastobject.objectSubId = 0;
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r1;
+				toastobject.objectId = rel1->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r2;
+				toastobject.objectId = rel2->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+		}
+	}
+
+	if (swap_toast_by_content &&
+		rel1->rd_rel->relkind == RELKIND_TOASTVALUE &&
+		rel2->rd_rel->relkind == RELKIND_TOASTVALUE)
+	{
+		Oid			toastIndex1,
+					toastIndex2;
+
+		/* Get valid index for each relation */
+		toastIndex1 = toast_get_valid_index(r1,
+											AccessExclusiveLock);
+		toastIndex2 = toast_get_valid_index(r2,
+											AccessExclusiveLock);
+
+		gtt_swap_relation_files(toastIndex1,
+							toastIndex2,
+							target_is_pg_class,
+							swap_toast_by_content,
+							is_internal,
+							InvalidTransactionId,
+							InvalidMultiXactId,
+							mapped_tables);
+	}
+
+	relation_close(rel1, NoLock);
+	relation_close(rel2, NoLock);
+
+	table_close(relRelation, RowExclusiveLock);
+
+	RelationCloseSmgrByOid(r1);
+	RelationCloseSmgrByOid(r2);
+
+	CommandCounterIncrement();
+}
+
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index ac07f75..655184d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -27,6 +27,7 @@
 #include "catalog/dependency.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -1063,7 +1064,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, stmt->filename, stmt->is_program,
@@ -2794,6 +2795,8 @@ CopyFrom(CopyState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	estate->es_result_relations = resultRelInfo;
 	estate->es_num_result_relations = 1;
 	estate->es_result_relation_info = resultRelInfo;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 2baca12..5338db9 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -521,6 +521,7 @@ DefineIndex(Oid relationId,
 	Snapshot	snapshot;
 	int			save_nestlevel = -1;
 	int			i;
+	char		rel_persistence;
 
 	/*
 	 * Some callers need us to run with an empty default_tablespace; this is a
@@ -542,7 +543,9 @@ DefineIndex(Oid relationId,
 	 * is more efficient.  Do this before any use of the concurrent option is
 	 * done.
 	 */
-	if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+	rel_persistence = get_rel_persistence(relationId);
+	if (stmt->concurrent &&
+		!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		concurrent = true;
 	else
 		concurrent = false;
@@ -2460,7 +2463,8 @@ ReindexIndex(RangeVar *indexRelation, int options, bool concurrent)
 	persistence = irel->rd_rel->relpersistence;
 	index_close(irel, NoLock);
 
-	if (concurrent && persistence != RELPERSISTENCE_TEMP)
+	if (concurrent &&
+		!(persistence == RELPERSISTENCE_TEMP || persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		ReindexRelationConcurrently(indOid, options);
 	else
 		reindex_index(indOid, false, persistence,
@@ -2546,6 +2550,7 @@ ReindexTable(RangeVar *relation, int options, bool concurrent)
 {
 	Oid			heapOid;
 	bool		result;
+	char		rel_persistence;
 
 	/*
 	 * The lock level used here should match reindex_relation().
@@ -2560,7 +2565,9 @@ ReindexTable(RangeVar *relation, int options, bool concurrent)
 									   0,
 									   RangeVarCallbackOwnsTable, NULL);
 
-	if (concurrent && get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+	rel_persistence = get_rel_persistence(heapOid);
+	if (concurrent &&
+		!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 	{
 		result = ReindexRelationConcurrently(heapOid, options);
 
@@ -2761,12 +2768,15 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 	foreach(l, relids)
 	{
 		Oid			relid = lfirst_oid(l);
+		char		rel_persistence;
 
 		StartTransactionCommand();
 		/* functions in indexes may want a snapshot set */
 		PushActiveSnapshot(GetTransactionSnapshot());
 
-		if (concurrent && get_rel_persistence(relid) != RELPERSISTENCE_TEMP)
+		rel_persistence = get_rel_persistence(relid);
+		if (concurrent &&
+			!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		{
 			(void) ReindexRelationConcurrently(relid, options);
 			/* ReindexRelationConcurrently() does the verbose output */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..a45863a 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -94,7 +96,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -222,7 +225,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* now initialize the sequence's data */
 	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -327,7 +330,7 @@ ResetSequence(Oid seq_relid)
 	/*
 	 * Insert the modified tuple into the new storage file.
 	 */
-	fill_seq_with_data(seq_rel, tuple);
+	fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -340,17 +343,21 @@ ResetSequence(Oid seq_relid)
  * Initialize a sequence's relation with the specified tuple as content
  */
 static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 	sequence_magic *sm;
 	OffsetNumber offnum;
+	bool lockBuffer = false;
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ReadBuffer(rel, P_NEW);
-	Assert(BufferGetBlockNumber(buf) == 0);
+	if (buf == InvalidBuffer)
+	{
+		buf = ReadBuffer(rel, P_NEW);
+		Assert(BufferGetBlockNumber(buf) == 0);
+		lockBuffer = true;
+	}
 
 	page = BufferGetPage(buf);
 
@@ -360,7 +367,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* Now insert sequence tuple */
 
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+	if (lockBuffer)
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +418,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	END_CRIT_SECTION();
 
-	UnlockReleaseBuffer(buf);
+	if (lockBuffer)
+		UnlockReleaseBuffer(buf);
 }
 
 /*
@@ -451,6 +460,13 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 
 	init_sequence(relid, &elm, &seqrel);
 
+	if (RELATION_IS_GLOBAL_TEMP(seqrel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(seqrel)))
+			elog(ERROR, "cannot alter global temp sequence %s when other backend attached it",
+						RelationGetRelationName(seqrel));
+	}
+
 	rel = table_open(SequenceRelationId, RowExclusiveLock);
 	seqtuple = SearchSysCacheCopy1(SEQRELID,
 								   ObjectIdGetDatum(relid));
@@ -502,7 +518,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * Insert the modified tuple into the new storage file.
 		 */
-		fill_seq_with_data(seqrel, newdatatuple);
+		fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
 	}
 
 	/* process OWNED BY if given */
@@ -611,7 +627,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +952,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1154,6 +1170,13 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1954,3 +1977,46 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+void
+gtt_init_seq(Relation rel)
+{
+	/* Initialize sequence for global temporary tables */
+	Datum		value[SEQ_COL_LASTCOL] = {0};
+	bool		null[SEQ_COL_LASTCOL] = {false};
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 037d457..6eadc05 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -555,6 +556,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 								  Relation partitionTbl);
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static OnCommitAction gtt_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -600,6 +602,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -610,8 +613,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Check consistency of arguments
 	 */
+	/* global temp table same as local temp table */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !(stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+			 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -641,7 +646,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	/* global temp table same as local temp table */
+	if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+		 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -742,6 +749,55 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* global temp table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* check parent table*/
+		if (inheritOids)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			table_close(relation, NoLock);
+		}
+
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temp table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1329,6 +1385,7 @@ RemoveRelations(DropStmt *drop)
 		Oid			relOid;
 		ObjectAddress obj;
 		struct DropRelationCallbackState state;
+		char		rel_persistence;
 
 		/*
 		 * These next few steps are a great deal like relation_openrv, but we
@@ -1362,8 +1419,9 @@ RemoveRelations(DropStmt *drop)
 		 * Decide if concurrent mode needs to be used here or not.  The
 		 * relation persistence cannot be known without its OID.
 		 */
+		rel_persistence = get_rel_persistence(relOid);
 		if (drop->concurrent &&
-			get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+			!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		{
 			Assert(list_length(drop->objects) == 1 &&
 				   drop->removeType == OBJECT_INDEX);
@@ -1818,6 +1876,10 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
 		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 			continue;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			continue;
+
 		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
@@ -3569,6 +3631,14 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			elog(ERROR, "can not alter table %s when other backend attached this global temp table",
+						RelationGetRelationName(rel));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -4846,6 +4916,38 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot rewrite temporary tables of other sessions")));
 
+			if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+			{
+				if (tab->chgPersistence)
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot change global temporary table persistence setting")));
+
+				/* gtt may not attached, create it */
+				if(!gtt_storage_attached(tab->relid))
+				{
+					ResultRelInfo *resultRelInfo;
+					MemoryContext oldcontext;
+					MemoryContext ctx_alter_gtt;
+
+					ctx_alter_gtt = AllocSetContextCreate(CurrentMemoryContext,
+											"gtt alter table", ALLOCSET_DEFAULT_SIZES);
+
+					oldcontext = MemoryContextSwitchTo(ctx_alter_gtt);
+					resultRelInfo = makeNode(ResultRelInfo);
+					InitResultRelInfo(resultRelInfo, OldHeap,
+									1, NULL, 0);
+					if (resultRelInfo->ri_RelationDesc->rd_rel->relhasindex &&
+						resultRelInfo->ri_IndexRelationDescs == NULL)
+						ExecOpenIndices(resultRelInfo, false);
+
+					init_gtt_storage(CMD_UTILITY, resultRelInfo);
+					ExecCloseIndices(resultRelInfo);
+					MemoryContextSwitchTo(oldcontext);
+					MemoryContextDelete(ctx_alter_gtt);
+				}
+			}
+
 			/*
 			 * Select destination tablespace (same as original unless user
 			 * requested a change)
@@ -8183,6 +8285,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -12739,6 +12847,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -12941,6 +13052,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temp table");
+
 	/*
 	 * No work if no change in tablespace.
 	 */
@@ -13315,7 +13429,7 @@ index_copy_data(Relation rel, RelFileNode newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
@@ -14722,7 +14836,9 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
 	 */
 	switch (rel->rd_rel->relpersistence)
 	{
+		/* global temp table same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -17315,3 +17431,36 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
 		table_close(rel, NoLock);
 	}
 }
+
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			bool	res = false;
+			char	*sval = defGetString(def);
+
+			if (!parse_bool(sval, &res))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
+
+			if (res)
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 5a110ed..e35bbeb 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"
@@ -1215,6 +1216,17 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	 /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		is_gtt = true;
+		up_gtt_relstats(relation,
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1228,17 +1240,26 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relpages = (int32) num_pages;
+	else if (pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (is_gtt)
+		relation->rd_rel->reltuples = (float4) num_tuples;
+	else if (pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	else if (pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1283,7 +1304,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1294,7 +1316,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1392,6 +1415,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
@@ -1449,6 +1476,41 @@ 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	safe_age;
+
+		TransactionId oldest_gtt_frozenxid =
+			list_all_session_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		safe_age = oldest_gtt_frozenxid + vacuum_gtt_defer_check_age;
+		if (safe_age < FirstNormalTransactionId)
+			safe_age += FirstNormalTransactionId;
+
+		/*
+		 * We tolerate that the minimum age of gtt is less than
+		 * the minimum age of conventional tables, otherwise it will
+		 * throw warning message.
+		 */
+		if (TransactionIdIsNormal(safe_age) &&
+			TransactionIdPrecedes(safe_age, newFrozenXid))
+		{
+			ereport(WARNING,
+				(errmsg("global temp table oldest relfrozenxid %u is the oldest in the entire db",
+						oldest_gtt_frozenxid),
+				 errdetail("The oldest relfrozenxid in pg_class is %u", newFrozenXid),
+				 errhint("If they differ greatly, please consider cleaning up the data in global temp table.")));
+		}
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid) &&
+			TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+			newFrozenXid = oldest_gtt_frozenxid;
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1776,6 +1838,15 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6e65103..04706ee 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -530,6 +530,11 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 4fdffad..0aa7559 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index fb6ce49..06d9237 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
@@ -545,6 +546,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	init_gtt_storage(mtstate->operation, leaf_part_rri);
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 20a4c47..e9f6dcd 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,6 +42,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -2379,6 +2380,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			ExecOpenIndices(resultRelInfo,
 							node->onConflictAction != ONCONFLICT_NONE);
 
+		init_gtt_storage(operation, resultRelInfo);
+
 		/*
 		 * If this is an UPDATE and a BEFORE UPDATE trigger is present, the
 		 * trigger itself might modify the partition-key values. So arrange
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 255f56b..d94d6f0 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 e664eb1..e03bfa0 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6419,7 +6419,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
+	/* global temp table is same as local temp table */
 	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 2554502..04fb5d4 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -28,6 +28,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temp table */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index eee9c33..1891c53 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2594,6 +2594,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 1219ac8..70e5a93 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3302,17 +3302,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; }
@@ -11638,19 +11632,13 @@ OptTempTableName:
 				}
 			| GLOBAL TEMPORARY opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED opt_table qualified_name
 				{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b875a50..123945b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 75c122f..97213ed 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
@@ -3108,6 +3111,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		cxt.isforeign = false;
 	}
 	cxt.relation = stmt->relation;
+	cxt.relation->relpersistence = RelationGetRelPersistence(rel);
 	cxt.rel = rel;
 	cxt.inhRelations = NIL;
 	cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 7e97ffa..1f2ac58 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2089,6 +2089,11 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* autovacuum skip vacuum global temp table */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2155,7 +2160,9 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		/* autovacuum skip vacuum global temp table */
+		if (classForm->relpersistence == RELPERSISTENCE_TEMP || 
+			classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index f9980cf..406a25c 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -53,6 +54,7 @@
 #include "utils/rel.h"
 #include "utils/resowner_private.h"
 #include "utils/timestamp.h"
+#include "utils/guc.h"
 
 
 /* Note: these two macros only work on shared buffers, not local ones! */
@@ -2819,6 +2821,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d5..7adb969 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -21,6 +21,7 @@
 #include "access/nbtree.h"
 #include "access/subtrans.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, active_gtt_shared_hash_size());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* global temporary table */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 3630006..0c8ef80 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -63,6 +63,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -4086,3 +4087,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 5aa19d3..b3bc455 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 8406644..0d99321 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -1008,6 +1008,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 4fdcb07..58fc8de 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -112,6 +112,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -4731,12 +4732,25 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -4861,15 +4875,27 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6251,6 +6277,7 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	{
 		/* Simple variable --- look to stats for the underlying table */
 		RangeTblEntry *rte = planner_rt_fetch(index->rel->relid, root);
+		char	rel_persistence = get_rel_persistence(rte->relid);
 
 		Assert(rte->rtekind == RTE_RELATION);
 		relid = rte->relid;
@@ -6268,6 +6295,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6279,6 +6313,8 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	}
 	else
 	{
+		char	rel_persistence = get_rel_persistence(index->indexoid);
+
 		/* Expression --- maybe there are stats for the index itself */
 		relid = index->indexoid;
 		colnum = 1;
@@ -6294,6 +6330,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7212,6 +7255,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		/* attempt to lookup stats in relation for this index column */
 		if (attnum != 0)
 		{
+			char	rel_persistence = get_rel_persistence(rte->relid);
+
 			/* Simple variable -- look to stats for the underlying table */
 			if (get_relation_stats_hook &&
 				(*get_relation_stats_hook) (root, rte, attnum, &vardata))
@@ -7224,6 +7269,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7236,6 +7289,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		}
 		else
 		{
+			char	rel_persistence = get_rel_persistence(index->indexoid);
+
 			/*
 			 * Looks like we've found an expression column in the index. Let's
 			 * see if there's any stats for it.
@@ -7255,6 +7310,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index a7d63f1..d8a52cb 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -2939,6 +2940,18 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 9f1f11d..aeb2a78 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -65,6 +65,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1133,6 +1134,24 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1187,6 +1206,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			gtt_fix_index_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -1317,7 +1337,17 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+			if (newrelnode != InvalidOid &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2246,6 +2276,8 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		gtt_fix_index_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3472,6 +3504,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3579,28 +3615,34 @@ void
 RelationSetNewRelfilenode(Relation relation, char persistence)
 {
 	Oid			newrelfilenode;
-	Relation	pg_class;
-	HeapTuple	tuple;
+	Relation	pg_class = NULL;
+	HeapTuple	tuple = NULL;
 	Form_pg_class classform;
 	MultiXactId minmulti = InvalidMultiXactId;
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
+	bool		modify_pg_class = !RELATION_IS_GLOBAL_TEMP(relation);
 
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
 
-	/*
-	 * Get a writable copy of the pg_class tuple for the given relation.
-	 */
-	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+	if (modify_pg_class)
+	{
+		/*
+		 * Get a writable copy of the pg_class tuple for the given relation.
+		 */
+		pg_class = table_open(RelationRelationId, RowExclusiveLock);
 
-	tuple = SearchSysCacheCopy1(RELOID,
-								ObjectIdGetDatum(RelationGetRelid(relation)));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "could not find tuple for relation %u",
-			 RelationGetRelid(relation));
-	classform = (Form_pg_class) GETSTRUCT(tuple);
+		tuple = SearchSysCacheCopy1(RELOID,
+									ObjectIdGetDatum(RelationGetRelid(relation)));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u",
+				 RelationGetRelid(relation));
+		classform = (Form_pg_class) GETSTRUCT(tuple);
+	}
+	else
+		memset(&classform, 0, sizeof(classform));
 
 	/*
 	 * Schedule unlinking of the old storage at transaction commit.
@@ -3626,7 +3668,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;
@@ -3646,6 +3688,15 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 			break;
 	}
 
+	if (!modify_pg_class)
+	{
+		Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+		Assert(RELATION_IS_GLOBAL_TEMP(relation));
+		Assert(!RelationIsMapped(relation));
+		relation->rd_node.relNode = relnode;
+		CacheInvalidateRelcache(relation);
+	}
 	/*
 	 * If we're dealing with a mapped index, pg_class.relfilenode doesn't
 	 * change; instead we have to send the update to the relation mapper.
@@ -3655,7 +3706,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	 * possibly-inaccurate values of relpages etc, but those will be fixed up
 	 * later.
 	 */
-	if (RelationIsMapped(relation))
+	else if (RelationIsMapped(relation))
 	{
 		/* This case is only supported for indexes */
 		Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3701,9 +3752,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 		CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 	}
 
-	heap_freetuple(tuple);
+	if (modify_pg_class)
+	{
+		heap_freetuple(tuple);
 
-	table_close(pg_class, RowExclusiveLock);
+		table_close(pg_class, RowExclusiveLock);
+	}
 
 	/*
 	 * Make the pg_class row change or relation map change visible.  This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 5bdc02f..b52b51d 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -37,6 +37,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "commands/prepare.h"
 #include "commands/trigger.h"
@@ -142,6 +143,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temp table feature.
+ * global temp table define can still storage in catalog
+ * just can not use.
+ * num > 0 means database can management num active global temp table.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2070,6 +2083,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."),
@@ -2577,6 +2599,16 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"vacuum_gtt_defer_check_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("The defer check age of GTT, used to check expired data after vacuum."),
+			NULL
+		},
+		&vacuum_gtt_defer_check_age,
+		10000, 0, 1000000,
+		NULL, NULL, NULL
+	},
+
 	/*
 	 * See also CheckRequiredParameterValues() if this parameter changes
 	 */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 5db4f57..018a9bf 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2424,6 +2424,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
 		dopt->no_unlogged_table_data)
 		return;
 
+	/* Don't dump data in global temp table/sequence */
+	if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		return;
+
 	/* Check that the data is not explicitly excluded */
 	if (simple_oid_list_member(&tabledata_exclude_oids,
 							   tbinfo->dobj.catId.oid))
@@ -15777,6 +15781,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
 		char	   *foreign = "";
+		char	   *table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15830,9 +15835,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);
 
@@ -16226,7 +16237,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
-			 tbinfo->relkind == RELKIND_MATVIEW))
+			 tbinfo->relkind == RELKIND_MATVIEW) &&
+			 tbinfo->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
 		{
 			appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
 			appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
@@ -17131,6 +17143,7 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	PQExpBuffer query = createPQExpBuffer();
 	PQExpBuffer delqry = createPQExpBuffer();
 	char	   *qseqname;
+	bool	   global_temp_seq = false;
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
@@ -17140,9 +17153,11 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
-						  "FROM pg_catalog.pg_sequence "
-						  "WHERE seqrelid = '%u'::oid",
+						  "seqcache, seqcycle, c.relpersistence "
+						  "FROM pg_catalog.pg_sequence s, "
+						  "pg_catalog.pg_class c "
+						  "WHERE seqrelid = '%u'::oid "
+						  "and s.seqrelid = c.oid",
 						  tbinfo->dobj.catId.oid);
 	}
 	else if (fout->remoteVersion >= 80400)
@@ -17187,6 +17202,9 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
 
+	if (fout->remoteVersion >= 130000)
+		global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
 	if (strcmp(seqtype, "smallint") == 0)
@@ -17261,9 +17279,13 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	}
 	else
 	{
-		appendPQExpBuffer(query,
-						  "CREATE SEQUENCE %s\n",
-						  fmtQualifiedDumpable(tbinfo));
+		appendPQExpBuffer(query, "CREATE ");
+
+		if (global_temp_seq)
+			appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+		appendPQExpBuffer(query, "SEQUENCE %s\n",
+								  fmtQualifiedDumpable(tbinfo));
 
 		if (strcmp(seqtype, "bigint") != 0)
 			appendPQExpBuffer(query, "    AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 00aef85..9ad71c8 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -84,7 +84,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -157,7 +157,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 7e524ea..371168f 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -441,22 +443,46 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 	 * output, so we have to copy that system table.  It's easiest to do that
 	 * by treating it as a user table.
 	 */
-	snprintf(query + strlen(query), sizeof(query) - strlen(query),
-			 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
-			 "  SELECT c.oid, 0::oid, 0::oid "
-			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
-			 "         ON c.relnamespace = n.oid "
-			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
-	/* exclude possible orphaned temp tables */
-			 "    ((n.nspname !~ '^pg_temp_' AND "
-			 "      n.nspname !~ '^pg_toast_temp_' AND "
-			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
-			 "                        'binary_upgrade', 'pg_toast') AND "
-			 "      c.oid >= %u::pg_catalog.oid) OR "
-			 "     (n.nspname = 'pg_catalog' AND "
-			 "      relname IN ('pg_largeobject') ))), ",
-			 FirstNormalObjectId);
+	if (skip_gtt)
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude global temp tables */
+				 "    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
+	else
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
 
 	/*
 	 * Add a CTE that collects OIDs of toast tables belonging to the tables
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 70194eb..8dd9e66 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -411,7 +411,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -642,7 +642,9 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -653,7 +655,9 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 8b90cef..d155205 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -389,7 +389,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index f05e914..19adab9 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3724,7 +3724,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 0e7a373..a47de01 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1023,6 +1023,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2390,6 +2392,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2598,6 +2603,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 78b33b2..4be89f6 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -168,6 +168,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad..c8236a1 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5558,6 +5558,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4388',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4389',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4390',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4391',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 30c38e0..7ff2408 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,7 +22,7 @@
 /* GUC variables */
 extern int	wal_skip_threshold;
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationPreTruncate(Relation rel);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..902375d
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern int		vacuum_gtt_defer_check_age;
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Relation relation,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void gtt_force_enable_index(Relation index);
+extern void gtt_fix_index_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+extern void gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index e2638ab..89a5ce4 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 3f88683..f870e9a 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 8fda8e4..172eae9 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_TBM,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_SXACT,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index ae4f573..69cce92 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -117,6 +117,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId session_gtt_frozenxid;	/* session level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 2819282..363fc33 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -282,6 +282,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 0b5957b..0720a4c 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	struct SMgrRelationData *rd_smgr;	/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -305,6 +305,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -570,11 +571,13 @@ typedef struct ViewOptions
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -582,6 +585,7 @@ typedef struct ViewOptions
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -594,6 +598,14 @@ typedef struct ViewOptions
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is either local temp relation of this session
+ * 		or global temp relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -637,6 +649,17 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..ca2d135
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,14 @@
+reset search_path;
+select pg_sleep(5);
+ pg_sleep 
+----------
+ 
+(1 row)
+
+drop schema gtt cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
+drop cascades to table gtt.gtt_with_seq
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..d6a675a
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,383 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+ n 
+---
+ 9
+(1 row)
+
+commit;
+-- 0 row
+select * from gtt6;
+ n 
+---
+(0 rows)
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temp table
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  could not create global temporary table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR:  insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count 
+-------
+     1
+(1 row)
+
+select count(*) from orders;
+ count 
+-------
+     0
+(1 row)
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  2 |  3
+(2 rows)
+
+commit;
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+select * from gtt_s_2 order by c1;
+ c1 | c2 
+----+----
+  1 |  2
+  2 |  4
+(2 rows)
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 1)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 300)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 33 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test_new
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.gtt6
+drop cascades to table gtt_function.foo
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to sequence gtt_function.seq_1
+drop cascades to table gtt_function.gtt_s_1
+drop cascades to table gtt_function.gtt_s_2
+drop cascades to table gtt_function.gt1
+drop cascades to table gtt_function.gtt_test1
+drop cascades to table gtt_function.gtt_test2
+drop cascades to table gtt_function.gtt_test3
+drop cascades to table gtt_function.gtt_test4
+drop cascades to table gtt_function.gtt_test5
+drop cascades to table gtt_function.gtt_test6
+drop cascades to table gtt_function.gtt_test7
+drop cascades to table gtt_function.gtt_test8
+drop cascades to table gtt_function.gtt_test9
+drop cascades to table gtt_function.gtt_test10
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..0646aae
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,90 @@
+set search_path=gtt,sys;
+select nextval('gtt_with_seq_c2_seq');
+ nextval 
+---------
+       1
+(1 row)
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..2f084be
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,343 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+rollback;
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+commit;
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          114688 |                 614400
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |            49152 |         49152 |                  49152
+(2 rows)
+
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2000
+(1 row)
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+ c1 | c2 
+----+----
+  1 |  1
+(1 row)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..8c0c376
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,10 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..4420fdb
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,80 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+ gtt_stats  | gtt_pkey  |        1 |         0 |             0
+(2 rows)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+reindex table gtt;
+reindex index gtt_pkey;
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+ gtt_stats  | gtt_pkey  |       30 |     10000 |             0
+(2 rows)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index ac31840..55b221e 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1349,6 +1349,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 95f1925..76b2374 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..2c8e586
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,8 @@
+
+
+reset search_path;
+
+select pg_sleep(5);
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..81f0bfc
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,253 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+commit;
+-- 0 row
+select * from gtt6;
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+commit;
+select * from gtt_s_1 order by c1;
+select * from gtt_s_2 order by c1;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d05745e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,44 @@
+
+
+set search_path=gtt,sys;
+
+select nextval('gtt_with_seq_c2_seq');
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..39cca5e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,154 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+rollback;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+commit;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+select count(*) from gtt_t_kenyon;
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..dbe84d1
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,19 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..d61b0ff
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,46 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+reindex table gtt;
+
+reindex index gtt_pkey;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#248曾文旌
wenjing.zwj@alibaba-inc.com
In reply to: Prabhat Sahu (#246)
2 attachment(s)
Re: [Proposal] Global temporary tables

2020年4月17日 下午8:59,Prabhat Sahu <prabhat.sahu@enterprisedb.com> 写道:

Hi Wenjing,

Please check below scenario, we are getting a server crash with "ALTER TABLE" add column with default value as sequence:

-- Create gtt, exit and re-connect the psql prompt, create sequence, alter table add a column with sequence.
postgres=# create global temporary table gtt1 (c1 int);
CREATE TABLE
postgres=# \q
[edb@localhost bin]$ ./psql postgres
psql (13devel)
Type "help" for help.

postgres=# create sequence seq;
CREATE SEQUENCE
postgres=# alter table gtt1 add c2 int default nextval('seq');
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.
!?> \q

Please check my new patch.

Wenjing

Show quoted text

-- Stack trace:
[edb@localhost bin]$ gdb -q -c data/core.70358 postgres
Reading symbols from /home/edb/PG/PGsrcNew/postgresql/inst/bin/postgres...done.
[New LWP 70358]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Core was generated by `postgres: edb postgres [local] ALTER TABLE '.
Program terminated with signal 6, Aborted.
#0 0x00007f150223b337 in raise () from /lib64/libc.so.6
Missing separate debuginfos, use: debuginfo-install glibc-2.17-292.el7.x86_64 keyutils-libs-1.5.8-3.el7.x86_64 krb5-libs-1.15.1-37.el7_7.2.x86_64 libcom_err-1.42.9-16.el7.x86_64 libgcc-4.8.5-39.el7.x86_64 libselinux-2.5-14.1.el7.x86_64 openssl-libs-1.0.2k-19.el7.x86_64 pcre-8.32-17.el7.x86_64 zlib-1.2.7-18.el7.x86_64
(gdb) bt
#0 0x00007f150223b337 in raise () from /lib64/libc.so.6
#1 0x00007f150223ca28 in abort () from /lib64/libc.so.6
#2 0x0000000000ab2cdd in ExceptionalCondition (conditionName=0xc03ab8 "OidIsValid(relfilenode1) && OidIsValid(relfilenode2)",
errorType=0xc0371f "FailedAssertion", fileName=0xc03492 "cluster.c", lineNumber=1637) at assert.c:67
#3 0x000000000065e200 in gtt_swap_relation_files (r1=16384, r2=16390, target_is_pg_class=false, swap_toast_by_content=false, is_internal=true,
frozenXid=490, cutoffMulti=1, mapped_tables=0x7ffd841f7ee0) at cluster.c:1637
#4 0x000000000065dcd9 in finish_heap_swap (OIDOldHeap=16384, OIDNewHeap=16390, is_system_catalog=false, swap_toast_by_content=false,
check_constraints=true, is_internal=true, frozenXid=490, cutoffMulti=1, newrelpersistence=103 'g') at cluster.c:1395
#5 0x00000000006bca18 in ATRewriteTables (parsetree=0x1deab80, wqueue=0x7ffd841f80c8, lockmode=8, context=0x7ffd841f8260) at tablecmds.c:4991
#6 0x00000000006ba890 in ATController (parsetree=0x1deab80, rel=0x7f150378f330, cmds=0x1deab28, recurse=true, lockmode=8, context=0x7ffd841f8260)
at tablecmds.c:3991
#7 0x00000000006ba4f8 in AlterTable (stmt=0x1deab80, lockmode=8, context=0x7ffd841f8260) at tablecmds.c:3644
#8 0x000000000093b62a in ProcessUtilitySlow (pstate=0x1e0d6d0, pstmt=0x1deac48,
queryString=0x1de9b30 "alter table gtt1 add c2 int default nextval('seq');", context=PROCESS_UTILITY_TOPLEVEL, params=0x0, queryEnv=0x0, dest=0x1deaf28,
qc=0x7ffd841f8830) at utility.c:1267
#9 0x000000000093b141 in standard_ProcessUtility (pstmt=0x1deac48, queryString=0x1de9b30 "alter table gtt1 add c2 int default nextval('seq');",
context=PROCESS_UTILITY_TOPLEVEL, params=0x0, queryEnv=0x0, dest=0x1deaf28, qc=0x7ffd841f8830) at utility.c:1067
#10 0x000000000093a22b in ProcessUtility (pstmt=0x1deac48, queryString=0x1de9b30 "alter table gtt1 add c2 int default nextval('seq');",
context=PROCESS_UTILITY_TOPLEVEL, params=0x0, queryEnv=0x0, dest=0x1deaf28, qc=0x7ffd841f8830) at utility.c:522
#11 0x000000000093909d in PortalRunUtility (portal=0x1e4fba0, pstmt=0x1deac48, isTopLevel=true, setHoldSnapshot=false, dest=0x1deaf28, qc=0x7ffd841f8830)
at pquery.c:1157
#12 0x00000000009392b3 in PortalRunMulti (portal=0x1e4fba0, isTopLevel=true, setHoldSnapshot=false, dest=0x1deaf28, altdest=0x1deaf28, qc=0x7ffd841f8830)
at pquery.c:1303
#13 0x00000000009387d1 in PortalRun (portal=0x1e4fba0, count=9223372036854775807, isTopLevel=true, run_once=true, dest=0x1deaf28, altdest=0x1deaf28,
qc=0x7ffd841f8830) at pquery.c:779
#14 0x000000000093298b in exec_simple_query (query_string=0x1de9b30 "alter table gtt1 add c2 int default nextval('seq');") at postgres.c:1239
#15 0x0000000000936997 in PostgresMain (argc=1, argv=0x1e13b80, dbname=0x1e13a78 "postgres", username=0x1e13a58 "edb") at postgres.c:4315
#16 0x00000000008868b3 in BackendRun (port=0x1e0bb50) at postmaster.c:4510
#17 0x00000000008860a8 in BackendStartup (port=0x1e0bb50) at postmaster.c:4202
#18 0x0000000000882626 in ServerLoop () at postmaster.c:1727
#19 0x0000000000881efd in PostmasterMain (argc=3, argv=0x1de4460) at postmaster.c:1400
#20 0x0000000000789288 in main (argc=3, argv=0x1de4460) at main.c:210
(gdb)

--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

Attachments:

global_temporary_table_v28-pg13.patchapplication/octet-stream; name=global_temporary_table_v28-pg13.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 8ccc228..b061de9 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -168,6 +168,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * For global temp table only
+	 * use ShareUpdateExclusiveLock for ensure safety
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temp table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},	
 	/* list terminator */
 	{{NULL}}
 };
@@ -1817,6 +1830,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1961,13 +1976,18 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	/*
 	 * There are no options for partitioned tables yet, but this is able to do
 	 * some validation.
 	 */
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index 765329b..4761fdc 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1011,7 +1011,9 @@ gistproperty(Oid index_oid, int attno,
 XLogRecPtr
 gistGetFakeLSN(Relation rel)
 {
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	/* global temp is same as local temp table */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 3ec6d52..1222594 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -150,7 +150,9 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	/* global temp table is same as local temp table */
+	if (!(index->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		index->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 56b3562..d3443ec 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -587,7 +587,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -640,7 +640,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 3c18db2..b08a1fc 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -62,6 +62,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -431,9 +432,9 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
 	Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
-	/* not every AM requires these to be valid, but heap does */
-	Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
-	Assert(MultiXactIdIsValid(onerel->rd_rel->relminmxid));
+	/* not every AM requires these to be valid, but regular heap does */
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ MultiXactIdIsValid(onerel->rd_rel->relminmxid));
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 39b8f17..abb76dc 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -601,6 +602,10 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 11e3273..32694c9 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6422,6 +6422,10 @@ StartupXLOG(void)
 	else
 		recoveryTargetTLI = ControlFile->checkPointCopy.ThisTimeLineID;
 
+	/* clean temp relation files */
+	if (max_active_gtt > 0)
+		RemovePgTempFiles();
+
 	/*
 	 * Check for signal files, and if so set up state for offline recovery
 	 */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 9499bb3..ae47364 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -43,6 +43,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 7d6acae..3be8d63 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -393,7 +393,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/* global temp table is same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 632c058..01cf990 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -427,7 +429,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -959,6 +961,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -997,8 +1000,18 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/* global temp table not remember transaction info in catalog */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1360,6 +1373,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1940,6 +1954,14 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/* We allow to drop global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			elog(ERROR, "can not drop relation %s when other backend attached this global temp table",
+						RelationGetRelationName(rel));
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3167,7 +3189,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3179,7 +3201,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3225,8 +3247,13 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		/* truncate global temp table only need RowExclusiveLock */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3259,6 +3286,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3267,23 +3295,37 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (!gtt_storage_attached(RelationGetRelid(rel)))
+			return;
+
+		/*
+		 * Truncate global temp table only need RowExclusiveLock
+		 */
+		lockmode = RowExclusiveLock;
+	}
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(rel, 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index bd7ec92..747cda0 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -53,6 +53,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -881,6 +882,19 @@ index_create(Relation heapRelation,
 						indexRelationName, RelationGetRelationName(heapRelation))));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temp table use concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot reindex global temporary tables concurrently")));
+
+		/* if global temp table not init storage, then skip build index */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -2041,7 +2055,8 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 * lock (see comments in RemoveRelations), and a non-concurrent DROP is
 	 * more efficient.
 	 */
-	Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+	Assert(!(get_rel_persistence(indexId) == RELPERSISTENCE_TEMP ||
+			 get_rel_persistence(indexId) == RELPERSISTENCE_GLOBAL_TEMP) ||
 		   (!concurrent && !concurrent_lock_mode));
 
 	/*
@@ -2073,6 +2088,14 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/* We allow to drop index on global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+			elog(ERROR, "can not drop index %s when other backend attached this global temp table.",
+						RelationGetRelationName(userHeapRelation));
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2681,6 +2704,11 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	/* update index stats into localhash and rel_rd_rel for global temp table */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		is_gtt = true;
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2766,21 +2794,35 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
+		if (is_gtt)
+			rel->rd_rel->relpages = (int32) relpages;
+		else if (rd_rel->relpages != (int32) relpages)
 		{
 			rd_rel->relpages = (int32) relpages;
 			dirty = true;
 		}
-		if (rd_rel->reltuples != (float4) reltuples)
+
+		if (is_gtt)
+			rel->rd_rel->reltuples = (float4) reltuples;
+		else if (rd_rel->reltuples != (float4) reltuples)
 		{
 			rd_rel->reltuples = (float4) reltuples;
 			dirty = true;
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+
+		if (is_gtt)
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+		else if (rd_rel->relallvisible != (int32) relallvisible)
 		{
 			rd_rel->relallvisible = (int32) relallvisible;
 			dirty = true;
 		}
+
+		if (is_gtt)
+		{
+			up_gtt_relstats(rel, relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
+		}
 	}
 
 	/*
@@ -2894,6 +2936,15 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, index, val);
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			gtt_force_enable_index(indexRelation);
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3442,6 +3493,10 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	PGRUsage	ru0;
 	bool		progress = (options & REINDEXOPT_REPORT_PROGRESS) != 0;
 
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		!gtt_storage_attached(indexId))
+		return;
+
 	pg_rusage_init(&ru0);
 
 	/*
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 2ec2301..1b6061d 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -655,6 +655,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		/* global temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp relations in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index d713d5c..afcf004 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,6 +27,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -61,6 +62,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			relOid;			/* InvalidOid if not a global temp rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -115,7 +117,7 @@ AddPendingSync(const RelFileNode *rnode)
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -127,6 +129,8 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		/* global temp table use same storage strategy as local temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -154,6 +158,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->relOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
@@ -165,6 +170,13 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		AddPendingSync(&rnode);
 	}
 
+	/* remember global temp table storage info to localhash */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel)
+	{
+		pending->relOid = RelationGetRelid(rel);
+		remember_gtt_storage_info(rnode, rel);
+	}
+
 	return srel;
 }
 
@@ -201,11 +213,15 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->relOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->relOid = RelationGetRelid(rel);
+
 	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
@@ -602,6 +618,7 @@ smgrDoPendingDeletes(bool isCommit)
 				i = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -631,14 +648,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->relOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -651,9 +672,18 @@ smgrDoPendingDeletes(bool isCommit)
 		smgrdounlinkall(srels, nrels, false);
 
 		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* clean global temp table flags when transaction commit or rollback */
+			if (SmgrIsTemp(srels[i]) &&
+				reloids[i] != InvalidOid &&
+				gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..451cf82
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1491 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  code to create and destroy physical storage for global temparary table
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_relstats_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+int		vacuum_gtt_defer_check_age = 0;
+
+typedef struct gtt_ctl_data
+{
+	LWLock			lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+	Oid			relfilenode;
+	Oid			spcnode;
+
+	/* pg_class stat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+
+	/* pg_statistic */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+
+	Oid			oldrelid;			/* remember the source of relid, before the switch relfilenode. */
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_reset_statistics(gtt_local_hash_entry *entry);
+static void gtt_free_statistics(gtt_local_hash_entry *entry);
+static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok);
+static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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();
+	}
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(gtt_fnode);
+	info.entrysize = action_gtt_shared_hash_entry_size();
+	active_gtt_shared_hash =
+		ShmemInitHash("active gtt shared hash",
+						gtt_shared_ctl->max_entry,
+						gtt_shared_ctl->max_entry,
+						&info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			found;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *)&(fnode), 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_global_temporary_table.")));
+	}
+
+	if (found == false)
+	{
+		int			wordnum;
+
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode				fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	if (!skiplock)
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		if (!skiplock)
+			LWLockRelease(&gtt_shared_ctl->lock);
+
+		if (isCommit)
+			elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+	bms_del_member(entry->map, MyBackendId);
+
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+
+	if (!skiplock)
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+	return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	Bitmapset	*map_copy = NULL;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool		in_use = false;
+	int			num_use = 0;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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;
+	gtt_relfilenode			*new_node = NULL;
+	Oid						relid = RelationGetRelid(rel);
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		/* First time through: initialize the hash table */
+		HASHCTL		ctl;
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_relstats_context =
+			AllocSetContextCreate(CacheMemoryContext,
+							"gtt relstats context",
+							ALLOCSET_DEFAULT_SIZES);
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+	entry = gtt_search_by_relid(relid, true);
+	if (!entry)
+	{
+		bool		found = false;
+		int 		natts = 0;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash,
+						(void *) &relid, HASH_ENTER, &found);
+
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+		entry->natts = 0;
+		entry->attnum = NULL;
+		entry->att_stat_tups = NULL;
+		entry->oldrelid = InvalidOid;
+
+		natts = RelationGetNumberOfAttributes(rel);
+		entry->attnum = palloc0(sizeof(int) * natts);
+		entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+		entry->natts = natts;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			gtt_storage_checkin(relid);
+		}
+	}
+
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* only heap contain transaction information */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	gtt_reset_statistics(entry);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	gtt_relfilenode *d_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+		return;
+	}
+
+	d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+		else if (entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+			gtt_relfilenode *gttnode2 = NULL;
+
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+			Assert(gttnode2->relfilenode == rnode.relNode);
+			Assert(list_length(entry->relfilenode_list) == 1);
+			/* rollback switch relfilenode */
+			gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+									   entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+									   false);
+			/* clean up footprint */
+			entry2->oldrelid = InvalidOid;
+			d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+			Assert(d_rnode);
+		}
+		else
+		{
+			if (entry->relfilenode_list == NIL)
+			{
+				if (entry->relkind == RELKIND_RELATION ||
+					entry->relkind == RELKIND_SEQUENCE)
+					gtt_storage_checkout(relid, false, isCommit);
+
+				gtt_free_statistics(entry);
+				hash_search(gtt_storage_local_hash,
+						(void *) &(relid), HASH_REMOVE, NULL);
+			}
+
+			return;
+		}
+	}
+
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	pfree(d_rnode);
+	if (entry->relfilenode_list == NIL)
+	{
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(relid, false, isCommit);
+
+		if (isCommit && entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			/* clean up footprint */
+			entry2->oldrelid = InvalidOid;
+		}
+
+		gtt_free_statistics(entry);
+		hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_REMOVE, NULL);
+	}
+	else
+		gtt_reset_statistics(entry);
+
+	return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool found = false;
+	gtt_local_hash_entry *entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry)
+		found = true;
+
+	return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS				status;
+	gtt_local_hash_entry		*entry;
+	SMgrRelation	*srels = NULL;
+	Oid				*relids = NULL;
+	char			*relkinds = NULL;
+	int			nrels = 0,
+				nfiles = 0,
+				maxrels = 0,
+				maxfiles = 0,
+				i = 0;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel;
+			RelFileNode 	rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel = smgropen(rnode, MyBackendId);
+
+			if (maxfiles == 0)
+			{
+				maxfiles = 32;
+				srels = palloc(sizeof(SMgrRelation) * maxfiles);
+			}
+			else if (maxfiles <= nfiles)
+			{
+				maxfiles *= 2;
+				srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+			}
+
+			srels[nfiles++] = srel;
+		}
+
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			relids  = palloc(sizeof(Oid) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			relids  = repalloc(relids , sizeof(Oid) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		relkinds[nrels] = entry->relkind;
+		relids[nrels] = entry->relid;
+		nrels++;
+	}
+
+	if (nfiles > 0)
+	{
+		smgrdounlinkall(srels, nfiles, false);
+		for (i = 0; i < nfiles; i++)
+			smgrclose(srels[i]);
+
+		pfree(srels);
+	}
+
+	if (nrels)
+	{
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+		for (i = 0; i < nrels; i++)
+		{
+			if (relkinds[i] == RELKIND_RELATION ||
+				relkinds[i] == RELKIND_SEQUENCE)
+				gtt_storage_checkout(relids[i], true, false);
+		}
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+		pfree(relids);
+		pfree(relkinds);
+	}
+
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+	return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible) 
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+					BlockNumber num_pages,
+					double num_tuples,
+					BlockNumber num_all_visible_pages,
+					TransactionId relfrozenxid,
+					TransactionId relminmxid)
+{
+	Oid		relid = RelationGetRelid(relation);
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (num_pages >= 0 &&
+		gtt_rnode->relpages != (int32)num_pages)
+		gtt_rnode->relpages = (int32)num_pages;
+
+	if (num_tuples >= 0 &&
+		gtt_rnode->reltuples != (float4)num_tuples)
+		gtt_rnode->reltuples = (float4)num_tuples;
+
+	/* only heap contain transaction information and relallvisible */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (gtt_rnode->relallvisible >= 0 &&
+			gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+		{
+			gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+		}
+
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNewTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible) 
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	MemoryContext			oldcontext;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return;
+
+	if (entry->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	Assert(entry->relid == reloid);
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == 0)
+		{
+			entry->attnum[i] = attnum;
+			break;
+		}
+		else if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < entry->natts);
+	Assert(entry->att_stat_tups[i] == NULL);
+	entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return NULL;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			return entry->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int			i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	HeapTuple	tuple;
+	Relation	rel = NULL;
+	int		attnum = PG_GETARG_INT32(1);
+	Oid		reloid = PG_GETARG_OID(0);
+	char		rel_persistence;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	  tupdesc;
+	MemoryContext oldcontext;
+	Tuplestorestate *tupstore;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+
+	relation_close(rel, NoLock);
+	relation_close(pg_tatistic, AccessShareLock);
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate	*tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Oid			relnode = 0;
+	Relation	rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[6];
+		bool	isnull[6];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(reloid);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(RelationGetRelid(index)));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid relOid = index->rd_index->indrelid;
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	if (gtt_storage_attached(relOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int			i;
+	Oid			toastrelid;
+
+	if (!(operation == CMD_UTILITY || operation == CMD_INSERT))
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+
+		index_build(relation, index, info, true, false);
+	}
+
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid 		indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo 	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+static void
+gtt_reset_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+
+		entry->attnum[i] = 0;
+	}
+
+	return;
+}
+
+static void
+gtt_free_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (entry->attnum)
+		pfree(entry->attnum);
+
+	if (entry->att_stat_tups)
+		pfree(entry->att_stat_tups);
+
+	return;
+}
+
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
+void
+gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint)
+{
+	gtt_local_hash_entry	*entry1;
+	gtt_local_hash_entry	*entry2;
+	gtt_relfilenode			*gtt_rnode1 = NULL;
+	gtt_relfilenode			*gtt_rnode2 = NULL;
+	MemoryContext			oldcontext;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry1 = gtt_search_by_relid(rel1, false);
+	gtt_rnode1 = gtt_search_relfilenode(entry1, relfilenode1, false);
+
+	entry2 = gtt_search_by_relid(rel2, false);
+	gtt_rnode2 = gtt_search_relfilenode(entry2, relfilenode2, false);
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	entry1->relfilenode_list = list_delete_ptr(entry1->relfilenode_list, gtt_rnode1);
+	entry2->relfilenode_list = lappend(entry2->relfilenode_list, gtt_rnode1);
+
+	entry2->relfilenode_list = list_delete_ptr(entry2->relfilenode_list, gtt_rnode2);
+	entry1->relfilenode_list = lappend(entry1->relfilenode_list, gtt_rnode2);
+	MemoryContextSwitchTo(oldcontext);
+
+	if (footprint)
+	{
+		entry1->oldrelid = rel2;
+		entry2->oldrelid = rel1;
+	}
+
+	return;
+}
+
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok)
+{
+	gtt_relfilenode			*rnode = NULL;
+	ListCell				*lc;
+
+	Assert(entry);
+
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == relfilenode)
+		{
+			rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (!missing_ok && rnode == NULL)
+		elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+	return rnode;
+}
+
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry == NULL && !missing_ok)
+		elog(ERROR, "relid %u not found in local hash", relid);
+
+	return entry;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index d406ea8..88be041 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM 
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 924ef37..db12eef 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -184,6 +185,13 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -586,14 +594,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 		 * pg_statistic for columns we didn't process, we leave them alone.)
 		 */
 		update_attstats(RelationGetRelid(onerel), inh,
-						attr_cnt, vacattrstats);
+						attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
 
 		for (ind = 0; ind < nindexes; ind++)
 		{
 			AnlIndexData *thisdata = &indexdata[ind];
 
 			update_attstats(RelationGetRelid(Irel[ind]), false,
-							thisdata->attr_cnt, thisdata->vacattrstats);
+							thisdata->attr_cnt, thisdata->vacattrstats,
+							RelationGetRelPersistence(Irel[ind]));
 		}
 
 		/*
@@ -1456,7 +1465,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
  *		by taking a self-exclusive lock on the relation in analyze_rel().
  */
 static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
 {
 	Relation	sd;
 	int			attno;
@@ -1558,31 +1567,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/* Update column statistic to localhash, not catalog */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 04d12a7..42c82c8 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/toasting.h"
 #include "commands/cluster.h"
 #include "commands/progress.h"
@@ -72,6 +73,12 @@ static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
 							bool verbose, bool *pSwapToastByContent,
 							TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
 static List *get_tables_to_cluster(MemoryContext cluster_context);
+static void gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables);
 
 
 /*---------------------------------------------------------------------------
@@ -366,6 +373,14 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap) &&
+		!gtt_storage_attached(RelationGetRelid(OldHeap)))
+	{
+		relation_close(OldHeap, AccessExclusiveLock);
+		pgstat_progress_end_command();
+		return;
+	}
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
@@ -750,6 +765,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	BlockNumber num_pages;
 	int			elevel = verbose ? INFO : DEBUG2;
 	PGRUsage	ru0;
+	bool		is_gtt = false;
+	uint32		gtt_relfrozenxid = 0;
+	uint32		gtt_relminmxid = 0;
 
 	pg_rusage_init(&ru0);
 
@@ -763,6 +781,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	else
 		OldIndex = NULL;
 
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		is_gtt = true;
+
 	/*
 	 * Their tuple descriptors should be exactly alike, but here we only need
 	 * assume that they have the same number of columns.
@@ -830,20 +851,37 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 						  &OldestXmin, &FreezeXid, NULL, &MultiXactCutoff,
 						  NULL);
 
-	/*
-	 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
-	 * backwards, so take the max.
-	 */
-	if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
-		TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
-		FreezeXid = OldHeap->rd_rel->relfrozenxid;
+	if (is_gtt)
+	{
+		get_gtt_relstats(OIDOldHeap,
+					NULL, NULL, NULL,
+					&gtt_relfrozenxid, &gtt_relminmxid);
 
-	/*
-	 * MultiXactCutoff, similarly, shouldn't go backwards either.
-	 */
-	if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
-		MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
-		MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+		if (TransactionIdIsValid(gtt_relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, gtt_relfrozenxid))
+			FreezeXid = gtt_relfrozenxid;
+
+		if (MultiXactIdIsValid(gtt_relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, gtt_relminmxid))
+			MultiXactCutoff = gtt_relminmxid;
+	}
+	else
+	{
+		/*
+		 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
+		 * backwards, so take the max.
+		 */
+		if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
+			FreezeXid = OldHeap->rd_rel->relfrozenxid;
+
+		/*
+		 * MultiXactCutoff, similarly, shouldn't go backwards either.
+		 */
+		if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
+			MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+	}
 
 	/*
 	 * Decide whether to use an indexscan or seqscan-and-optional-sort to scan
@@ -911,6 +949,12 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	table_close(OldHeap, NoLock);
 	table_close(NewHeap, NoLock);
 
+	if (is_gtt)
+	{
+		CommandCounterIncrement();
+		return;
+	}
+
 	/* Update pg_class to reflect the correct values of pages and tuples. */
 	relRelation = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1346,10 +1390,21 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 	 * Swap the contents of the heap relations (including any toast tables).
 	 * Also set old heap's relfrozenxid to frozenXid.
 	 */
-	swap_relation_files(OIDOldHeap, OIDNewHeap,
+	if (newrelpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		Assert(!is_system_catalog);
+		gtt_swap_relation_files(OIDOldHeap, OIDNewHeap,
+								(OIDOldHeap == RelationRelationId),
+								swap_toast_by_content, is_internal,
+								frozenXid, cutoffMulti, mapped_tables);
+	}
+	else
+	{
+		swap_relation_files(OIDOldHeap, OIDNewHeap,
 						(OIDOldHeap == RelationRelationId),
 						swap_toast_by_content, is_internal,
 						frozenXid, cutoffMulti, mapped_tables);
+	}
 
 	/*
 	 * If it's a system catalog, queue a sinval message to flush all catcaches
@@ -1557,3 +1612,141 @@ get_tables_to_cluster(MemoryContext cluster_context)
 
 	return rvs;
 }
+
+static void
+gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables)
+{
+	Relation	relRelation;
+	Oid			relfilenode1,
+				relfilenode2;
+	Relation	rel1;
+	Relation	rel2;
+
+	relRelation = table_open(RelationRelationId, RowExclusiveLock);
+
+	rel1 = relation_open(r1, AccessExclusiveLock);
+	rel2 = relation_open(r2, AccessExclusiveLock);
+
+	relfilenode1 = gtt_fetch_current_relfilenode(r1);
+	relfilenode2 = gtt_fetch_current_relfilenode(r2);
+
+	Assert(OidIsValid(relfilenode1) && OidIsValid(relfilenode2));
+	gtt_switch_rel_relfilenode(r1, relfilenode1, r2, relfilenode2, true);
+
+	CacheInvalidateRelcache(rel1);
+	CacheInvalidateRelcache(rel2);
+
+	InvokeObjectPostAlterHookArg(RelationRelationId, r1, 0,
+								 InvalidOid, is_internal);
+	InvokeObjectPostAlterHookArg(RelationRelationId, r2, 0,
+								 InvalidOid, true);
+
+	if (rel1->rd_rel->reltoastrelid || rel2->rd_rel->reltoastrelid)
+	{
+		if (swap_toast_by_content)
+		{
+			if (rel1->rd_rel->reltoastrelid && rel2->rd_rel->reltoastrelid)
+			{
+				gtt_swap_relation_files(rel1->rd_rel->reltoastrelid,
+									rel2->rd_rel->reltoastrelid,
+									target_is_pg_class,
+									swap_toast_by_content,
+									is_internal,
+									frozenXid,
+									cutoffMulti,
+									mapped_tables);
+			}
+			else
+				elog(ERROR, "cannot swap toast files by content when there's only one");
+		}
+		else
+		{
+			ObjectAddress baseobject,
+						toastobject;
+			long		count;
+
+			if (IsSystemRelation(rel1))
+				elog(ERROR, "cannot swap toast files by links for system catalogs");
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel1->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel2->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+
+			/* Register new dependencies */
+			baseobject.classId = RelationRelationId;
+			baseobject.objectSubId = 0;
+			toastobject.classId = RelationRelationId;
+			toastobject.objectSubId = 0;
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r1;
+				toastobject.objectId = rel1->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r2;
+				toastobject.objectId = rel2->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+		}
+	}
+
+	if (swap_toast_by_content &&
+		rel1->rd_rel->relkind == RELKIND_TOASTVALUE &&
+		rel2->rd_rel->relkind == RELKIND_TOASTVALUE)
+	{
+		Oid			toastIndex1,
+					toastIndex2;
+
+		/* Get valid index for each relation */
+		toastIndex1 = toast_get_valid_index(r1,
+											AccessExclusiveLock);
+		toastIndex2 = toast_get_valid_index(r2,
+											AccessExclusiveLock);
+
+		gtt_swap_relation_files(toastIndex1,
+							toastIndex2,
+							target_is_pg_class,
+							swap_toast_by_content,
+							is_internal,
+							InvalidTransactionId,
+							InvalidMultiXactId,
+							mapped_tables);
+	}
+
+	relation_close(rel1, NoLock);
+	relation_close(rel2, NoLock);
+
+	table_close(relRelation, RowExclusiveLock);
+
+	RelationCloseSmgrByOid(r1);
+	RelationCloseSmgrByOid(r2);
+
+	CommandCounterIncrement();
+}
+
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index ac07f75..655184d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -27,6 +27,7 @@
 #include "catalog/dependency.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -1063,7 +1064,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, stmt->filename, stmt->is_program,
@@ -2794,6 +2795,8 @@ CopyFrom(CopyState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	estate->es_result_relations = resultRelInfo;
 	estate->es_num_result_relations = 1;
 	estate->es_result_relation_info = resultRelInfo;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 2baca12..5338db9 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -521,6 +521,7 @@ DefineIndex(Oid relationId,
 	Snapshot	snapshot;
 	int			save_nestlevel = -1;
 	int			i;
+	char		rel_persistence;
 
 	/*
 	 * Some callers need us to run with an empty default_tablespace; this is a
@@ -542,7 +543,9 @@ DefineIndex(Oid relationId,
 	 * is more efficient.  Do this before any use of the concurrent option is
 	 * done.
 	 */
-	if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+	rel_persistence = get_rel_persistence(relationId);
+	if (stmt->concurrent &&
+		!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		concurrent = true;
 	else
 		concurrent = false;
@@ -2460,7 +2463,8 @@ ReindexIndex(RangeVar *indexRelation, int options, bool concurrent)
 	persistence = irel->rd_rel->relpersistence;
 	index_close(irel, NoLock);
 
-	if (concurrent && persistence != RELPERSISTENCE_TEMP)
+	if (concurrent &&
+		!(persistence == RELPERSISTENCE_TEMP || persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		ReindexRelationConcurrently(indOid, options);
 	else
 		reindex_index(indOid, false, persistence,
@@ -2546,6 +2550,7 @@ ReindexTable(RangeVar *relation, int options, bool concurrent)
 {
 	Oid			heapOid;
 	bool		result;
+	char		rel_persistence;
 
 	/*
 	 * The lock level used here should match reindex_relation().
@@ -2560,7 +2565,9 @@ ReindexTable(RangeVar *relation, int options, bool concurrent)
 									   0,
 									   RangeVarCallbackOwnsTable, NULL);
 
-	if (concurrent && get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+	rel_persistence = get_rel_persistence(heapOid);
+	if (concurrent &&
+		!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 	{
 		result = ReindexRelationConcurrently(heapOid, options);
 
@@ -2761,12 +2768,15 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 	foreach(l, relids)
 	{
 		Oid			relid = lfirst_oid(l);
+		char		rel_persistence;
 
 		StartTransactionCommand();
 		/* functions in indexes may want a snapshot set */
 		PushActiveSnapshot(GetTransactionSnapshot());
 
-		if (concurrent && get_rel_persistence(relid) != RELPERSISTENCE_TEMP)
+		rel_persistence = get_rel_persistence(relid);
+		if (concurrent &&
+			!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		{
 			(void) ReindexRelationConcurrently(relid, options);
 			/* ReindexRelationConcurrently() does the verbose output */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..a45863a 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -94,7 +96,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -222,7 +225,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* now initialize the sequence's data */
 	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -327,7 +330,7 @@ ResetSequence(Oid seq_relid)
 	/*
 	 * Insert the modified tuple into the new storage file.
 	 */
-	fill_seq_with_data(seq_rel, tuple);
+	fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -340,17 +343,21 @@ ResetSequence(Oid seq_relid)
  * Initialize a sequence's relation with the specified tuple as content
  */
 static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 	sequence_magic *sm;
 	OffsetNumber offnum;
+	bool lockBuffer = false;
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ReadBuffer(rel, P_NEW);
-	Assert(BufferGetBlockNumber(buf) == 0);
+	if (buf == InvalidBuffer)
+	{
+		buf = ReadBuffer(rel, P_NEW);
+		Assert(BufferGetBlockNumber(buf) == 0);
+		lockBuffer = true;
+	}
 
 	page = BufferGetPage(buf);
 
@@ -360,7 +367,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* Now insert sequence tuple */
 
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+	if (lockBuffer)
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +418,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	END_CRIT_SECTION();
 
-	UnlockReleaseBuffer(buf);
+	if (lockBuffer)
+		UnlockReleaseBuffer(buf);
 }
 
 /*
@@ -451,6 +460,13 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 
 	init_sequence(relid, &elm, &seqrel);
 
+	if (RELATION_IS_GLOBAL_TEMP(seqrel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(seqrel)))
+			elog(ERROR, "cannot alter global temp sequence %s when other backend attached it",
+						RelationGetRelationName(seqrel));
+	}
+
 	rel = table_open(SequenceRelationId, RowExclusiveLock);
 	seqtuple = SearchSysCacheCopy1(SEQRELID,
 								   ObjectIdGetDatum(relid));
@@ -502,7 +518,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * Insert the modified tuple into the new storage file.
 		 */
-		fill_seq_with_data(seqrel, newdatatuple);
+		fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
 	}
 
 	/* process OWNED BY if given */
@@ -611,7 +627,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +952,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1154,6 +1170,13 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1954,3 +1977,46 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+void
+gtt_init_seq(Relation rel)
+{
+	/* Initialize sequence for global temporary tables */
+	Datum		value[SEQ_COL_LASTCOL] = {0};
+	bool		null[SEQ_COL_LASTCOL] = {false};
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 037d457..6eadc05 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -555,6 +556,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 								  Relation partitionTbl);
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static OnCommitAction gtt_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -600,6 +602,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -610,8 +613,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Check consistency of arguments
 	 */
+	/* global temp table same as local temp table */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !(stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+			 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -641,7 +646,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	/* global temp table same as local temp table */
+	if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+		 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -742,6 +749,55 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* global temp table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* check parent table*/
+		if (inheritOids)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			table_close(relation, NoLock);
+		}
+
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temp table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1329,6 +1385,7 @@ RemoveRelations(DropStmt *drop)
 		Oid			relOid;
 		ObjectAddress obj;
 		struct DropRelationCallbackState state;
+		char		rel_persistence;
 
 		/*
 		 * These next few steps are a great deal like relation_openrv, but we
@@ -1362,8 +1419,9 @@ RemoveRelations(DropStmt *drop)
 		 * Decide if concurrent mode needs to be used here or not.  The
 		 * relation persistence cannot be known without its OID.
 		 */
+		rel_persistence = get_rel_persistence(relOid);
 		if (drop->concurrent &&
-			get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+			!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		{
 			Assert(list_length(drop->objects) == 1 &&
 				   drop->removeType == OBJECT_INDEX);
@@ -1818,6 +1876,10 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
 		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 			continue;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			continue;
+
 		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
@@ -3569,6 +3631,14 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			elog(ERROR, "can not alter table %s when other backend attached this global temp table",
+						RelationGetRelationName(rel));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -4846,6 +4916,38 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot rewrite temporary tables of other sessions")));
 
+			if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+			{
+				if (tab->chgPersistence)
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot change global temporary table persistence setting")));
+
+				/* gtt may not attached, create it */
+				if(!gtt_storage_attached(tab->relid))
+				{
+					ResultRelInfo *resultRelInfo;
+					MemoryContext oldcontext;
+					MemoryContext ctx_alter_gtt;
+
+					ctx_alter_gtt = AllocSetContextCreate(CurrentMemoryContext,
+											"gtt alter table", ALLOCSET_DEFAULT_SIZES);
+
+					oldcontext = MemoryContextSwitchTo(ctx_alter_gtt);
+					resultRelInfo = makeNode(ResultRelInfo);
+					InitResultRelInfo(resultRelInfo, OldHeap,
+									1, NULL, 0);
+					if (resultRelInfo->ri_RelationDesc->rd_rel->relhasindex &&
+						resultRelInfo->ri_IndexRelationDescs == NULL)
+						ExecOpenIndices(resultRelInfo, false);
+
+					init_gtt_storage(CMD_UTILITY, resultRelInfo);
+					ExecCloseIndices(resultRelInfo);
+					MemoryContextSwitchTo(oldcontext);
+					MemoryContextDelete(ctx_alter_gtt);
+				}
+			}
+
 			/*
 			 * Select destination tablespace (same as original unless user
 			 * requested a change)
@@ -8183,6 +8285,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -12739,6 +12847,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -12941,6 +13052,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temp table");
+
 	/*
 	 * No work if no change in tablespace.
 	 */
@@ -13315,7 +13429,7 @@ index_copy_data(Relation rel, RelFileNode newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
@@ -14722,7 +14836,9 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
 	 */
 	switch (rel->rd_rel->relpersistence)
 	{
+		/* global temp table same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -17315,3 +17431,36 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
 		table_close(rel, NoLock);
 	}
 }
+
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			bool	res = false;
+			char	*sval = defGetString(def);
+
+			if (!parse_bool(sval, &res))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
+
+			if (res)
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 5a110ed..02e6f1e 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"
@@ -1215,6 +1216,17 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	 /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		is_gtt = true;
+		up_gtt_relstats(relation,
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1228,17 +1240,26 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relpages = (int32) num_pages;
+	else if (pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (is_gtt)
+		relation->rd_rel->reltuples = (float4) num_tuples;
+	else if (pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	else if (pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1283,7 +1304,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1294,7 +1316,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1392,6 +1415,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
@@ -1449,6 +1476,42 @@ 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	safe_age;
+		TransactionId	oldest_gtt_frozenxid =
+			list_all_session_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid))
+		{
+			safe_age = oldest_gtt_frozenxid + vacuum_gtt_defer_check_age;
+			if (safe_age < FirstNormalTransactionId)
+				safe_age += FirstNormalTransactionId;
+
+			/*
+			 * We tolerate that the minimum age of gtt is less than
+			 * the minimum age of conventional tables, otherwise it will
+			 * throw warning message.
+			 */
+			if (TransactionIdIsNormal(safe_age) &&
+				TransactionIdPrecedes(safe_age, newFrozenXid))
+			{
+				ereport(WARNING,
+					(errmsg("global temp table oldest relfrozenxid %u is the oldest in the entire db",
+							oldest_gtt_frozenxid),
+					 errdetail("The oldest relfrozenxid in pg_class is %u", newFrozenXid),
+					 errhint("If they differ greatly, please consider cleaning up the data in global temp table.")));
+			}
+
+			if (TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+				newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1776,6 +1839,15 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6e65103..04706ee 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -530,6 +530,11 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 4fdffad..0aa7559 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index fb6ce49..06d9237 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
@@ -545,6 +546,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	init_gtt_storage(mtstate->operation, leaf_part_rri);
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 20a4c47..e9f6dcd 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,6 +42,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -2379,6 +2380,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			ExecOpenIndices(resultRelInfo,
 							node->onConflictAction != ONCONFLICT_NONE);
 
+		init_gtt_storage(operation, resultRelInfo);
+
 		/*
 		 * If this is an UPDATE and a BEFORE UPDATE trigger is present, the
 		 * trigger itself might modify the partition-key values. So arrange
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 255f56b..d94d6f0 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 e664eb1..e03bfa0 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6419,7 +6419,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
+	/* global temp table is same as local temp table */
 	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 2554502..04fb5d4 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -28,6 +28,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temp table */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index eee9c33..1891c53 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2594,6 +2594,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 1219ac8..70e5a93 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3302,17 +3302,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; }
@@ -11638,19 +11632,13 @@ OptTempTableName:
 				}
 			| GLOBAL TEMPORARY opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED opt_table qualified_name
 				{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b875a50..123945b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 75c122f..97213ed 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
@@ -3108,6 +3111,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		cxt.isforeign = false;
 	}
 	cxt.relation = stmt->relation;
+	cxt.relation->relpersistence = RelationGetRelPersistence(rel);
 	cxt.rel = rel;
 	cxt.inhRelations = NIL;
 	cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 7e97ffa..1f2ac58 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2089,6 +2089,11 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* autovacuum skip vacuum global temp table */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2155,7 +2160,9 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		/* autovacuum skip vacuum global temp table */
+		if (classForm->relpersistence == RELPERSISTENCE_TEMP || 
+			classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index f9980cf..406a25c 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -53,6 +54,7 @@
 #include "utils/rel.h"
 #include "utils/resowner_private.h"
 #include "utils/timestamp.h"
+#include "utils/guc.h"
 
 
 /* Note: these two macros only work on shared buffers, not local ones! */
@@ -2819,6 +2821,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d5..7adb969 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -21,6 +21,7 @@
 #include "access/nbtree.h"
 #include "access/subtrans.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, active_gtt_shared_hash_size());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* global temporary table */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 3630006..0c8ef80 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -63,6 +63,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -4086,3 +4087,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 5aa19d3..b3bc455 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 8406644..0d99321 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -1008,6 +1008,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 4fdcb07..58fc8de 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -112,6 +112,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -4731,12 +4732,25 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -4861,15 +4875,27 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6251,6 +6277,7 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	{
 		/* Simple variable --- look to stats for the underlying table */
 		RangeTblEntry *rte = planner_rt_fetch(index->rel->relid, root);
+		char	rel_persistence = get_rel_persistence(rte->relid);
 
 		Assert(rte->rtekind == RTE_RELATION);
 		relid = rte->relid;
@@ -6268,6 +6295,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6279,6 +6313,8 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	}
 	else
 	{
+		char	rel_persistence = get_rel_persistence(index->indexoid);
+
 		/* Expression --- maybe there are stats for the index itself */
 		relid = index->indexoid;
 		colnum = 1;
@@ -6294,6 +6330,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7212,6 +7255,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		/* attempt to lookup stats in relation for this index column */
 		if (attnum != 0)
 		{
+			char	rel_persistence = get_rel_persistence(rte->relid);
+
 			/* Simple variable -- look to stats for the underlying table */
 			if (get_relation_stats_hook &&
 				(*get_relation_stats_hook) (root, rte, attnum, &vardata))
@@ -7224,6 +7269,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7236,6 +7289,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		}
 		else
 		{
+			char	rel_persistence = get_rel_persistence(index->indexoid);
+
 			/*
 			 * Looks like we've found an expression column in the index. Let's
 			 * see if there's any stats for it.
@@ -7255,6 +7310,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index a7d63f1..d8a52cb 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -2939,6 +2940,18 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 9f1f11d..aeb2a78 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -65,6 +65,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1133,6 +1134,24 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1187,6 +1206,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			gtt_fix_index_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -1317,7 +1337,17 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+			if (newrelnode != InvalidOid &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2246,6 +2276,8 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		gtt_fix_index_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3472,6 +3504,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3579,28 +3615,34 @@ void
 RelationSetNewRelfilenode(Relation relation, char persistence)
 {
 	Oid			newrelfilenode;
-	Relation	pg_class;
-	HeapTuple	tuple;
+	Relation	pg_class = NULL;
+	HeapTuple	tuple = NULL;
 	Form_pg_class classform;
 	MultiXactId minmulti = InvalidMultiXactId;
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
+	bool		modify_pg_class = !RELATION_IS_GLOBAL_TEMP(relation);
 
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
 
-	/*
-	 * Get a writable copy of the pg_class tuple for the given relation.
-	 */
-	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+	if (modify_pg_class)
+	{
+		/*
+		 * Get a writable copy of the pg_class tuple for the given relation.
+		 */
+		pg_class = table_open(RelationRelationId, RowExclusiveLock);
 
-	tuple = SearchSysCacheCopy1(RELOID,
-								ObjectIdGetDatum(RelationGetRelid(relation)));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "could not find tuple for relation %u",
-			 RelationGetRelid(relation));
-	classform = (Form_pg_class) GETSTRUCT(tuple);
+		tuple = SearchSysCacheCopy1(RELOID,
+									ObjectIdGetDatum(RelationGetRelid(relation)));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u",
+				 RelationGetRelid(relation));
+		classform = (Form_pg_class) GETSTRUCT(tuple);
+	}
+	else
+		memset(&classform, 0, sizeof(classform));
 
 	/*
 	 * Schedule unlinking of the old storage at transaction commit.
@@ -3626,7 +3668,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;
@@ -3646,6 +3688,15 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 			break;
 	}
 
+	if (!modify_pg_class)
+	{
+		Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+		Assert(RELATION_IS_GLOBAL_TEMP(relation));
+		Assert(!RelationIsMapped(relation));
+		relation->rd_node.relNode = relnode;
+		CacheInvalidateRelcache(relation);
+	}
 	/*
 	 * If we're dealing with a mapped index, pg_class.relfilenode doesn't
 	 * change; instead we have to send the update to the relation mapper.
@@ -3655,7 +3706,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	 * possibly-inaccurate values of relpages etc, but those will be fixed up
 	 * later.
 	 */
-	if (RelationIsMapped(relation))
+	else if (RelationIsMapped(relation))
 	{
 		/* This case is only supported for indexes */
 		Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3701,9 +3752,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 		CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 	}
 
-	heap_freetuple(tuple);
+	if (modify_pg_class)
+	{
+		heap_freetuple(tuple);
 
-	table_close(pg_class, RowExclusiveLock);
+		table_close(pg_class, RowExclusiveLock);
+	}
 
 	/*
 	 * Make the pg_class row change or relation map change visible.  This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 5bdc02f..b52b51d 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -37,6 +37,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "commands/prepare.h"
 #include "commands/trigger.h"
@@ -142,6 +143,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temp table feature.
+ * global temp table define can still storage in catalog
+ * just can not use.
+ * num > 0 means database can management num active global temp table.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2070,6 +2083,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."),
@@ -2577,6 +2599,16 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"vacuum_gtt_defer_check_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("The defer check age of GTT, used to check expired data after vacuum."),
+			NULL
+		},
+		&vacuum_gtt_defer_check_age,
+		10000, 0, 1000000,
+		NULL, NULL, NULL
+	},
+
 	/*
 	 * See also CheckRequiredParameterValues() if this parameter changes
 	 */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 5db4f57..018a9bf 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2424,6 +2424,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
 		dopt->no_unlogged_table_data)
 		return;
 
+	/* Don't dump data in global temp table/sequence */
+	if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		return;
+
 	/* Check that the data is not explicitly excluded */
 	if (simple_oid_list_member(&tabledata_exclude_oids,
 							   tbinfo->dobj.catId.oid))
@@ -15777,6 +15781,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
 		char	   *foreign = "";
+		char	   *table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15830,9 +15835,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);
 
@@ -16226,7 +16237,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
-			 tbinfo->relkind == RELKIND_MATVIEW))
+			 tbinfo->relkind == RELKIND_MATVIEW) &&
+			 tbinfo->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
 		{
 			appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
 			appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
@@ -17131,6 +17143,7 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	PQExpBuffer query = createPQExpBuffer();
 	PQExpBuffer delqry = createPQExpBuffer();
 	char	   *qseqname;
+	bool	   global_temp_seq = false;
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
@@ -17140,9 +17153,11 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
-						  "FROM pg_catalog.pg_sequence "
-						  "WHERE seqrelid = '%u'::oid",
+						  "seqcache, seqcycle, c.relpersistence "
+						  "FROM pg_catalog.pg_sequence s, "
+						  "pg_catalog.pg_class c "
+						  "WHERE seqrelid = '%u'::oid "
+						  "and s.seqrelid = c.oid",
 						  tbinfo->dobj.catId.oid);
 	}
 	else if (fout->remoteVersion >= 80400)
@@ -17187,6 +17202,9 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
 
+	if (fout->remoteVersion >= 130000)
+		global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
 	if (strcmp(seqtype, "smallint") == 0)
@@ -17261,9 +17279,13 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	}
 	else
 	{
-		appendPQExpBuffer(query,
-						  "CREATE SEQUENCE %s\n",
-						  fmtQualifiedDumpable(tbinfo));
+		appendPQExpBuffer(query, "CREATE ");
+
+		if (global_temp_seq)
+			appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+		appendPQExpBuffer(query, "SEQUENCE %s\n",
+								  fmtQualifiedDumpable(tbinfo));
 
 		if (strcmp(seqtype, "bigint") != 0)
 			appendPQExpBuffer(query, "    AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 00aef85..9ad71c8 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -84,7 +84,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -157,7 +157,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 7e524ea..371168f 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -441,22 +443,46 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 	 * output, so we have to copy that system table.  It's easiest to do that
 	 * by treating it as a user table.
 	 */
-	snprintf(query + strlen(query), sizeof(query) - strlen(query),
-			 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
-			 "  SELECT c.oid, 0::oid, 0::oid "
-			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
-			 "         ON c.relnamespace = n.oid "
-			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
-	/* exclude possible orphaned temp tables */
-			 "    ((n.nspname !~ '^pg_temp_' AND "
-			 "      n.nspname !~ '^pg_toast_temp_' AND "
-			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
-			 "                        'binary_upgrade', 'pg_toast') AND "
-			 "      c.oid >= %u::pg_catalog.oid) OR "
-			 "     (n.nspname = 'pg_catalog' AND "
-			 "      relname IN ('pg_largeobject') ))), ",
-			 FirstNormalObjectId);
+	if (skip_gtt)
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude global temp tables */
+				 "    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
+	else
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
 
 	/*
 	 * Add a CTE that collects OIDs of toast tables belonging to the tables
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 70194eb..8dd9e66 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -411,7 +411,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -642,7 +642,9 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -653,7 +655,9 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 8b90cef..d155205 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -389,7 +389,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index f05e914..19adab9 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3724,7 +3724,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 0e7a373..a47de01 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1023,6 +1023,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2390,6 +2392,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2598,6 +2603,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 78b33b2..4be89f6 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -168,6 +168,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad..c8236a1 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5558,6 +5558,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4388',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4389',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4390',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4391',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 30c38e0..7ff2408 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,7 +22,7 @@
 /* GUC variables */
 extern int	wal_skip_threshold;
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationPreTruncate(Relation rel);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..902375d
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern int		vacuum_gtt_defer_check_age;
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Relation relation,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void gtt_force_enable_index(Relation index);
+extern void gtt_fix_index_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+extern void gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index e2638ab..89a5ce4 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 3f88683..f870e9a 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 8fda8e4..172eae9 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_TBM,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_SXACT,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index ae4f573..69cce92 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -117,6 +117,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId session_gtt_frozenxid;	/* session level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 2819282..363fc33 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -282,6 +282,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 0b5957b..0720a4c 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	struct SMgrRelationData *rd_smgr;	/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -305,6 +305,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -570,11 +571,13 @@ typedef struct ViewOptions
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -582,6 +585,7 @@ typedef struct ViewOptions
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -594,6 +598,14 @@ typedef struct ViewOptions
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is either local temp relation of this session
+ * 		or global temp relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -637,6 +649,17 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..ca2d135
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,14 @@
+reset search_path;
+select pg_sleep(5);
+ pg_sleep 
+----------
+ 
+(1 row)
+
+drop schema gtt cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
+drop cascades to table gtt.gtt_with_seq
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..d6a675a
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,383 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+ n 
+---
+ 9
+(1 row)
+
+commit;
+-- 0 row
+select * from gtt6;
+ n 
+---
+(0 rows)
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temp table
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  could not create global temporary table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR:  insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count 
+-------
+     1
+(1 row)
+
+select count(*) from orders;
+ count 
+-------
+     0
+(1 row)
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  2 |  3
+(2 rows)
+
+commit;
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+select * from gtt_s_2 order by c1;
+ c1 | c2 
+----+----
+  1 |  2
+  2 |  4
+(2 rows)
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 1)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 300)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 33 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test_new
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.gtt6
+drop cascades to table gtt_function.foo
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to sequence gtt_function.seq_1
+drop cascades to table gtt_function.gtt_s_1
+drop cascades to table gtt_function.gtt_s_2
+drop cascades to table gtt_function.gt1
+drop cascades to table gtt_function.gtt_test1
+drop cascades to table gtt_function.gtt_test2
+drop cascades to table gtt_function.gtt_test3
+drop cascades to table gtt_function.gtt_test4
+drop cascades to table gtt_function.gtt_test5
+drop cascades to table gtt_function.gtt_test6
+drop cascades to table gtt_function.gtt_test7
+drop cascades to table gtt_function.gtt_test8
+drop cascades to table gtt_function.gtt_test9
+drop cascades to table gtt_function.gtt_test10
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..0646aae
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,90 @@
+set search_path=gtt,sys;
+select nextval('gtt_with_seq_c2_seq');
+ nextval 
+---------
+       1
+(1 row)
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..2f084be
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,343 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+rollback;
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+commit;
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          114688 |                 614400
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |            49152 |         49152 |                  49152
+(2 rows)
+
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2000
+(1 row)
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+ c1 | c2 
+----+----
+  1 |  1
+(1 row)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..8c0c376
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,10 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..4420fdb
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,80 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+ gtt_stats  | gtt_pkey  |        1 |         0 |             0
+(2 rows)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+reindex table gtt;
+reindex index gtt_pkey;
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+ gtt_stats  | gtt_pkey  |       30 |     10000 |             0
+(2 rows)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index ac31840..55b221e 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1349,6 +1349,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 95f1925..76b2374 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..2c8e586
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,8 @@
+
+
+reset search_path;
+
+select pg_sleep(5);
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..81f0bfc
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,253 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+commit;
+-- 0 row
+select * from gtt6;
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+commit;
+select * from gtt_s_1 order by c1;
+select * from gtt_s_2 order by c1;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d05745e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,44 @@
+
+
+set search_path=gtt,sys;
+
+select nextval('gtt_with_seq_c2_seq');
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..39cca5e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,154 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+rollback;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+commit;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+select count(*) from gtt_t_kenyon;
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..dbe84d1
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,19 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..d61b0ff
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,46 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+reindex table gtt;
+
+reindex index gtt_pkey;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#249曾文旌
wenjing.zwj@alibaba-inc.com
In reply to: Prabhat Sahu (#245)
1 attachment(s)
Re: [Proposal] Global temporary tables

2020年4月17日 下午7:26,Prabhat Sahu <prabhat.sahu@enterprisedb.com> 写道:

On Fri, Apr 17, 2020 at 2:44 PM 曾文旌 <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> wrote:

I improved the logic of the warning message so that when the gap between relfrozenxid of GTT is small,
it will no longer be alarmed message.

Hi Wenjing,
Thanks for the patch(v26), I have verified the previous related issues, and are working fine now.
Please check the below scenario VACUUM from a non-super user.

-- Create user "test_gtt", connect it , create gtt, VACUUM gtt and VACUUM / VACUUM FULL
postgres=# CREATE USER test_gtt;
CREATE ROLE
postgres=# \c postgres test_gtt
You are now connected to database "postgres" as user "test_gtt".
postgres=> CREATE GLOBAL TEMPORARY TABLE gtt1(c1 int);
CREATE TABLE

-- VACUUM gtt is working fine, whereas we are getting huge WARNING for VACUUM / VACUUM FULL as below:
postgres=> VACUUM gtt1 ;
VACUUM
postgres=> VACUUM;
WARNING: skipping "pg_statistic" --- only superuser or database owner can vacuum it
WARNING: skipping "pg_type" --- only superuser or database owner can vacuum it
WARNING: skipping "pg_toast_2600" --- only table or database owner can vacuum it
WARNING: skipping "pg_toast_2600_index" --- only table or database owner can vacuum it

... ...
... ...

WARNING: skipping "_pg_foreign_tables" --- only table or database owner can vacuum it
WARNING: skipping "foreign_table_options" --- only table or database owner can vacuum it
WARNING: skipping "user_mapping_options" --- only table or database owner can vacuum it
WARNING: skipping "user_mappings" --- only table or database owner can vacuum it
VACUUM

I think this is expected, and user test_gtt does not have permission to vacuum the system table.
This has nothing to do with GTT.

Wenjing

Show quoted text

--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

Attachments:

smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#250Prabhat Sahu
prabhat.sahu@enterprisedb.com
In reply to: 曾文旌 (#249)
Re: [Proposal] Global temporary tables

I think this is expected, and user test_gtt does not have permission to
vacuum the system table.
This has nothing to do with GTT.

Hi Wenjing, Thanks for the explanation.

Thanks for the new patch. I have verified the crash, Now its resolved.

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

#251tushar
tushar.ahuja@enterprisedb.com
In reply to: 曾文旌 (#248)
Re: [Proposal] Global temporary tables

On 4/20/20 2:59 PM, 曾文旌 wrote:

Please check my new patch.

Thanks Wenjing. Please refer this below scenario , getting error - 
ERROR:  could not read block 0 in file "base/16466/t4_16472": read only
0 of 8192 bytes

Steps to reproduce

Connect to psql terminal,create a table ( create global temp table t2 (n
int primary key ) on commit delete rows;)
exit from psql terminal and execute (./clusterdb -t t2 -d postgres -v)
connect to psql terminal and one by one execute these below sql statements
(
cluster verbose t2 using t2_pkey;
cluster verbose t2 ;
alter table t2 add column i int;
cluster verbose t2 ;
cluster verbose t2 using t2_pkey;
create unique index ind on t2(n);
create unique index concurrently  ind1 on t2(n);
select * from t2;
)
This last SQL - will throw this error -  - ERROR:  could not read block
0 in file "base/16466/t4_16472": read only 0 of 8192 bytes

--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company

#252曾文旌
wenjing.zwj@alibaba-inc.com
In reply to: tushar (#251)
2 attachment(s)
Re: [Proposal] Global temporary tables

2020年4月20日 下午9:15,tushar <tushar.ahuja@enterprisedb.com> 写道:

On 4/20/20 2:59 PM, 曾文旌 wrote:

Please check my new patch.

Thanks Wenjing. Please refer this below scenario , getting error - ERROR: could not read block 0 in file "base/16466/t4_16472": read only 0 of 8192 bytes

Steps to reproduce

Connect to psql terminal,create a table ( create global temp table t2 (n int primary key ) on commit delete rows;)
exit from psql terminal and execute (./clusterdb -t t2 -d postgres -v)
connect to psql terminal and one by one execute these below sql statements
(
cluster verbose t2 using t2_pkey;
cluster verbose t2 ;
alter table t2 add column i int;
cluster verbose t2 ;
cluster verbose t2 using t2_pkey;
create unique index ind on t2(n);
create unique index concurrently ind1 on t2(n);
select * from t2;
)
This last SQL - will throw this error - - ERROR: could not read block 0 in file "base/16466/t4_16472": read only 0 of 8192 bytes

Fixed in global_temporary_table_v29-pg13.patch
Please check.

Wenjing

Attachments:

global_temporary_table_v29-pg13.patchapplication/octet-stream; name=global_temporary_table_v29-pg13.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 8ccc228..b061de9 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -168,6 +168,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * For global temp table only
+	 * use ShareUpdateExclusiveLock for ensure safety
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temp table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},	
 	/* list terminator */
 	{{NULL}}
 };
@@ -1817,6 +1830,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1961,13 +1976,18 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	/*
 	 * There are no options for partitioned tables yet, but this is able to do
 	 * some validation.
 	 */
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index 765329b..4761fdc 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1011,7 +1011,9 @@ gistproperty(Oid index_oid, int attno,
 XLogRecPtr
 gistGetFakeLSN(Relation rel)
 {
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	/* global temp is same as local temp table */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 3ec6d52..1222594 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -150,7 +150,9 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	/* global temp table is same as local temp table */
+	if (!(index->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		index->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 56b3562..d3443ec 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -587,7 +587,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -640,7 +640,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 3c18db2..b08a1fc 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -62,6 +62,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -431,9 +432,9 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
 	Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
-	/* not every AM requires these to be valid, but heap does */
-	Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
-	Assert(MultiXactIdIsValid(onerel->rd_rel->relminmxid));
+	/* not every AM requires these to be valid, but regular heap does */
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ MultiXactIdIsValid(onerel->rd_rel->relminmxid));
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 39b8f17..abb76dc 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -601,6 +602,10 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 11e3273..32694c9 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6422,6 +6422,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/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 5eaca27..e5002aa 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -231,7 +231,8 @@ Boot_CreateStmt:
 												   mapped_relation,
 												   true,
 												   &relfrozenxid,
-												   &relminmxid);
+												   &relminmxid,
+												   false);
 						elog(DEBUG4, "bootstrap relation created");
 					}
 					else
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 9499bb3..ae47364 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -43,6 +43,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 7d6acae..3be8d63 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -393,7 +393,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/* global temp table is same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 632c058..9511182 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -304,7 +306,8 @@ heap_create(const char *relname,
 			bool mapped_relation,
 			bool allow_system_table_mods,
 			TransactionId *relfrozenxid,
-			MultiXactId *relminmxid)
+			MultiXactId *relminmxid,
+			bool skip_create_storage)
 {
 	bool		create_storage;
 	Relation	rel;
@@ -404,6 +407,9 @@ heap_create(const char *relname,
 									 relpersistence,
 									 relkind);
 
+	if (skip_create_storage)
+		create_storage = false;
+
 	/*
 	 * Have the storage manager create the relation's disk file, if needed.
 	 *
@@ -427,7 +433,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -959,6 +965,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -997,8 +1004,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;
@@ -1261,7 +1278,8 @@ heap_create_with_catalog(const char *relname,
 							   mapped_relation,
 							   allow_system_table_mods,
 							   &relfrozenxid,
-							   &relminmxid);
+							   &relminmxid,
+							   false);
 
 	Assert(relid == RelationGetRelid(new_rel_desc));
 
@@ -1360,6 +1378,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1940,6 +1959,14 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/* We allow to drop global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			elog(ERROR, "can not drop relation %s when other backend attached this global temp table",
+						RelationGetRelationName(rel));
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3167,7 +3194,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3179,7 +3206,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3225,8 +3252,13 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
+
+		/* truncate global temp table only need RowExclusiveLock */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3259,6 +3291,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3267,23 +3300,37 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (!gtt_storage_attached(RelationGetRelid(rel)))
+			return;
+
+		/*
+		 * Truncate global temp table only need RowExclusiveLock
+		 */
+		lockmode = RowExclusiveLock;
+	}
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(rel, 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index bd7ec92..2003115 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -53,6 +53,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -728,6 +729,11 @@ index_create(Relation heapRelation,
 	char		relkind;
 	TransactionId relfrozenxid;
 	MultiXactId relminmxid;
+	bool		skip_create_storage = false;
+
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation) &&
+		!gtt_storage_attached(RelationGetRelid(heapRelation)))
+		skip_create_storage = true;
 
 	/* constraint flags can only be set when a constraint is requested */
 	Assert((constr_flags == 0) ||
@@ -881,6 +887,19 @@ index_create(Relation heapRelation,
 						indexRelationName, RelationGetRelationName(heapRelation))));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temp table use concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot reindex global temporary tables concurrently")));
+
+		/* if global temp table not init storage, then skip build index */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -935,7 +954,8 @@ index_create(Relation heapRelation,
 								mapped_relation,
 								allow_system_table_mods,
 								&relfrozenxid,
-								&relminmxid);
+								&relminmxid,
+								skip_create_storage);
 
 	Assert(relfrozenxid == InvalidTransactionId);
 	Assert(relminmxid == InvalidMultiXactId);
@@ -2041,7 +2061,8 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 * lock (see comments in RemoveRelations), and a non-concurrent DROP is
 	 * more efficient.
 	 */
-	Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+	Assert(!(get_rel_persistence(indexId) == RELPERSISTENCE_TEMP ||
+			 get_rel_persistence(indexId) == RELPERSISTENCE_GLOBAL_TEMP) ||
 		   (!concurrent && !concurrent_lock_mode));
 
 	/*
@@ -2073,6 +2094,14 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/* We allow to drop index on global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+			elog(ERROR, "can not drop index %s when other backend attached this global temp table.",
+						RelationGetRelationName(userHeapRelation));
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2681,6 +2710,11 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	/* update index stats into localhash and rel_rd_rel for global temp table */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		is_gtt = true;
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2766,21 +2800,35 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
+		if (is_gtt)
+			rel->rd_rel->relpages = (int32) relpages;
+		else if (rd_rel->relpages != (int32) relpages)
 		{
 			rd_rel->relpages = (int32) relpages;
 			dirty = true;
 		}
-		if (rd_rel->reltuples != (float4) reltuples)
+
+		if (is_gtt)
+			rel->rd_rel->reltuples = (float4) reltuples;
+		else if (rd_rel->reltuples != (float4) reltuples)
 		{
 			rd_rel->reltuples = (float4) reltuples;
 			dirty = true;
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+
+		if (is_gtt)
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+		else if (rd_rel->relallvisible != (int32) relallvisible)
 		{
 			rd_rel->relallvisible = (int32) relallvisible;
 			dirty = true;
 		}
+
+		if (is_gtt)
+		{
+			up_gtt_relstats(rel, relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
+		}
 	}
 
 	/*
@@ -2894,6 +2942,15 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, index, val);
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			gtt_force_enable_index(indexRelation);
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3442,6 +3499,10 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	PGRUsage	ru0;
 	bool		progress = (options & REINDEXOPT_REPORT_PROGRESS) != 0;
 
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		!gtt_storage_attached(indexId))
+		return;
+
 	pg_rusage_init(&ru0);
 
 	/*
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 2ec2301..1b6061d 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -655,6 +655,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		/* global temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp relations in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index d713d5c..afcf004 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,6 +27,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -61,6 +62,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			relOid;			/* InvalidOid if not a global temp rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -115,7 +117,7 @@ AddPendingSync(const RelFileNode *rnode)
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -127,6 +129,8 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		/* global temp table use same storage strategy as local temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -154,6 +158,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->relOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
@@ -165,6 +170,13 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		AddPendingSync(&rnode);
 	}
 
+	/* remember global temp table storage info to localhash */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel)
+	{
+		pending->relOid = RelationGetRelid(rel);
+		remember_gtt_storage_info(rnode, rel);
+	}
+
 	return srel;
 }
 
@@ -201,11 +213,15 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->relOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->relOid = RelationGetRelid(rel);
+
 	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
@@ -602,6 +618,7 @@ smgrDoPendingDeletes(bool isCommit)
 				i = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -631,14 +648,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->relOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -651,9 +672,18 @@ smgrDoPendingDeletes(bool isCommit)
 		smgrdounlinkall(srels, nrels, false);
 
 		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* clean global temp table flags when transaction commit or rollback */
+			if (SmgrIsTemp(srels[i]) &&
+				reloids[i] != InvalidOid &&
+				gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..451cf82
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1491 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  code to create and destroy physical storage for global temparary table
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_relstats_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+int		vacuum_gtt_defer_check_age = 0;
+
+typedef struct gtt_ctl_data
+{
+	LWLock			lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+	Oid			relfilenode;
+	Oid			spcnode;
+
+	/* pg_class stat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+
+	/* pg_statistic */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+
+	Oid			oldrelid;			/* remember the source of relid, before the switch relfilenode. */
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_reset_statistics(gtt_local_hash_entry *entry);
+static void gtt_free_statistics(gtt_local_hash_entry *entry);
+static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok);
+static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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();
+	}
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(gtt_fnode);
+	info.entrysize = action_gtt_shared_hash_entry_size();
+	active_gtt_shared_hash =
+		ShmemInitHash("active gtt shared hash",
+						gtt_shared_ctl->max_entry,
+						gtt_shared_ctl->max_entry,
+						&info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			found;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *)&(fnode), 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_global_temporary_table.")));
+	}
+
+	if (found == false)
+	{
+		int			wordnum;
+
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode				fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	if (!skiplock)
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		if (!skiplock)
+			LWLockRelease(&gtt_shared_ctl->lock);
+
+		if (isCommit)
+			elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+	bms_del_member(entry->map, MyBackendId);
+
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+
+	if (!skiplock)
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+	return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	Bitmapset	*map_copy = NULL;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool		in_use = false;
+	int			num_use = 0;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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;
+	gtt_relfilenode			*new_node = NULL;
+	Oid						relid = RelationGetRelid(rel);
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		/* First time through: initialize the hash table */
+		HASHCTL		ctl;
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_relstats_context =
+			AllocSetContextCreate(CacheMemoryContext,
+							"gtt relstats context",
+							ALLOCSET_DEFAULT_SIZES);
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+	entry = gtt_search_by_relid(relid, true);
+	if (!entry)
+	{
+		bool		found = false;
+		int 		natts = 0;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash,
+						(void *) &relid, HASH_ENTER, &found);
+
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+		entry->natts = 0;
+		entry->attnum = NULL;
+		entry->att_stat_tups = NULL;
+		entry->oldrelid = InvalidOid;
+
+		natts = RelationGetNumberOfAttributes(rel);
+		entry->attnum = palloc0(sizeof(int) * natts);
+		entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+		entry->natts = natts;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			gtt_storage_checkin(relid);
+		}
+	}
+
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* only heap contain transaction information */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	gtt_reset_statistics(entry);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	gtt_relfilenode *d_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+		return;
+	}
+
+	d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+		else if (entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+			gtt_relfilenode *gttnode2 = NULL;
+
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+			Assert(gttnode2->relfilenode == rnode.relNode);
+			Assert(list_length(entry->relfilenode_list) == 1);
+			/* rollback switch relfilenode */
+			gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+									   entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+									   false);
+			/* clean up footprint */
+			entry2->oldrelid = InvalidOid;
+			d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+			Assert(d_rnode);
+		}
+		else
+		{
+			if (entry->relfilenode_list == NIL)
+			{
+				if (entry->relkind == RELKIND_RELATION ||
+					entry->relkind == RELKIND_SEQUENCE)
+					gtt_storage_checkout(relid, false, isCommit);
+
+				gtt_free_statistics(entry);
+				hash_search(gtt_storage_local_hash,
+						(void *) &(relid), HASH_REMOVE, NULL);
+			}
+
+			return;
+		}
+	}
+
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	pfree(d_rnode);
+	if (entry->relfilenode_list == NIL)
+	{
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(relid, false, isCommit);
+
+		if (isCommit && entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			/* clean up footprint */
+			entry2->oldrelid = InvalidOid;
+		}
+
+		gtt_free_statistics(entry);
+		hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_REMOVE, NULL);
+	}
+	else
+		gtt_reset_statistics(entry);
+
+	return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool found = false;
+	gtt_local_hash_entry *entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry)
+		found = true;
+
+	return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS				status;
+	gtt_local_hash_entry		*entry;
+	SMgrRelation	*srels = NULL;
+	Oid				*relids = NULL;
+	char			*relkinds = NULL;
+	int			nrels = 0,
+				nfiles = 0,
+				maxrels = 0,
+				maxfiles = 0,
+				i = 0;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel;
+			RelFileNode 	rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel = smgropen(rnode, MyBackendId);
+
+			if (maxfiles == 0)
+			{
+				maxfiles = 32;
+				srels = palloc(sizeof(SMgrRelation) * maxfiles);
+			}
+			else if (maxfiles <= nfiles)
+			{
+				maxfiles *= 2;
+				srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+			}
+
+			srels[nfiles++] = srel;
+		}
+
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			relids  = palloc(sizeof(Oid) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			relids  = repalloc(relids , sizeof(Oid) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		relkinds[nrels] = entry->relkind;
+		relids[nrels] = entry->relid;
+		nrels++;
+	}
+
+	if (nfiles > 0)
+	{
+		smgrdounlinkall(srels, nfiles, false);
+		for (i = 0; i < nfiles; i++)
+			smgrclose(srels[i]);
+
+		pfree(srels);
+	}
+
+	if (nrels)
+	{
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+		for (i = 0; i < nrels; i++)
+		{
+			if (relkinds[i] == RELKIND_RELATION ||
+				relkinds[i] == RELKIND_SEQUENCE)
+				gtt_storage_checkout(relids[i], true, false);
+		}
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+		pfree(relids);
+		pfree(relkinds);
+	}
+
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+	return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible) 
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+					BlockNumber num_pages,
+					double num_tuples,
+					BlockNumber num_all_visible_pages,
+					TransactionId relfrozenxid,
+					TransactionId relminmxid)
+{
+	Oid		relid = RelationGetRelid(relation);
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (num_pages >= 0 &&
+		gtt_rnode->relpages != (int32)num_pages)
+		gtt_rnode->relpages = (int32)num_pages;
+
+	if (num_tuples >= 0 &&
+		gtt_rnode->reltuples != (float4)num_tuples)
+		gtt_rnode->reltuples = (float4)num_tuples;
+
+	/* only heap contain transaction information and relallvisible */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (gtt_rnode->relallvisible >= 0 &&
+			gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+		{
+			gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+		}
+
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNewTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible) 
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	MemoryContext			oldcontext;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return;
+
+	if (entry->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	Assert(entry->relid == reloid);
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == 0)
+		{
+			entry->attnum[i] = attnum;
+			break;
+		}
+		else if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < entry->natts);
+	Assert(entry->att_stat_tups[i] == NULL);
+	entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return NULL;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			return entry->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int			i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	HeapTuple	tuple;
+	Relation	rel = NULL;
+	int		attnum = PG_GETARG_INT32(1);
+	Oid		reloid = PG_GETARG_OID(0);
+	char		rel_persistence;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	  tupdesc;
+	MemoryContext oldcontext;
+	Tuplestorestate *tupstore;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+
+	relation_close(rel, NoLock);
+	relation_close(pg_tatistic, AccessShareLock);
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate	*tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Oid			relnode = 0;
+	Relation	rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[6];
+		bool	isnull[6];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(reloid);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(RelationGetRelid(index)));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid relOid = index->rd_index->indrelid;
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	if (gtt_storage_attached(relOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int			i;
+	Oid			toastrelid;
+
+	if (!(operation == CMD_UTILITY || operation == CMD_INSERT))
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+
+		index_build(relation, index, info, true, false);
+	}
+
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid 		indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo 	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+static void
+gtt_reset_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+
+		entry->attnum[i] = 0;
+	}
+
+	return;
+}
+
+static void
+gtt_free_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (entry->attnum)
+		pfree(entry->attnum);
+
+	if (entry->att_stat_tups)
+		pfree(entry->att_stat_tups);
+
+	return;
+}
+
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
+void
+gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint)
+{
+	gtt_local_hash_entry	*entry1;
+	gtt_local_hash_entry	*entry2;
+	gtt_relfilenode			*gtt_rnode1 = NULL;
+	gtt_relfilenode			*gtt_rnode2 = NULL;
+	MemoryContext			oldcontext;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry1 = gtt_search_by_relid(rel1, false);
+	gtt_rnode1 = gtt_search_relfilenode(entry1, relfilenode1, false);
+
+	entry2 = gtt_search_by_relid(rel2, false);
+	gtt_rnode2 = gtt_search_relfilenode(entry2, relfilenode2, false);
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	entry1->relfilenode_list = list_delete_ptr(entry1->relfilenode_list, gtt_rnode1);
+	entry2->relfilenode_list = lappend(entry2->relfilenode_list, gtt_rnode1);
+
+	entry2->relfilenode_list = list_delete_ptr(entry2->relfilenode_list, gtt_rnode2);
+	entry1->relfilenode_list = lappend(entry1->relfilenode_list, gtt_rnode2);
+	MemoryContextSwitchTo(oldcontext);
+
+	if (footprint)
+	{
+		entry1->oldrelid = rel2;
+		entry2->oldrelid = rel1;
+	}
+
+	return;
+}
+
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok)
+{
+	gtt_relfilenode			*rnode = NULL;
+	ListCell				*lc;
+
+	Assert(entry);
+
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == relfilenode)
+		{
+			rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (!missing_ok && rnode == NULL)
+		elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+	return rnode;
+}
+
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry == NULL && !missing_ok)
+		elog(ERROR, "relid %u not found in local hash", relid);
+
+	return entry;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index d406ea8..88be041 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM 
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 924ef37..db12eef 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -184,6 +185,13 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -586,14 +594,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 		 * pg_statistic for columns we didn't process, we leave them alone.)
 		 */
 		update_attstats(RelationGetRelid(onerel), inh,
-						attr_cnt, vacattrstats);
+						attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
 
 		for (ind = 0; ind < nindexes; ind++)
 		{
 			AnlIndexData *thisdata = &indexdata[ind];
 
 			update_attstats(RelationGetRelid(Irel[ind]), false,
-							thisdata->attr_cnt, thisdata->vacattrstats);
+							thisdata->attr_cnt, thisdata->vacattrstats,
+							RelationGetRelPersistence(Irel[ind]));
 		}
 
 		/*
@@ -1456,7 +1465,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
  *		by taking a self-exclusive lock on the relation in analyze_rel().
  */
 static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
 {
 	Relation	sd;
 	int			attno;
@@ -1558,31 +1567,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/* Update column statistic to localhash, not catalog */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 04d12a7..42c82c8 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/toasting.h"
 #include "commands/cluster.h"
 #include "commands/progress.h"
@@ -72,6 +73,12 @@ static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
 							bool verbose, bool *pSwapToastByContent,
 							TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
 static List *get_tables_to_cluster(MemoryContext cluster_context);
+static void gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables);
 
 
 /*---------------------------------------------------------------------------
@@ -366,6 +373,14 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap) &&
+		!gtt_storage_attached(RelationGetRelid(OldHeap)))
+	{
+		relation_close(OldHeap, AccessExclusiveLock);
+		pgstat_progress_end_command();
+		return;
+	}
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
@@ -750,6 +765,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	BlockNumber num_pages;
 	int			elevel = verbose ? INFO : DEBUG2;
 	PGRUsage	ru0;
+	bool		is_gtt = false;
+	uint32		gtt_relfrozenxid = 0;
+	uint32		gtt_relminmxid = 0;
 
 	pg_rusage_init(&ru0);
 
@@ -763,6 +781,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	else
 		OldIndex = NULL;
 
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		is_gtt = true;
+
 	/*
 	 * Their tuple descriptors should be exactly alike, but here we only need
 	 * assume that they have the same number of columns.
@@ -830,20 +851,37 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 						  &OldestXmin, &FreezeXid, NULL, &MultiXactCutoff,
 						  NULL);
 
-	/*
-	 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
-	 * backwards, so take the max.
-	 */
-	if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
-		TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
-		FreezeXid = OldHeap->rd_rel->relfrozenxid;
+	if (is_gtt)
+	{
+		get_gtt_relstats(OIDOldHeap,
+					NULL, NULL, NULL,
+					&gtt_relfrozenxid, &gtt_relminmxid);
 
-	/*
-	 * MultiXactCutoff, similarly, shouldn't go backwards either.
-	 */
-	if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
-		MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
-		MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+		if (TransactionIdIsValid(gtt_relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, gtt_relfrozenxid))
+			FreezeXid = gtt_relfrozenxid;
+
+		if (MultiXactIdIsValid(gtt_relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, gtt_relminmxid))
+			MultiXactCutoff = gtt_relminmxid;
+	}
+	else
+	{
+		/*
+		 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
+		 * backwards, so take the max.
+		 */
+		if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
+			FreezeXid = OldHeap->rd_rel->relfrozenxid;
+
+		/*
+		 * MultiXactCutoff, similarly, shouldn't go backwards either.
+		 */
+		if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
+			MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+	}
 
 	/*
 	 * Decide whether to use an indexscan or seqscan-and-optional-sort to scan
@@ -911,6 +949,12 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	table_close(OldHeap, NoLock);
 	table_close(NewHeap, NoLock);
 
+	if (is_gtt)
+	{
+		CommandCounterIncrement();
+		return;
+	}
+
 	/* Update pg_class to reflect the correct values of pages and tuples. */
 	relRelation = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1346,10 +1390,21 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 	 * Swap the contents of the heap relations (including any toast tables).
 	 * Also set old heap's relfrozenxid to frozenXid.
 	 */
-	swap_relation_files(OIDOldHeap, OIDNewHeap,
+	if (newrelpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		Assert(!is_system_catalog);
+		gtt_swap_relation_files(OIDOldHeap, OIDNewHeap,
+								(OIDOldHeap == RelationRelationId),
+								swap_toast_by_content, is_internal,
+								frozenXid, cutoffMulti, mapped_tables);
+	}
+	else
+	{
+		swap_relation_files(OIDOldHeap, OIDNewHeap,
 						(OIDOldHeap == RelationRelationId),
 						swap_toast_by_content, is_internal,
 						frozenXid, cutoffMulti, mapped_tables);
+	}
 
 	/*
 	 * If it's a system catalog, queue a sinval message to flush all catcaches
@@ -1557,3 +1612,141 @@ get_tables_to_cluster(MemoryContext cluster_context)
 
 	return rvs;
 }
+
+static void
+gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables)
+{
+	Relation	relRelation;
+	Oid			relfilenode1,
+				relfilenode2;
+	Relation	rel1;
+	Relation	rel2;
+
+	relRelation = table_open(RelationRelationId, RowExclusiveLock);
+
+	rel1 = relation_open(r1, AccessExclusiveLock);
+	rel2 = relation_open(r2, AccessExclusiveLock);
+
+	relfilenode1 = gtt_fetch_current_relfilenode(r1);
+	relfilenode2 = gtt_fetch_current_relfilenode(r2);
+
+	Assert(OidIsValid(relfilenode1) && OidIsValid(relfilenode2));
+	gtt_switch_rel_relfilenode(r1, relfilenode1, r2, relfilenode2, true);
+
+	CacheInvalidateRelcache(rel1);
+	CacheInvalidateRelcache(rel2);
+
+	InvokeObjectPostAlterHookArg(RelationRelationId, r1, 0,
+								 InvalidOid, is_internal);
+	InvokeObjectPostAlterHookArg(RelationRelationId, r2, 0,
+								 InvalidOid, true);
+
+	if (rel1->rd_rel->reltoastrelid || rel2->rd_rel->reltoastrelid)
+	{
+		if (swap_toast_by_content)
+		{
+			if (rel1->rd_rel->reltoastrelid && rel2->rd_rel->reltoastrelid)
+			{
+				gtt_swap_relation_files(rel1->rd_rel->reltoastrelid,
+									rel2->rd_rel->reltoastrelid,
+									target_is_pg_class,
+									swap_toast_by_content,
+									is_internal,
+									frozenXid,
+									cutoffMulti,
+									mapped_tables);
+			}
+			else
+				elog(ERROR, "cannot swap toast files by content when there's only one");
+		}
+		else
+		{
+			ObjectAddress baseobject,
+						toastobject;
+			long		count;
+
+			if (IsSystemRelation(rel1))
+				elog(ERROR, "cannot swap toast files by links for system catalogs");
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel1->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel2->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+
+			/* Register new dependencies */
+			baseobject.classId = RelationRelationId;
+			baseobject.objectSubId = 0;
+			toastobject.classId = RelationRelationId;
+			toastobject.objectSubId = 0;
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r1;
+				toastobject.objectId = rel1->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r2;
+				toastobject.objectId = rel2->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+		}
+	}
+
+	if (swap_toast_by_content &&
+		rel1->rd_rel->relkind == RELKIND_TOASTVALUE &&
+		rel2->rd_rel->relkind == RELKIND_TOASTVALUE)
+	{
+		Oid			toastIndex1,
+					toastIndex2;
+
+		/* Get valid index for each relation */
+		toastIndex1 = toast_get_valid_index(r1,
+											AccessExclusiveLock);
+		toastIndex2 = toast_get_valid_index(r2,
+											AccessExclusiveLock);
+
+		gtt_swap_relation_files(toastIndex1,
+							toastIndex2,
+							target_is_pg_class,
+							swap_toast_by_content,
+							is_internal,
+							InvalidTransactionId,
+							InvalidMultiXactId,
+							mapped_tables);
+	}
+
+	relation_close(rel1, NoLock);
+	relation_close(rel2, NoLock);
+
+	table_close(relRelation, RowExclusiveLock);
+
+	RelationCloseSmgrByOid(r1);
+	RelationCloseSmgrByOid(r2);
+
+	CommandCounterIncrement();
+}
+
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index ac07f75..655184d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -27,6 +27,7 @@
 #include "catalog/dependency.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -1063,7 +1064,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, stmt->filename, stmt->is_program,
@@ -2794,6 +2795,8 @@ CopyFrom(CopyState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	estate->es_result_relations = resultRelInfo;
 	estate->es_num_result_relations = 1;
 	estate->es_result_relation_info = resultRelInfo;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 2baca12..5338db9 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -521,6 +521,7 @@ DefineIndex(Oid relationId,
 	Snapshot	snapshot;
 	int			save_nestlevel = -1;
 	int			i;
+	char		rel_persistence;
 
 	/*
 	 * Some callers need us to run with an empty default_tablespace; this is a
@@ -542,7 +543,9 @@ DefineIndex(Oid relationId,
 	 * is more efficient.  Do this before any use of the concurrent option is
 	 * done.
 	 */
-	if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+	rel_persistence = get_rel_persistence(relationId);
+	if (stmt->concurrent &&
+		!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		concurrent = true;
 	else
 		concurrent = false;
@@ -2460,7 +2463,8 @@ ReindexIndex(RangeVar *indexRelation, int options, bool concurrent)
 	persistence = irel->rd_rel->relpersistence;
 	index_close(irel, NoLock);
 
-	if (concurrent && persistence != RELPERSISTENCE_TEMP)
+	if (concurrent &&
+		!(persistence == RELPERSISTENCE_TEMP || persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		ReindexRelationConcurrently(indOid, options);
 	else
 		reindex_index(indOid, false, persistence,
@@ -2546,6 +2550,7 @@ ReindexTable(RangeVar *relation, int options, bool concurrent)
 {
 	Oid			heapOid;
 	bool		result;
+	char		rel_persistence;
 
 	/*
 	 * The lock level used here should match reindex_relation().
@@ -2560,7 +2565,9 @@ ReindexTable(RangeVar *relation, int options, bool concurrent)
 									   0,
 									   RangeVarCallbackOwnsTable, NULL);
 
-	if (concurrent && get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+	rel_persistence = get_rel_persistence(heapOid);
+	if (concurrent &&
+		!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 	{
 		result = ReindexRelationConcurrently(heapOid, options);
 
@@ -2761,12 +2768,15 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 	foreach(l, relids)
 	{
 		Oid			relid = lfirst_oid(l);
+		char		rel_persistence;
 
 		StartTransactionCommand();
 		/* functions in indexes may want a snapshot set */
 		PushActiveSnapshot(GetTransactionSnapshot());
 
-		if (concurrent && get_rel_persistence(relid) != RELPERSISTENCE_TEMP)
+		rel_persistence = get_rel_persistence(relid);
+		if (concurrent &&
+			!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		{
 			(void) ReindexRelationConcurrently(relid, options);
 			/* ReindexRelationConcurrently() does the verbose output */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..a45863a 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -94,7 +96,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -222,7 +225,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* now initialize the sequence's data */
 	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -327,7 +330,7 @@ ResetSequence(Oid seq_relid)
 	/*
 	 * Insert the modified tuple into the new storage file.
 	 */
-	fill_seq_with_data(seq_rel, tuple);
+	fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -340,17 +343,21 @@ ResetSequence(Oid seq_relid)
  * Initialize a sequence's relation with the specified tuple as content
  */
 static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 	sequence_magic *sm;
 	OffsetNumber offnum;
+	bool lockBuffer = false;
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ReadBuffer(rel, P_NEW);
-	Assert(BufferGetBlockNumber(buf) == 0);
+	if (buf == InvalidBuffer)
+	{
+		buf = ReadBuffer(rel, P_NEW);
+		Assert(BufferGetBlockNumber(buf) == 0);
+		lockBuffer = true;
+	}
 
 	page = BufferGetPage(buf);
 
@@ -360,7 +367,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* Now insert sequence tuple */
 
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+	if (lockBuffer)
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +418,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	END_CRIT_SECTION();
 
-	UnlockReleaseBuffer(buf);
+	if (lockBuffer)
+		UnlockReleaseBuffer(buf);
 }
 
 /*
@@ -451,6 +460,13 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 
 	init_sequence(relid, &elm, &seqrel);
 
+	if (RELATION_IS_GLOBAL_TEMP(seqrel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(seqrel)))
+			elog(ERROR, "cannot alter global temp sequence %s when other backend attached it",
+						RelationGetRelationName(seqrel));
+	}
+
 	rel = table_open(SequenceRelationId, RowExclusiveLock);
 	seqtuple = SearchSysCacheCopy1(SEQRELID,
 								   ObjectIdGetDatum(relid));
@@ -502,7 +518,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * Insert the modified tuple into the new storage file.
 		 */
-		fill_seq_with_data(seqrel, newdatatuple);
+		fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
 	}
 
 	/* process OWNED BY if given */
@@ -611,7 +627,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +952,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1154,6 +1170,13 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1954,3 +1977,46 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+void
+gtt_init_seq(Relation rel)
+{
+	/* Initialize sequence for global temporary tables */
+	Datum		value[SEQ_COL_LASTCOL] = {0};
+	bool		null[SEQ_COL_LASTCOL] = {false};
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 037d457..6eadc05 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -555,6 +556,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 								  Relation partitionTbl);
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static OnCommitAction gtt_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -600,6 +602,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -610,8 +613,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Check consistency of arguments
 	 */
+	/* global temp table same as local temp table */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !(stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+			 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -641,7 +646,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	/* global temp table same as local temp table */
+	if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+		 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -742,6 +749,55 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* global temp table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* check parent table*/
+		if (inheritOids)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			table_close(relation, NoLock);
+		}
+
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temp table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1329,6 +1385,7 @@ RemoveRelations(DropStmt *drop)
 		Oid			relOid;
 		ObjectAddress obj;
 		struct DropRelationCallbackState state;
+		char		rel_persistence;
 
 		/*
 		 * These next few steps are a great deal like relation_openrv, but we
@@ -1362,8 +1419,9 @@ RemoveRelations(DropStmt *drop)
 		 * Decide if concurrent mode needs to be used here or not.  The
 		 * relation persistence cannot be known without its OID.
 		 */
+		rel_persistence = get_rel_persistence(relOid);
 		if (drop->concurrent &&
-			get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+			!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		{
 			Assert(list_length(drop->objects) == 1 &&
 				   drop->removeType == OBJECT_INDEX);
@@ -1818,6 +1876,10 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
 		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 			continue;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			continue;
+
 		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
@@ -3569,6 +3631,14 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			elog(ERROR, "can not alter table %s when other backend attached this global temp table",
+						RelationGetRelationName(rel));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -4846,6 +4916,38 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot rewrite temporary tables of other sessions")));
 
+			if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+			{
+				if (tab->chgPersistence)
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot change global temporary table persistence setting")));
+
+				/* gtt may not attached, create it */
+				if(!gtt_storage_attached(tab->relid))
+				{
+					ResultRelInfo *resultRelInfo;
+					MemoryContext oldcontext;
+					MemoryContext ctx_alter_gtt;
+
+					ctx_alter_gtt = AllocSetContextCreate(CurrentMemoryContext,
+											"gtt alter table", ALLOCSET_DEFAULT_SIZES);
+
+					oldcontext = MemoryContextSwitchTo(ctx_alter_gtt);
+					resultRelInfo = makeNode(ResultRelInfo);
+					InitResultRelInfo(resultRelInfo, OldHeap,
+									1, NULL, 0);
+					if (resultRelInfo->ri_RelationDesc->rd_rel->relhasindex &&
+						resultRelInfo->ri_IndexRelationDescs == NULL)
+						ExecOpenIndices(resultRelInfo, false);
+
+					init_gtt_storage(CMD_UTILITY, resultRelInfo);
+					ExecCloseIndices(resultRelInfo);
+					MemoryContextSwitchTo(oldcontext);
+					MemoryContextDelete(ctx_alter_gtt);
+				}
+			}
+
 			/*
 			 * Select destination tablespace (same as original unless user
 			 * requested a change)
@@ -8183,6 +8285,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -12739,6 +12847,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -12941,6 +13052,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temp table");
+
 	/*
 	 * No work if no change in tablespace.
 	 */
@@ -13315,7 +13429,7 @@ index_copy_data(Relation rel, RelFileNode newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
@@ -14722,7 +14836,9 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
 	 */
 	switch (rel->rd_rel->relpersistence)
 	{
+		/* global temp table same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -17315,3 +17431,36 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
 		table_close(rel, NoLock);
 	}
 }
+
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			bool	res = false;
+			char	*sval = defGetString(def);
+
+			if (!parse_bool(sval, &res))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
+
+			if (res)
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 5a110ed..02e6f1e 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"
@@ -1215,6 +1216,17 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	 /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		is_gtt = true;
+		up_gtt_relstats(relation,
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1228,17 +1240,26 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relpages = (int32) num_pages;
+	else if (pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (is_gtt)
+		relation->rd_rel->reltuples = (float4) num_tuples;
+	else if (pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	else if (pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1283,7 +1304,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1294,7 +1316,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1392,6 +1415,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
@@ -1449,6 +1476,42 @@ 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	safe_age;
+		TransactionId	oldest_gtt_frozenxid =
+			list_all_session_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid))
+		{
+			safe_age = oldest_gtt_frozenxid + vacuum_gtt_defer_check_age;
+			if (safe_age < FirstNormalTransactionId)
+				safe_age += FirstNormalTransactionId;
+
+			/*
+			 * We tolerate that the minimum age of gtt is less than
+			 * the minimum age of conventional tables, otherwise it will
+			 * throw warning message.
+			 */
+			if (TransactionIdIsNormal(safe_age) &&
+				TransactionIdPrecedes(safe_age, newFrozenXid))
+			{
+				ereport(WARNING,
+					(errmsg("global temp table oldest relfrozenxid %u is the oldest in the entire db",
+							oldest_gtt_frozenxid),
+					 errdetail("The oldest relfrozenxid in pg_class is %u", newFrozenXid),
+					 errhint("If they differ greatly, please consider cleaning up the data in global temp table.")));
+			}
+
+			if (TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+				newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1776,6 +1839,15 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6e65103..04706ee 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -530,6 +530,11 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 4fdffad..0aa7559 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index fb6ce49..06d9237 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
@@ -545,6 +546,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	init_gtt_storage(mtstate->operation, leaf_part_rri);
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 20a4c47..e9f6dcd 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,6 +42,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -2379,6 +2380,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			ExecOpenIndices(resultRelInfo,
 							node->onConflictAction != ONCONFLICT_NONE);
 
+		init_gtt_storage(operation, resultRelInfo);
+
 		/*
 		 * If this is an UPDATE and a BEFORE UPDATE trigger is present, the
 		 * trigger itself might modify the partition-key values. So arrange
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 255f56b..d94d6f0 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 e664eb1..e03bfa0 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6419,7 +6419,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
+	/* global temp table is same as local temp table */
 	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 2554502..04fb5d4 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -28,6 +28,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temp table */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index eee9c33..1891c53 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2594,6 +2594,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 3c78f2d..edb8d9f 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3302,17 +3302,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; }
@@ -11648,19 +11642,13 @@ OptTempTableName:
 				}
 			| GLOBAL TEMPORARY opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED opt_table qualified_name
 				{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b875a50..123945b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 75c122f..97213ed 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
@@ -3108,6 +3111,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		cxt.isforeign = false;
 	}
 	cxt.relation = stmt->relation;
+	cxt.relation->relpersistence = RelationGetRelPersistence(rel);
 	cxt.rel = rel;
 	cxt.inhRelations = NIL;
 	cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 7e97ffa..1f2ac58 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2089,6 +2089,11 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* autovacuum skip vacuum global temp table */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2155,7 +2160,9 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		/* autovacuum skip vacuum global temp table */
+		if (classForm->relpersistence == RELPERSISTENCE_TEMP || 
+			classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index f9980cf..406a25c 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -53,6 +54,7 @@
 #include "utils/rel.h"
 #include "utils/resowner_private.h"
 #include "utils/timestamp.h"
+#include "utils/guc.h"
 
 
 /* Note: these two macros only work on shared buffers, not local ones! */
@@ -2819,6 +2821,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d5..7adb969 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -21,6 +21,7 @@
 #include "access/nbtree.h"
 #include "access/subtrans.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, active_gtt_shared_hash_size());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* global temporary table */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 3630006..0c8ef80 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -63,6 +63,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -4086,3 +4087,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 5aa19d3..b3bc455 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 8406644..0d99321 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -1008,6 +1008,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 4fdcb07..58fc8de 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -112,6 +112,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -4731,12 +4732,25 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -4861,15 +4875,27 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6251,6 +6277,7 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	{
 		/* Simple variable --- look to stats for the underlying table */
 		RangeTblEntry *rte = planner_rt_fetch(index->rel->relid, root);
+		char	rel_persistence = get_rel_persistence(rte->relid);
 
 		Assert(rte->rtekind == RTE_RELATION);
 		relid = rte->relid;
@@ -6268,6 +6295,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6279,6 +6313,8 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	}
 	else
 	{
+		char	rel_persistence = get_rel_persistence(index->indexoid);
+
 		/* Expression --- maybe there are stats for the index itself */
 		relid = index->indexoid;
 		colnum = 1;
@@ -6294,6 +6330,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7212,6 +7255,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		/* attempt to lookup stats in relation for this index column */
 		if (attnum != 0)
 		{
+			char	rel_persistence = get_rel_persistence(rte->relid);
+
 			/* Simple variable -- look to stats for the underlying table */
 			if (get_relation_stats_hook &&
 				(*get_relation_stats_hook) (root, rte, attnum, &vardata))
@@ -7224,6 +7269,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7236,6 +7289,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		}
 		else
 		{
+			char	rel_persistence = get_rel_persistence(index->indexoid);
+
 			/*
 			 * Looks like we've found an expression column in the index. Let's
 			 * see if there's any stats for it.
@@ -7255,6 +7310,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index a7d63f1..d8a52cb 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -2939,6 +2940,18 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 9f1f11d..aeb2a78 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -65,6 +65,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1133,6 +1134,24 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1187,6 +1206,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			gtt_fix_index_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -1317,7 +1337,17 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+			if (newrelnode != InvalidOid &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2246,6 +2276,8 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		gtt_fix_index_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3472,6 +3504,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3579,28 +3615,34 @@ void
 RelationSetNewRelfilenode(Relation relation, char persistence)
 {
 	Oid			newrelfilenode;
-	Relation	pg_class;
-	HeapTuple	tuple;
+	Relation	pg_class = NULL;
+	HeapTuple	tuple = NULL;
 	Form_pg_class classform;
 	MultiXactId minmulti = InvalidMultiXactId;
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
+	bool		modify_pg_class = !RELATION_IS_GLOBAL_TEMP(relation);
 
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
 
-	/*
-	 * Get a writable copy of the pg_class tuple for the given relation.
-	 */
-	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+	if (modify_pg_class)
+	{
+		/*
+		 * Get a writable copy of the pg_class tuple for the given relation.
+		 */
+		pg_class = table_open(RelationRelationId, RowExclusiveLock);
 
-	tuple = SearchSysCacheCopy1(RELOID,
-								ObjectIdGetDatum(RelationGetRelid(relation)));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "could not find tuple for relation %u",
-			 RelationGetRelid(relation));
-	classform = (Form_pg_class) GETSTRUCT(tuple);
+		tuple = SearchSysCacheCopy1(RELOID,
+									ObjectIdGetDatum(RelationGetRelid(relation)));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u",
+				 RelationGetRelid(relation));
+		classform = (Form_pg_class) GETSTRUCT(tuple);
+	}
+	else
+		memset(&classform, 0, sizeof(classform));
 
 	/*
 	 * Schedule unlinking of the old storage at transaction commit.
@@ -3626,7 +3668,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;
@@ -3646,6 +3688,15 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 			break;
 	}
 
+	if (!modify_pg_class)
+	{
+		Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+		Assert(RELATION_IS_GLOBAL_TEMP(relation));
+		Assert(!RelationIsMapped(relation));
+		relation->rd_node.relNode = relnode;
+		CacheInvalidateRelcache(relation);
+	}
 	/*
 	 * If we're dealing with a mapped index, pg_class.relfilenode doesn't
 	 * change; instead we have to send the update to the relation mapper.
@@ -3655,7 +3706,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	 * possibly-inaccurate values of relpages etc, but those will be fixed up
 	 * later.
 	 */
-	if (RelationIsMapped(relation))
+	else if (RelationIsMapped(relation))
 	{
 		/* This case is only supported for indexes */
 		Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3701,9 +3752,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 		CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 	}
 
-	heap_freetuple(tuple);
+	if (modify_pg_class)
+	{
+		heap_freetuple(tuple);
 
-	table_close(pg_class, RowExclusiveLock);
+		table_close(pg_class, RowExclusiveLock);
+	}
 
 	/*
 	 * Make the pg_class row change or relation map change visible.  This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 5bdc02f..b52b51d 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -37,6 +37,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "commands/prepare.h"
 #include "commands/trigger.h"
@@ -142,6 +143,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temp table feature.
+ * global temp table define can still storage in catalog
+ * just can not use.
+ * num > 0 means database can management num active global temp table.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2070,6 +2083,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."),
@@ -2577,6 +2599,16 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"vacuum_gtt_defer_check_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("The defer check age of GTT, used to check expired data after vacuum."),
+			NULL
+		},
+		&vacuum_gtt_defer_check_age,
+		10000, 0, 1000000,
+		NULL, NULL, NULL
+	},
+
 	/*
 	 * See also CheckRequiredParameterValues() if this parameter changes
 	 */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 5db4f57..018a9bf 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2424,6 +2424,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
 		dopt->no_unlogged_table_data)
 		return;
 
+	/* Don't dump data in global temp table/sequence */
+	if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		return;
+
 	/* Check that the data is not explicitly excluded */
 	if (simple_oid_list_member(&tabledata_exclude_oids,
 							   tbinfo->dobj.catId.oid))
@@ -15777,6 +15781,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
 		char	   *foreign = "";
+		char	   *table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15830,9 +15835,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);
 
@@ -16226,7 +16237,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
-			 tbinfo->relkind == RELKIND_MATVIEW))
+			 tbinfo->relkind == RELKIND_MATVIEW) &&
+			 tbinfo->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
 		{
 			appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
 			appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
@@ -17131,6 +17143,7 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	PQExpBuffer query = createPQExpBuffer();
 	PQExpBuffer delqry = createPQExpBuffer();
 	char	   *qseqname;
+	bool	   global_temp_seq = false;
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
@@ -17140,9 +17153,11 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
-						  "FROM pg_catalog.pg_sequence "
-						  "WHERE seqrelid = '%u'::oid",
+						  "seqcache, seqcycle, c.relpersistence "
+						  "FROM pg_catalog.pg_sequence s, "
+						  "pg_catalog.pg_class c "
+						  "WHERE seqrelid = '%u'::oid "
+						  "and s.seqrelid = c.oid",
 						  tbinfo->dobj.catId.oid);
 	}
 	else if (fout->remoteVersion >= 80400)
@@ -17187,6 +17202,9 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
 
+	if (fout->remoteVersion >= 130000)
+		global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
 	if (strcmp(seqtype, "smallint") == 0)
@@ -17261,9 +17279,13 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	}
 	else
 	{
-		appendPQExpBuffer(query,
-						  "CREATE SEQUENCE %s\n",
-						  fmtQualifiedDumpable(tbinfo));
+		appendPQExpBuffer(query, "CREATE ");
+
+		if (global_temp_seq)
+			appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+		appendPQExpBuffer(query, "SEQUENCE %s\n",
+								  fmtQualifiedDumpable(tbinfo));
 
 		if (strcmp(seqtype, "bigint") != 0)
 			appendPQExpBuffer(query, "    AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 00aef85..9ad71c8 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -84,7 +84,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -157,7 +157,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 7e524ea..371168f 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -441,22 +443,46 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 	 * output, so we have to copy that system table.  It's easiest to do that
 	 * by treating it as a user table.
 	 */
-	snprintf(query + strlen(query), sizeof(query) - strlen(query),
-			 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
-			 "  SELECT c.oid, 0::oid, 0::oid "
-			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
-			 "         ON c.relnamespace = n.oid "
-			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
-	/* exclude possible orphaned temp tables */
-			 "    ((n.nspname !~ '^pg_temp_' AND "
-			 "      n.nspname !~ '^pg_toast_temp_' AND "
-			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
-			 "                        'binary_upgrade', 'pg_toast') AND "
-			 "      c.oid >= %u::pg_catalog.oid) OR "
-			 "     (n.nspname = 'pg_catalog' AND "
-			 "      relname IN ('pg_largeobject') ))), ",
-			 FirstNormalObjectId);
+	if (skip_gtt)
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude global temp tables */
+				 "    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
+	else
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
 
 	/*
 	 * Add a CTE that collects OIDs of toast tables belonging to the tables
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 70194eb..8dd9e66 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -411,7 +411,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -642,7 +642,9 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -653,7 +655,9 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 8b90cef..d155205 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -389,7 +389,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index f05e914..19adab9 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3724,7 +3724,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index f6fd623..ea87bc4 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1023,6 +1023,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2394,6 +2396,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2602,6 +2607,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index cbfdfe2..be39472 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -59,7 +59,8 @@ extern Relation heap_create(const char *relname,
 							bool mapped_relation,
 							bool allow_system_table_mods,
 							TransactionId *relfrozenxid,
-							MultiXactId *relminmxid);
+							MultiXactId *relminmxid,
+							bool skip_create_storage);
 
 extern Oid	heap_create_with_catalog(const char *relname,
 									 Oid relnamespace,
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 78b33b2..4be89f6 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -168,6 +168,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad..c8236a1 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5558,6 +5558,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4388',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4389',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4390',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4391',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 30c38e0..7ff2408 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,7 +22,7 @@
 /* GUC variables */
 extern int	wal_skip_threshold;
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationPreTruncate(Relation rel);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..902375d
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern int		vacuum_gtt_defer_check_age;
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Relation relation,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void gtt_force_enable_index(Relation index);
+extern void gtt_fix_index_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+extern void gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index e2638ab..89a5ce4 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 3f88683..f870e9a 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 8fda8e4..172eae9 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_TBM,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_SXACT,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index ae4f573..69cce92 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -117,6 +117,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId session_gtt_frozenxid;	/* session level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 2819282..363fc33 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -282,6 +282,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 0b5957b..0720a4c 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	struct SMgrRelationData *rd_smgr;	/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -305,6 +305,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -570,11 +571,13 @@ typedef struct ViewOptions
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -582,6 +585,7 @@ typedef struct ViewOptions
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -594,6 +598,14 @@ typedef struct ViewOptions
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is either local temp relation of this session
+ * 		or global temp relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -637,6 +649,17 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..ca2d135
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,14 @@
+reset search_path;
+select pg_sleep(5);
+ pg_sleep 
+----------
+ 
+(1 row)
+
+drop schema gtt cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
+drop cascades to table gtt.gtt_with_seq
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..d6a675a
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,383 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+ n 
+---
+ 9
+(1 row)
+
+commit;
+-- 0 row
+select * from gtt6;
+ n 
+---
+(0 rows)
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temp table
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  could not create global temporary table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR:  insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count 
+-------
+     1
+(1 row)
+
+select count(*) from orders;
+ count 
+-------
+     0
+(1 row)
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  2 |  3
+(2 rows)
+
+commit;
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+select * from gtt_s_2 order by c1;
+ c1 | c2 
+----+----
+  1 |  2
+  2 |  4
+(2 rows)
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 1)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 300)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 33 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test_new
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.gtt6
+drop cascades to table gtt_function.foo
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to sequence gtt_function.seq_1
+drop cascades to table gtt_function.gtt_s_1
+drop cascades to table gtt_function.gtt_s_2
+drop cascades to table gtt_function.gt1
+drop cascades to table gtt_function.gtt_test1
+drop cascades to table gtt_function.gtt_test2
+drop cascades to table gtt_function.gtt_test3
+drop cascades to table gtt_function.gtt_test4
+drop cascades to table gtt_function.gtt_test5
+drop cascades to table gtt_function.gtt_test6
+drop cascades to table gtt_function.gtt_test7
+drop cascades to table gtt_function.gtt_test8
+drop cascades to table gtt_function.gtt_test9
+drop cascades to table gtt_function.gtt_test10
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..0646aae
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,90 @@
+set search_path=gtt,sys;
+select nextval('gtt_with_seq_c2_seq');
+ nextval 
+---------
+       1
+(1 row)
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..2f084be
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,343 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+rollback;
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+commit;
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          114688 |                 614400
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |            49152 |         49152 |                  49152
+(2 rows)
+
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2000
+(1 row)
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+ c1 | c2 
+----+----
+  1 |  1
+(1 row)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..8c0c376
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,10 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..4420fdb
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,80 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+ gtt_stats  | gtt_pkey  |        1 |         0 |             0
+(2 rows)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+reindex table gtt;
+reindex index gtt_pkey;
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+ gtt_stats  | gtt_pkey  |       30 |     10000 |             0
+(2 rows)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index ac31840..55b221e 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1349,6 +1349,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 95f1925..76b2374 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..2c8e586
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,8 @@
+
+
+reset search_path;
+
+select pg_sleep(5);
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..81f0bfc
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,253 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+commit;
+-- 0 row
+select * from gtt6;
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+commit;
+select * from gtt_s_1 order by c1;
+select * from gtt_s_2 order by c1;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d05745e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,44 @@
+
+
+set search_path=gtt,sys;
+
+select nextval('gtt_with_seq_c2_seq');
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..39cca5e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,154 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+rollback;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+commit;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+select count(*) from gtt_t_kenyon;
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..dbe84d1
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,19 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..d61b0ff
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,46 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+reindex table gtt;
+
+reindex index gtt_pkey;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#253曾文旌
wenjing.zwj@alibaba-inc.com
In reply to: Pavel Stehule (#225)
2 attachment(s)
Re: [Proposal] Global temporary tables

2020年4月3日 下午4:38,Pavel Stehule <pavel.stehule@gmail.com> 写道:

pá 3. 4. 2020 v 9:52 odesílatel 曾文旌 <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:
In my opinion
1 We are developing GTT according to the SQL standard, not Oracle.

2 The implementation differences you listed come from pg and oracle storage modules and DDL implementations.

2.1 issue 1 and issue 2
The creation of Normal table/GTT defines the catalog and initializes the data store file, in the case of the GTT, which initializes the store file for the current session.
But in oracle It just looks like only defines the catalog.
This causes other sessions can not drop the GTT in PostgreSQL.
This is the reason for issue 1 and issue 2, I think it is reasonable.

2.2 issue 3
I thinking the logic of drop GTT is
When only the current session is using the GTT, it is safe to drop the GTT.
because the GTT's definition and storage files can completely delete from db.
But, If multiple sessions are using this GTT, it is hard to drop GTT in session a, because remove the local buffer and data file of the GTT in other session is difficult.
I am not sure why oracle has this limitation.
So, issue 3 is reasonable.

2.3 TRUNCATE Normal table/GTT
TRUNCATE Normal table / GTT clean up the logical data but not unlink data store file. in the case of the GTT, which is the store file for the current session.
But in oracle, It just looks like data store file was cleaned up.
PostgreSQL storage is obviously different from oracle, In other words, session is detached from storage.
This is the reason for issue4 I think it is reasonable.

Although the implementation of GTT is different, I think so TRUNCATE on Postgres (when it is really finalized) can remove session metadata of GTT too (and reduce usage's counter). It is not critical feature, but I think so it should not be hard to implement. From practical reason can be nice to have a tool how to refresh GTT without a necessity to close session. TRUNCATE can be this tool.

Yes, I think we need a way to delete the GTT local storage without closing the session.

I provide the TRUNCATE tablename DROP to clear the data in the GTT and delete the storage files.
This feature requires the current transaction to commit immediately after it finishes truncate.

Wenjing

Show quoted text

Regards

Pavel

All in all, I think the current implementation is sufficient for dba to manage GTT.

2020年4月2日 下午4:45,Prabhat Sahu <prabhat.sahu@enterprisedb.com <mailto:prabhat.sahu@enterprisedb.com>> 写道:

Hi All,

I have noted down few behavioral difference in our GTT implementation in PG as compared to Oracle DB:
As per my understanding, the behavior of DROP TABLE in case of "Normal table and GTT" in Oracle DB are as below:
Any tables(Normal table / GTT) without having data in a session, we will be able to DROP from another session.
For a completed transaction on a normal table having data, we will be able to DROP from another session. If the transaction is not yet complete, and we are trying to drop the table from another session, then we will get an error. (working as expected)
For a completed transaction on GTT with(on commit delete rows) (i.e. no data in GTT) in a session, we will be able to DROP from another session.
For a completed transaction on GTT with(on commit preserve rows) with data in a session, we will not be able to DROP from any session(not even from the session in which GTT is created), we need to truncate the table data first from all the session(session1, session2) which is having data.
1. Any tables(Normal table / GTT) without having data in a session, we will be able to DROP from another session.
Session1:
create table t1 (c1 integer);
create global temporary table gtt1 (c1 integer) on commit delete rows;
create global temporary table gtt2 (c1 integer) on commit preserve rows;

Session2:
drop table t1;
drop table gtt1;
drop table gtt2;

-- Issue 1: But we are able to drop a simple table and failed to drop GTT as below.
postgres=# drop table t1;
DROP TABLE
postgres=# drop table gtt1;
ERROR: can not drop relation gtt1 when other backend attached this global temp table
postgres=# drop table gtt2;
ERROR: can not drop relation gtt2 when other backend attached this global temp table

3. For a completed transaction on GTT with(on commit delete rows) (i.e. no data in GTT) in a session, we will be able to DROP from another session.
Session1:
create global temporary table gtt1 (c1 integer) on commit delete rows;

Session2:
drop table gtt1;

-- Issue 2: But we are getting error for GTT with(on_commit_delete_rows) without data.
postgres=# drop table gtt1;
ERROR: can not drop relation gtt1 when other backend attached this global temp table

4. For a completed transaction on GTT with(on commit preserve rows) with data in any session, we will not be able to DROP from any session(not even from the session in which GTT is created)

Case1:
create global temporary table gtt2 (c1 integer) on commit preserve rows;
insert into gtt2 values(100);
drop table gtt2;

SQL> drop table gtt2;
drop table gtt2
*
ERROR at line 1:
ORA-14452: attempt to create, alter or drop an index on temporary table already in use

-- Issue 3: But, we are able to drop the GTT(having data) which we have created in the same session.
postgres=# drop table gtt2;
DROP TABLE

Case2: GTT with(on commit preserve rows) having data in both session1 and session2
Session1:
create global temporary table gtt2 (c1 integer) on commit preserve rows;
insert into gtt2 values(100);

Session2:
insert into gtt2 values(200);

-- If we try to drop the table from any session we should get an error, it is working fine.
drop table gtt2;
SQL> drop table gtt2;
drop table gtt2
*
ERROR at line 1:
ORA-14452: attempt to create, alter or drop an index on temporary table already in use

postgres=# drop table gtt2 ;
ERROR: can not drop relation gtt2 when other backend attached this global temp table

-- To drop the table gtt2 from any session1/session2, we need to truncate the table data first from all the session(session1, session2) which is having data.
Session1:
truncate table gtt2;
-- Session2:
truncate table gtt2;

Session 2:
SQL> drop table gtt2;

Table dropped.

-- Issue 4: But we are not able to drop the GTT, even after TRUNCATE the table in all the sessions.
-- truncate from all sessions where GTT have data.
postgres=# truncate gtt2 ;
TRUNCATE TABLE

-- try to DROP GTT still, we are getting error.
postgres=# drop table gtt2 ;
ERROR: can not drop relation gtt2 when other backend attached this global temp table

To drop the GTT from any session, we need to exit from all other sessions.
postgres=# drop table gtt2 ;
DROP TABLE

Kindly let me know if I am missing something.

On Wed, Apr 1, 2020 at 6:26 PM Prabhat Sahu <prabhat.sahu@enterprisedb.com <mailto:prabhat.sahu@enterprisedb.com>> wrote:
Hi Wenjing,
I hope we need to change the below error message.

postgres=# create global temporary table gtt(c1 int) on commit preserve rows;
CREATE TABLE

postgres=# create materialized view mvw as select * from gtt;
ERROR: materialized views must not use global temporary tables or views

Anyways we are not allowed to create a "global temporary view",
so the above ERROR message should change(i.e. " or view" need to be removed from the error message) something like:
"ERROR: materialized views must not use global temporary tables"

--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

Attachments:

global_temporary_table_v30-pg13.patchapplication/octet-stream; name=global_temporary_table_v30-pg13.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 8ccc228..b061de9 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -168,6 +168,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * For global temp table only
+	 * use ShareUpdateExclusiveLock for ensure safety
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temp table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},	
 	/* list terminator */
 	{{NULL}}
 };
@@ -1817,6 +1830,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1961,13 +1976,18 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	/*
 	 * There are no options for partitioned tables yet, but this is able to do
 	 * some validation.
 	 */
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index 765329b..4761fdc 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1011,7 +1011,9 @@ gistproperty(Oid index_oid, int attno,
 XLogRecPtr
 gistGetFakeLSN(Relation rel)
 {
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	/* global temp is same as local temp table */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 3ec6d52..1222594 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -150,7 +150,9 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	/* global temp table is same as local temp table */
+	if (!(index->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		index->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 56b3562..d3443ec 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -587,7 +587,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -640,7 +640,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 3c18db2..b08a1fc 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -62,6 +62,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -431,9 +432,9 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
 	Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
-	/* not every AM requires these to be valid, but heap does */
-	Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
-	Assert(MultiXactIdIsValid(onerel->rd_rel->relminmxid));
+	/* not every AM requires these to be valid, but regular heap does */
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ MultiXactIdIsValid(onerel->rd_rel->relminmxid));
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 39b8f17..abb76dc 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -601,6 +602,10 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 11e3273..32694c9 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6422,6 +6422,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/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 5eaca27..e5002aa 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -231,7 +231,8 @@ Boot_CreateStmt:
 												   mapped_relation,
 												   true,
 												   &relfrozenxid,
-												   &relminmxid);
+												   &relminmxid,
+												   false);
 						elog(DEBUG4, "bootstrap relation created");
 					}
 					else
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 9499bb3..ae47364 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -43,6 +43,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 7d6acae..3be8d63 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -393,7 +393,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/* global temp table is same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 632c058..9511182 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -304,7 +306,8 @@ heap_create(const char *relname,
 			bool mapped_relation,
 			bool allow_system_table_mods,
 			TransactionId *relfrozenxid,
-			MultiXactId *relminmxid)
+			MultiXactId *relminmxid,
+			bool skip_create_storage)
 {
 	bool		create_storage;
 	Relation	rel;
@@ -404,6 +407,9 @@ heap_create(const char *relname,
 									 relpersistence,
 									 relkind);
 
+	if (skip_create_storage)
+		create_storage = false;
+
 	/*
 	 * Have the storage manager create the relation's disk file, if needed.
 	 *
@@ -427,7 +433,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -959,6 +965,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -997,8 +1004,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;
@@ -1261,7 +1278,8 @@ heap_create_with_catalog(const char *relname,
 							   mapped_relation,
 							   allow_system_table_mods,
 							   &relfrozenxid,
-							   &relminmxid);
+							   &relminmxid,
+							   false);
 
 	Assert(relid == RelationGetRelid(new_rel_desc));
 
@@ -1360,6 +1378,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1940,6 +1959,14 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/* We allow to drop global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			elog(ERROR, "can not drop relation %s when other backend attached this global temp table",
+						RelationGetRelationName(rel));
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3167,7 +3194,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3179,7 +3206,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3225,8 +3252,13 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
+
+		/* truncate global temp table only need RowExclusiveLock */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3259,6 +3291,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3267,23 +3300,37 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (!gtt_storage_attached(RelationGetRelid(rel)))
+			return;
+
+		/*
+		 * Truncate global temp table only need RowExclusiveLock
+		 */
+		lockmode = RowExclusiveLock;
+	}
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(rel, 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 7cfbdd5..57375ec 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -53,6 +53,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -727,6 +728,11 @@ index_create(Relation heapRelation,
 	char		relkind;
 	TransactionId relfrozenxid;
 	MultiXactId relminmxid;
+	bool		skip_create_storage = false;
+
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation) &&
+		!gtt_storage_attached(RelationGetRelid(heapRelation)))
+		skip_create_storage = true;
 
 	/* constraint flags can only be set when a constraint is requested */
 	Assert((constr_flags == 0) ||
@@ -880,6 +886,19 @@ index_create(Relation heapRelation,
 						indexRelationName, RelationGetRelationName(heapRelation))));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temp table use concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot reindex global temporary tables concurrently")));
+
+		/* if global temp table not init storage, then skip build index */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -934,7 +953,8 @@ index_create(Relation heapRelation,
 								mapped_relation,
 								allow_system_table_mods,
 								&relfrozenxid,
-								&relminmxid);
+								&relminmxid,
+								skip_create_storage);
 
 	Assert(relfrozenxid == InvalidTransactionId);
 	Assert(relminmxid == InvalidMultiXactId);
@@ -2040,7 +2060,8 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 * lock (see comments in RemoveRelations), and a non-concurrent DROP is
 	 * more efficient.
 	 */
-	Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+	Assert(!(get_rel_persistence(indexId) == RELPERSISTENCE_TEMP ||
+			 get_rel_persistence(indexId) == RELPERSISTENCE_GLOBAL_TEMP) ||
 		   (!concurrent && !concurrent_lock_mode));
 
 	/*
@@ -2072,6 +2093,14 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/* We allow to drop index on global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+			elog(ERROR, "can not drop index %s when other backend attached this global temp table.",
+						RelationGetRelationName(userHeapRelation));
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2680,6 +2709,11 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	/* update index stats into localhash and rel_rd_rel for global temp table */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		is_gtt = true;
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2765,21 +2799,35 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
+		if (is_gtt)
+			rel->rd_rel->relpages = (int32) relpages;
+		else if (rd_rel->relpages != (int32) relpages)
 		{
 			rd_rel->relpages = (int32) relpages;
 			dirty = true;
 		}
-		if (rd_rel->reltuples != (float4) reltuples)
+
+		if (is_gtt)
+			rel->rd_rel->reltuples = (float4) reltuples;
+		else if (rd_rel->reltuples != (float4) reltuples)
 		{
 			rd_rel->reltuples = (float4) reltuples;
 			dirty = true;
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+
+		if (is_gtt)
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+		else if (rd_rel->relallvisible != (int32) relallvisible)
 		{
 			rd_rel->relallvisible = (int32) relallvisible;
 			dirty = true;
 		}
+
+		if (is_gtt)
+		{
+			up_gtt_relstats(rel, relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
+		}
 	}
 
 	/*
@@ -2893,6 +2941,15 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, index, val);
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			gtt_force_enable_index(indexRelation);
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3441,6 +3498,10 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	PGRUsage	ru0;
 	bool		progress = (options & REINDEXOPT_REPORT_PROGRESS) != 0;
 
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		!gtt_storage_attached(indexId))
+		return;
+
 	pg_rusage_init(&ru0);
 
 	/*
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 2ec2301..1b6061d 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -655,6 +655,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		/* global temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp relations in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index d713d5c..afcf004 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,6 +27,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -61,6 +62,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			relOid;			/* InvalidOid if not a global temp rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -115,7 +117,7 @@ AddPendingSync(const RelFileNode *rnode)
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -127,6 +129,8 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		/* global temp table use same storage strategy as local temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -154,6 +158,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->relOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
@@ -165,6 +170,13 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		AddPendingSync(&rnode);
 	}
 
+	/* remember global temp table storage info to localhash */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel)
+	{
+		pending->relOid = RelationGetRelid(rel);
+		remember_gtt_storage_info(rnode, rel);
+	}
+
 	return srel;
 }
 
@@ -201,11 +213,15 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->relOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->relOid = RelationGetRelid(rel);
+
 	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
@@ -602,6 +618,7 @@ smgrDoPendingDeletes(bool isCommit)
 				i = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -631,14 +648,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->relOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -651,9 +672,18 @@ smgrDoPendingDeletes(bool isCommit)
 		smgrdounlinkall(srels, nrels, false);
 
 		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* clean global temp table flags when transaction commit or rollback */
+			if (SmgrIsTemp(srels[i]) &&
+				reloids[i] != InvalidOid &&
+				gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..832fccd
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1535 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  code to create and destroy physical storage for global temparary table
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_relstats_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+int		vacuum_gtt_defer_check_age = 0;
+
+typedef struct gtt_ctl_data
+{
+	LWLock			lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+	Oid			relfilenode;
+	Oid			spcnode;
+
+	/* pg_class stat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+
+	/* pg_statistic */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+
+	Oid			oldrelid;			/* remember the source of relid, before the switch relfilenode. */
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_reset_statistics(gtt_local_hash_entry *entry);
+static void gtt_free_statistics(gtt_local_hash_entry *entry);
+static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok);
+static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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();
+	}
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(gtt_fnode);
+	info.entrysize = action_gtt_shared_hash_entry_size();
+	active_gtt_shared_hash =
+		ShmemInitHash("active gtt shared hash",
+						gtt_shared_ctl->max_entry,
+						gtt_shared_ctl->max_entry,
+						&info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			found;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *)&(fnode), 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_global_temporary_table.")));
+	}
+
+	if (found == false)
+	{
+		int			wordnum;
+
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode				fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	if (!skiplock)
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		if (!skiplock)
+			LWLockRelease(&gtt_shared_ctl->lock);
+
+		if (isCommit)
+			elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+	bms_del_member(entry->map, MyBackendId);
+
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+
+	if (!skiplock)
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+	return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	Bitmapset	*map_copy = NULL;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool		in_use = false;
+	int			num_use = 0;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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;
+	gtt_relfilenode			*new_node = NULL;
+	Oid						relid = RelationGetRelid(rel);
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		/* First time through: initialize the hash table */
+		HASHCTL		ctl;
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_relstats_context =
+			AllocSetContextCreate(CacheMemoryContext,
+							"gtt relstats context",
+							ALLOCSET_DEFAULT_SIZES);
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+	entry = gtt_search_by_relid(relid, true);
+	if (!entry)
+	{
+		bool		found = false;
+		int 		natts = 0;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash,
+						(void *) &relid, HASH_ENTER, &found);
+
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+		entry->natts = 0;
+		entry->attnum = NULL;
+		entry->att_stat_tups = NULL;
+		entry->oldrelid = InvalidOid;
+
+		natts = RelationGetNumberOfAttributes(rel);
+		entry->attnum = palloc0(sizeof(int) * natts);
+		entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+		entry->natts = natts;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			gtt_storage_checkin(relid);
+		}
+	}
+
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* only heap contain transaction information */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	gtt_reset_statistics(entry);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	gtt_relfilenode *d_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+		return;
+	}
+
+	d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+		else if (entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+			gtt_relfilenode *gttnode2 = NULL;
+
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+			Assert(gttnode2->relfilenode == rnode.relNode);
+			Assert(list_length(entry->relfilenode_list) == 1);
+			/* rollback switch relfilenode */
+			gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+									   entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+									   false);
+			/* clean up footprint */
+			entry2->oldrelid = InvalidOid;
+			d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+			Assert(d_rnode);
+		}
+		else
+		{
+			if (entry->relfilenode_list == NIL)
+			{
+				if (entry->relkind == RELKIND_RELATION ||
+					entry->relkind == RELKIND_SEQUENCE)
+					gtt_storage_checkout(relid, false, isCommit);
+
+				gtt_free_statistics(entry);
+				hash_search(gtt_storage_local_hash,
+						(void *) &(relid), HASH_REMOVE, NULL);
+			}
+
+			return;
+		}
+	}
+
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	pfree(d_rnode);
+	if (entry->relfilenode_list == NIL)
+	{
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(relid, false, isCommit);
+
+		if (isCommit && entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			/* clean up footprint */
+			entry2->oldrelid = InvalidOid;
+		}
+
+		gtt_free_statistics(entry);
+		hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_REMOVE, NULL);
+	}
+	else
+		gtt_reset_statistics(entry);
+
+	return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool found = false;
+	gtt_local_hash_entry *entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry)
+		found = true;
+
+	return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS				status;
+	gtt_local_hash_entry		*entry;
+	SMgrRelation	*srels = NULL;
+	Oid				*relids = NULL;
+	char			*relkinds = NULL;
+	int			nrels = 0,
+				nfiles = 0,
+				maxrels = 0,
+				maxfiles = 0,
+				i = 0;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel;
+			RelFileNode 	rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel = smgropen(rnode, MyBackendId);
+
+			if (maxfiles == 0)
+			{
+				maxfiles = 32;
+				srels = palloc(sizeof(SMgrRelation) * maxfiles);
+			}
+			else if (maxfiles <= nfiles)
+			{
+				maxfiles *= 2;
+				srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+			}
+
+			srels[nfiles++] = srel;
+		}
+
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			relids  = palloc(sizeof(Oid) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			relids  = repalloc(relids , sizeof(Oid) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		relkinds[nrels] = entry->relkind;
+		relids[nrels] = entry->relid;
+		nrels++;
+	}
+
+	if (nfiles > 0)
+	{
+		smgrdounlinkall(srels, nfiles, false);
+		for (i = 0; i < nfiles; i++)
+			smgrclose(srels[i]);
+
+		pfree(srels);
+	}
+
+	if (nrels)
+	{
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+		for (i = 0; i < nrels; i++)
+		{
+			if (relkinds[i] == RELKIND_RELATION ||
+				relkinds[i] == RELKIND_SEQUENCE)
+				gtt_storage_checkout(relids[i], true, false);
+		}
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+		pfree(relids);
+		pfree(relkinds);
+	}
+
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+	return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible) 
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+					BlockNumber num_pages,
+					double num_tuples,
+					BlockNumber num_all_visible_pages,
+					TransactionId relfrozenxid,
+					TransactionId relminmxid)
+{
+	Oid		relid = RelationGetRelid(relation);
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (num_pages >= 0 &&
+		gtt_rnode->relpages != (int32)num_pages)
+		gtt_rnode->relpages = (int32)num_pages;
+
+	if (num_tuples >= 0 &&
+		gtt_rnode->reltuples != (float4)num_tuples)
+		gtt_rnode->reltuples = (float4)num_tuples;
+
+	/* only heap contain transaction information and relallvisible */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (gtt_rnode->relallvisible >= 0 &&
+			gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+		{
+			gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+		}
+
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNewTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible) 
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	MemoryContext			oldcontext;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return;
+
+	if (entry->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	Assert(entry->relid == reloid);
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == 0)
+		{
+			entry->attnum[i] = attnum;
+			break;
+		}
+		else if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < entry->natts);
+	Assert(entry->att_stat_tups[i] == NULL);
+	entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return NULL;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			return entry->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int			i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	HeapTuple	tuple;
+	Relation	rel = NULL;
+	int		attnum = PG_GETARG_INT32(1);
+	Oid		reloid = PG_GETARG_OID(0);
+	char		rel_persistence;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	  tupdesc;
+	MemoryContext oldcontext;
+	Tuplestorestate *tupstore;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+
+	relation_close(rel, NoLock);
+	relation_close(pg_tatistic, AccessShareLock);
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate	*tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Oid			relnode = 0;
+	Relation	rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[6];
+		bool	isnull[6];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(reloid);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(RelationGetRelid(index)));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid relOid = index->rd_index->indrelid;
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	if (gtt_storage_attached(relOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int			i;
+	Oid			toastrelid;
+
+	if (!(operation == CMD_UTILITY || operation == CMD_INSERT))
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+
+		index_build(relation, index, info, true, false);
+	}
+
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid 		indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo 	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+static void
+gtt_reset_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+
+		entry->attnum[i] = 0;
+	}
+
+	return;
+}
+
+static void
+gtt_free_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (entry->attnum)
+		pfree(entry->attnum);
+
+	if (entry->att_stat_tups)
+		pfree(entry->att_stat_tups);
+
+	return;
+}
+
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
+void
+gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint)
+{
+	gtt_local_hash_entry	*entry1;
+	gtt_local_hash_entry	*entry2;
+	gtt_relfilenode			*gtt_rnode1 = NULL;
+	gtt_relfilenode			*gtt_rnode2 = NULL;
+	MemoryContext			oldcontext;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry1 = gtt_search_by_relid(rel1, false);
+	gtt_rnode1 = gtt_search_relfilenode(entry1, relfilenode1, false);
+
+	entry2 = gtt_search_by_relid(rel2, false);
+	gtt_rnode2 = gtt_search_relfilenode(entry2, relfilenode2, false);
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	entry1->relfilenode_list = list_delete_ptr(entry1->relfilenode_list, gtt_rnode1);
+	entry2->relfilenode_list = lappend(entry2->relfilenode_list, gtt_rnode1);
+
+	entry2->relfilenode_list = list_delete_ptr(entry2->relfilenode_list, gtt_rnode2);
+	entry1->relfilenode_list = lappend(entry1->relfilenode_list, gtt_rnode2);
+	MemoryContextSwitchTo(oldcontext);
+
+	if (footprint)
+	{
+		entry1->oldrelid = rel2;
+		entry2->oldrelid = rel1;
+	}
+
+	return;
+}
+
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok)
+{
+	gtt_relfilenode			*rnode = NULL;
+	ListCell				*lc;
+
+	Assert(entry);
+
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == relfilenode)
+		{
+			rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (!missing_ok && rnode == NULL)
+		elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+	return rnode;
+}
+
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry == NULL && !missing_ok)
+		elog(ERROR, "relid %u not found in local hash", relid);
+
+	return entry;
+}
+
+void
+gtt_drop_storage(Relation rel)
+{
+	LOCKMODE	lockmode = AccessExclusiveLock;
+	ListCell   *indlist;
+	Oid			toastrelid = InvalidOid;
+
+	if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+		remove_on_commit_action(RelationGetRelid(rel));
+
+	RelationDropStorage(rel);
+
+	foreach(indlist, RelationGetIndexList(rel))
+	{
+		Oid 		indexId = lfirst_oid(indlist);
+		Relation	currentIndex;
+
+		currentIndex = index_open(indexId, lockmode);
+		if (gtt_storage_attached(indexId))
+			RelationDropStorage(currentIndex);
+
+		index_close(currentIndex, NoLock);
+		RelationForgetRelation(indexId);
+	}
+
+	if (rel->rd_rel->relkind == RELKIND_RELATION)
+	{
+		toastrelid = rel->rd_rel->reltoastrelid;
+		if (OidIsValid(rel->rd_rel->reltoastrelid))
+		{
+			Relation	toastrel = table_open(toastrelid, lockmode);
+			Oid			toastrelid = RelationGetRelid(toastrel);
+
+			if (gtt_storage_attached(toastrelid))
+				gtt_drop_storage(toastrel);
+
+			table_close(toastrel, NoLock);
+			RelationForgetRelation(toastrelid);
+		}
+	}
+
+	return;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index d406ea8..88be041 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM 
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 924ef37..db12eef 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -184,6 +185,13 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -586,14 +594,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 		 * pg_statistic for columns we didn't process, we leave them alone.)
 		 */
 		update_attstats(RelationGetRelid(onerel), inh,
-						attr_cnt, vacattrstats);
+						attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
 
 		for (ind = 0; ind < nindexes; ind++)
 		{
 			AnlIndexData *thisdata = &indexdata[ind];
 
 			update_attstats(RelationGetRelid(Irel[ind]), false,
-							thisdata->attr_cnt, thisdata->vacattrstats);
+							thisdata->attr_cnt, thisdata->vacattrstats,
+							RelationGetRelPersistence(Irel[ind]));
 		}
 
 		/*
@@ -1456,7 +1465,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
  *		by taking a self-exclusive lock on the relation in analyze_rel().
  */
 static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
 {
 	Relation	sd;
 	int			attno;
@@ -1558,31 +1567,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/* Update column statistic to localhash, not catalog */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 04d12a7..42c82c8 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/toasting.h"
 #include "commands/cluster.h"
 #include "commands/progress.h"
@@ -72,6 +73,12 @@ static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
 							bool verbose, bool *pSwapToastByContent,
 							TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
 static List *get_tables_to_cluster(MemoryContext cluster_context);
+static void gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables);
 
 
 /*---------------------------------------------------------------------------
@@ -366,6 +373,14 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap) &&
+		!gtt_storage_attached(RelationGetRelid(OldHeap)))
+	{
+		relation_close(OldHeap, AccessExclusiveLock);
+		pgstat_progress_end_command();
+		return;
+	}
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
@@ -750,6 +765,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	BlockNumber num_pages;
 	int			elevel = verbose ? INFO : DEBUG2;
 	PGRUsage	ru0;
+	bool		is_gtt = false;
+	uint32		gtt_relfrozenxid = 0;
+	uint32		gtt_relminmxid = 0;
 
 	pg_rusage_init(&ru0);
 
@@ -763,6 +781,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	else
 		OldIndex = NULL;
 
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		is_gtt = true;
+
 	/*
 	 * Their tuple descriptors should be exactly alike, but here we only need
 	 * assume that they have the same number of columns.
@@ -830,20 +851,37 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 						  &OldestXmin, &FreezeXid, NULL, &MultiXactCutoff,
 						  NULL);
 
-	/*
-	 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
-	 * backwards, so take the max.
-	 */
-	if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
-		TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
-		FreezeXid = OldHeap->rd_rel->relfrozenxid;
+	if (is_gtt)
+	{
+		get_gtt_relstats(OIDOldHeap,
+					NULL, NULL, NULL,
+					&gtt_relfrozenxid, &gtt_relminmxid);
 
-	/*
-	 * MultiXactCutoff, similarly, shouldn't go backwards either.
-	 */
-	if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
-		MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
-		MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+		if (TransactionIdIsValid(gtt_relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, gtt_relfrozenxid))
+			FreezeXid = gtt_relfrozenxid;
+
+		if (MultiXactIdIsValid(gtt_relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, gtt_relminmxid))
+			MultiXactCutoff = gtt_relminmxid;
+	}
+	else
+	{
+		/*
+		 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
+		 * backwards, so take the max.
+		 */
+		if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
+			FreezeXid = OldHeap->rd_rel->relfrozenxid;
+
+		/*
+		 * MultiXactCutoff, similarly, shouldn't go backwards either.
+		 */
+		if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
+			MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+	}
 
 	/*
 	 * Decide whether to use an indexscan or seqscan-and-optional-sort to scan
@@ -911,6 +949,12 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	table_close(OldHeap, NoLock);
 	table_close(NewHeap, NoLock);
 
+	if (is_gtt)
+	{
+		CommandCounterIncrement();
+		return;
+	}
+
 	/* Update pg_class to reflect the correct values of pages and tuples. */
 	relRelation = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1346,10 +1390,21 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 	 * Swap the contents of the heap relations (including any toast tables).
 	 * Also set old heap's relfrozenxid to frozenXid.
 	 */
-	swap_relation_files(OIDOldHeap, OIDNewHeap,
+	if (newrelpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		Assert(!is_system_catalog);
+		gtt_swap_relation_files(OIDOldHeap, OIDNewHeap,
+								(OIDOldHeap == RelationRelationId),
+								swap_toast_by_content, is_internal,
+								frozenXid, cutoffMulti, mapped_tables);
+	}
+	else
+	{
+		swap_relation_files(OIDOldHeap, OIDNewHeap,
 						(OIDOldHeap == RelationRelationId),
 						swap_toast_by_content, is_internal,
 						frozenXid, cutoffMulti, mapped_tables);
+	}
 
 	/*
 	 * If it's a system catalog, queue a sinval message to flush all catcaches
@@ -1557,3 +1612,141 @@ get_tables_to_cluster(MemoryContext cluster_context)
 
 	return rvs;
 }
+
+static void
+gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables)
+{
+	Relation	relRelation;
+	Oid			relfilenode1,
+				relfilenode2;
+	Relation	rel1;
+	Relation	rel2;
+
+	relRelation = table_open(RelationRelationId, RowExclusiveLock);
+
+	rel1 = relation_open(r1, AccessExclusiveLock);
+	rel2 = relation_open(r2, AccessExclusiveLock);
+
+	relfilenode1 = gtt_fetch_current_relfilenode(r1);
+	relfilenode2 = gtt_fetch_current_relfilenode(r2);
+
+	Assert(OidIsValid(relfilenode1) && OidIsValid(relfilenode2));
+	gtt_switch_rel_relfilenode(r1, relfilenode1, r2, relfilenode2, true);
+
+	CacheInvalidateRelcache(rel1);
+	CacheInvalidateRelcache(rel2);
+
+	InvokeObjectPostAlterHookArg(RelationRelationId, r1, 0,
+								 InvalidOid, is_internal);
+	InvokeObjectPostAlterHookArg(RelationRelationId, r2, 0,
+								 InvalidOid, true);
+
+	if (rel1->rd_rel->reltoastrelid || rel2->rd_rel->reltoastrelid)
+	{
+		if (swap_toast_by_content)
+		{
+			if (rel1->rd_rel->reltoastrelid && rel2->rd_rel->reltoastrelid)
+			{
+				gtt_swap_relation_files(rel1->rd_rel->reltoastrelid,
+									rel2->rd_rel->reltoastrelid,
+									target_is_pg_class,
+									swap_toast_by_content,
+									is_internal,
+									frozenXid,
+									cutoffMulti,
+									mapped_tables);
+			}
+			else
+				elog(ERROR, "cannot swap toast files by content when there's only one");
+		}
+		else
+		{
+			ObjectAddress baseobject,
+						toastobject;
+			long		count;
+
+			if (IsSystemRelation(rel1))
+				elog(ERROR, "cannot swap toast files by links for system catalogs");
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel1->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel2->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+
+			/* Register new dependencies */
+			baseobject.classId = RelationRelationId;
+			baseobject.objectSubId = 0;
+			toastobject.classId = RelationRelationId;
+			toastobject.objectSubId = 0;
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r1;
+				toastobject.objectId = rel1->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r2;
+				toastobject.objectId = rel2->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+		}
+	}
+
+	if (swap_toast_by_content &&
+		rel1->rd_rel->relkind == RELKIND_TOASTVALUE &&
+		rel2->rd_rel->relkind == RELKIND_TOASTVALUE)
+	{
+		Oid			toastIndex1,
+					toastIndex2;
+
+		/* Get valid index for each relation */
+		toastIndex1 = toast_get_valid_index(r1,
+											AccessExclusiveLock);
+		toastIndex2 = toast_get_valid_index(r2,
+											AccessExclusiveLock);
+
+		gtt_swap_relation_files(toastIndex1,
+							toastIndex2,
+							target_is_pg_class,
+							swap_toast_by_content,
+							is_internal,
+							InvalidTransactionId,
+							InvalidMultiXactId,
+							mapped_tables);
+	}
+
+	relation_close(rel1, NoLock);
+	relation_close(rel2, NoLock);
+
+	table_close(relRelation, RowExclusiveLock);
+
+	RelationCloseSmgrByOid(r1);
+	RelationCloseSmgrByOid(r2);
+
+	CommandCounterIncrement();
+}
+
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index ac07f75..655184d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -27,6 +27,7 @@
 #include "catalog/dependency.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -1063,7 +1064,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, stmt->filename, stmt->is_program,
@@ -2794,6 +2795,8 @@ CopyFrom(CopyState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	estate->es_result_relations = resultRelInfo;
 	estate->es_num_result_relations = 1;
 	estate->es_result_relation_info = resultRelInfo;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 2baca12..5338db9 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -521,6 +521,7 @@ DefineIndex(Oid relationId,
 	Snapshot	snapshot;
 	int			save_nestlevel = -1;
 	int			i;
+	char		rel_persistence;
 
 	/*
 	 * Some callers need us to run with an empty default_tablespace; this is a
@@ -542,7 +543,9 @@ DefineIndex(Oid relationId,
 	 * is more efficient.  Do this before any use of the concurrent option is
 	 * done.
 	 */
-	if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+	rel_persistence = get_rel_persistence(relationId);
+	if (stmt->concurrent &&
+		!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		concurrent = true;
 	else
 		concurrent = false;
@@ -2460,7 +2463,8 @@ ReindexIndex(RangeVar *indexRelation, int options, bool concurrent)
 	persistence = irel->rd_rel->relpersistence;
 	index_close(irel, NoLock);
 
-	if (concurrent && persistence != RELPERSISTENCE_TEMP)
+	if (concurrent &&
+		!(persistence == RELPERSISTENCE_TEMP || persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		ReindexRelationConcurrently(indOid, options);
 	else
 		reindex_index(indOid, false, persistence,
@@ -2546,6 +2550,7 @@ ReindexTable(RangeVar *relation, int options, bool concurrent)
 {
 	Oid			heapOid;
 	bool		result;
+	char		rel_persistence;
 
 	/*
 	 * The lock level used here should match reindex_relation().
@@ -2560,7 +2565,9 @@ ReindexTable(RangeVar *relation, int options, bool concurrent)
 									   0,
 									   RangeVarCallbackOwnsTable, NULL);
 
-	if (concurrent && get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+	rel_persistence = get_rel_persistence(heapOid);
+	if (concurrent &&
+		!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 	{
 		result = ReindexRelationConcurrently(heapOid, options);
 
@@ -2761,12 +2768,15 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 	foreach(l, relids)
 	{
 		Oid			relid = lfirst_oid(l);
+		char		rel_persistence;
 
 		StartTransactionCommand();
 		/* functions in indexes may want a snapshot set */
 		PushActiveSnapshot(GetTransactionSnapshot());
 
-		if (concurrent && get_rel_persistence(relid) != RELPERSISTENCE_TEMP)
+		rel_persistence = get_rel_persistence(relid);
+		if (concurrent &&
+			!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		{
 			(void) ReindexRelationConcurrently(relid, options);
 			/* ReindexRelationConcurrently() does the verbose output */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..a45863a 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -94,7 +96,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -222,7 +225,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* now initialize the sequence's data */
 	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -327,7 +330,7 @@ ResetSequence(Oid seq_relid)
 	/*
 	 * Insert the modified tuple into the new storage file.
 	 */
-	fill_seq_with_data(seq_rel, tuple);
+	fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -340,17 +343,21 @@ ResetSequence(Oid seq_relid)
  * Initialize a sequence's relation with the specified tuple as content
  */
 static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 	sequence_magic *sm;
 	OffsetNumber offnum;
+	bool lockBuffer = false;
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ReadBuffer(rel, P_NEW);
-	Assert(BufferGetBlockNumber(buf) == 0);
+	if (buf == InvalidBuffer)
+	{
+		buf = ReadBuffer(rel, P_NEW);
+		Assert(BufferGetBlockNumber(buf) == 0);
+		lockBuffer = true;
+	}
 
 	page = BufferGetPage(buf);
 
@@ -360,7 +367,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* Now insert sequence tuple */
 
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+	if (lockBuffer)
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +418,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	END_CRIT_SECTION();
 
-	UnlockReleaseBuffer(buf);
+	if (lockBuffer)
+		UnlockReleaseBuffer(buf);
 }
 
 /*
@@ -451,6 +460,13 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 
 	init_sequence(relid, &elm, &seqrel);
 
+	if (RELATION_IS_GLOBAL_TEMP(seqrel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(seqrel)))
+			elog(ERROR, "cannot alter global temp sequence %s when other backend attached it",
+						RelationGetRelationName(seqrel));
+	}
+
 	rel = table_open(SequenceRelationId, RowExclusiveLock);
 	seqtuple = SearchSysCacheCopy1(SEQRELID,
 								   ObjectIdGetDatum(relid));
@@ -502,7 +518,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * Insert the modified tuple into the new storage file.
 		 */
-		fill_seq_with_data(seqrel, newdatatuple);
+		fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
 	}
 
 	/* process OWNED BY if given */
@@ -611,7 +627,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +952,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1154,6 +1170,13 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1954,3 +1977,46 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+void
+gtt_init_seq(Relation rel)
+{
+	/* Initialize sequence for global temporary tables */
+	Datum		value[SEQ_COL_LASTCOL] = {0};
+	bool		null[SEQ_COL_LASTCOL] = {false};
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 794ff30..f2bb870 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -556,6 +557,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 								  Relation partitionTbl);
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static OnCommitAction gtt_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -601,6 +603,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -611,8 +614,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")));
@@ -642,7 +647,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	/* global temp table same as local temp table */
+	if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+		 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -743,6 +750,55 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* global temp table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* check parent table*/
+		if (inheritOids)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			table_close(relation, NoLock);
+		}
+
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temp table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1330,6 +1386,7 @@ RemoveRelations(DropStmt *drop)
 		Oid			relOid;
 		ObjectAddress obj;
 		struct DropRelationCallbackState state;
+		char		rel_persistence;
 
 		/*
 		 * These next few steps are a great deal like relation_openrv, but we
@@ -1363,8 +1420,9 @@ RemoveRelations(DropStmt *drop)
 		 * Decide if concurrent mode needs to be used here or not.  The
 		 * relation persistence cannot be known without its OID.
 		 */
+		rel_persistence = get_rel_persistence(relOid);
 		if (drop->concurrent &&
-			get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+			!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		{
 			Assert(list_length(drop->objects) == 1 &&
 				   drop->removeType == OBJECT_INDEX);
@@ -1543,7 +1601,7 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
  * are truncated and reindexed.
  */
 void
-ExecuteTruncate(TruncateStmt *stmt)
+ExecuteTruncate(TruncateStmt *stmt, bool isTopLevel)
 {
 	List	   *rels = NIL;
 	List	   *relids = NIL;
@@ -1568,6 +1626,14 @@ ExecuteTruncate(TruncateStmt *stmt)
 		/* open the relation, we already hold a lock on it */
 		rel = table_open(myrelid, NoLock);
 
+		if (stmt->behavior == DROP_STORAGE)
+		{
+			if (RELATION_IS_GLOBAL_TEMP(rel))
+				PreventInTransactionBlock(isTopLevel, "Truncate global temporary table");
+			else
+				elog(ERROR, "only global temporary table support truncate table drop");
+		}
+
 		/* don't throw error for "TRUNCATE foo, foo" */
 		if (list_member_oid(relids, myrelid))
 		{
@@ -1819,6 +1885,15 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
 		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 			continue;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			continue;
+
+		if (behavior == DROP_STORAGE)
+		{
+			Assert(RELATION_IS_GLOBAL_TEMP(rel));
+			gtt_drop_storage(rel);
+		}
 		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
@@ -1826,7 +1901,7 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
 		 * truncate it in-place, because a rollback would cause the whole
 		 * table or the current physical file to be thrown away anyway.
 		 */
-		if (rel->rd_createSubid == mySubid ||
+		else if (rel->rd_createSubid == mySubid ||
 			rel->rd_newRelfilenodeSubid == mySubid)
 		{
 			/* Immediate, non-rollbackable truncation is OK */
@@ -1950,8 +2025,12 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
 	foreach(cell, rels)
 	{
 		Relation	rel = (Relation) lfirst(cell);
-
+		bool		is_gtt = RELATION_IS_GLOBAL_TEMP(rel);
+		Oid			relid = RelationGetRelid(rel);
 		table_close(rel, NoLock);
+
+		if (is_gtt)
+			RelationForgetRelation(relid);
 	}
 }
 
@@ -3570,6 +3649,14 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			elog(ERROR, "can not alter table %s when other backend attached this global temp table",
+						RelationGetRelationName(rel));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -4847,6 +4934,38 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot rewrite temporary tables of other sessions")));
 
+			if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+			{
+				if (tab->chgPersistence)
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot change global temporary table persistence setting")));
+
+				/* gtt may not attached, create it */
+				if(!gtt_storage_attached(tab->relid))
+				{
+					ResultRelInfo *resultRelInfo;
+					MemoryContext oldcontext;
+					MemoryContext ctx_alter_gtt;
+
+					ctx_alter_gtt = AllocSetContextCreate(CurrentMemoryContext,
+											"gtt alter table", ALLOCSET_DEFAULT_SIZES);
+
+					oldcontext = MemoryContextSwitchTo(ctx_alter_gtt);
+					resultRelInfo = makeNode(ResultRelInfo);
+					InitResultRelInfo(resultRelInfo, OldHeap,
+									1, NULL, 0);
+					if (resultRelInfo->ri_RelationDesc->rd_rel->relhasindex &&
+						resultRelInfo->ri_IndexRelationDescs == NULL)
+						ExecOpenIndices(resultRelInfo, false);
+
+					init_gtt_storage(CMD_UTILITY, resultRelInfo);
+					ExecCloseIndices(resultRelInfo);
+					MemoryContextSwitchTo(oldcontext);
+					MemoryContextDelete(ctx_alter_gtt);
+				}
+			}
+
 			/*
 			 * Select destination tablespace (same as original unless user
 			 * requested a change)
@@ -8184,6 +8303,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -12740,6 +12865,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -12942,6 +13070,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temp table");
+
 	/*
 	 * No work if no change in tablespace.
 	 */
@@ -13316,7 +13447,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,
@@ -14723,7 +14854,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",
@@ -17379,3 +17512,36 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
 		table_close(rel, NoLock);
 	}
 }
+
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			bool	res = false;
+			char	*sval = defGetString(def);
+
+			if (!parse_bool(sval, &res))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
+
+			if (res)
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 5a110ed..02e6f1e 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"
@@ -1215,6 +1216,17 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	 /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		is_gtt = true;
+		up_gtt_relstats(relation,
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1228,17 +1240,26 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relpages = (int32) num_pages;
+	else if (pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (is_gtt)
+		relation->rd_rel->reltuples = (float4) num_tuples;
+	else if (pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	else if (pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1283,7 +1304,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1294,7 +1316,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1392,6 +1415,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
@@ -1449,6 +1476,42 @@ 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	safe_age;
+		TransactionId	oldest_gtt_frozenxid =
+			list_all_session_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid))
+		{
+			safe_age = oldest_gtt_frozenxid + vacuum_gtt_defer_check_age;
+			if (safe_age < FirstNormalTransactionId)
+				safe_age += FirstNormalTransactionId;
+
+			/*
+			 * We tolerate that the minimum age of gtt is less than
+			 * the minimum age of conventional tables, otherwise it will
+			 * throw warning message.
+			 */
+			if (TransactionIdIsNormal(safe_age) &&
+				TransactionIdPrecedes(safe_age, newFrozenXid))
+			{
+				ereport(WARNING,
+					(errmsg("global temp table oldest relfrozenxid %u is the oldest in the entire db",
+							oldest_gtt_frozenxid),
+					 errdetail("The oldest relfrozenxid in pg_class is %u", newFrozenXid),
+					 errhint("If they differ greatly, please consider cleaning up the data in global temp table.")));
+			}
+
+			if (TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+				newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1776,6 +1839,15 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6e65103..04706ee 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -530,6 +530,11 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 4fdffad..0aa7559 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index fb6ce49..06d9237 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
@@ -545,6 +546,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	init_gtt_storage(mtstate->operation, leaf_part_rri);
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 20a4c47..e9f6dcd 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,6 +42,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -2379,6 +2380,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			ExecOpenIndices(resultRelInfo,
 							node->onConflictAction != ONCONFLICT_NONE);
 
+		init_gtt_storage(operation, resultRelInfo);
+
 		/*
 		 * If this is an UPDATE and a BEFORE UPDATE trigger is present, the
 		 * trigger itself might modify the partition-key values. So arrange
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 255f56b..d94d6f0 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 e664eb1..e03bfa0 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6419,7 +6419,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
+	/* global temp table is same as local temp table */
 	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 2554502..04fb5d4 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -28,6 +28,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temp table */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index eee9c33..1891c53 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2594,6 +2594,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 3c78f2d..0dced33 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -2599,6 +2599,7 @@ alter_column_default:
 opt_drop_behavior:
 			CASCADE						{ $$ = DROP_CASCADE; }
 			| RESTRICT					{ $$ = DROP_RESTRICT; }
+			| DROP						{ $$ = DROP_STORAGE; }
 			| /* EMPTY */				{ $$ = DROP_RESTRICT; /* default */ }
 		;
 
@@ -3302,17 +3303,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; }
@@ -11648,19 +11643,13 @@ OptTempTableName:
 				}
 			| GLOBAL TEMPORARY opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED opt_table qualified_name
 				{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b875a50..123945b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 75c122f..97213ed 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
@@ -3108,6 +3111,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		cxt.isforeign = false;
 	}
 	cxt.relation = stmt->relation;
+	cxt.relation->relpersistence = RelationGetRelPersistence(rel);
 	cxt.rel = rel;
 	cxt.inhRelations = NIL;
 	cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 7e97ffa..1f2ac58 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2089,6 +2089,11 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* autovacuum skip vacuum global temp table */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2155,7 +2160,9 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		/* autovacuum skip vacuum global temp table */
+		if (classForm->relpersistence == RELPERSISTENCE_TEMP || 
+			classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index f9980cf..406a25c 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -53,6 +54,7 @@
 #include "utils/rel.h"
 #include "utils/resowner_private.h"
 #include "utils/timestamp.h"
+#include "utils/guc.h"
 
 
 /* Note: these two macros only work on shared buffers, not local ones! */
@@ -2819,6 +2821,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d5..7adb969 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -21,6 +21,7 @@
 #include "access/nbtree.h"
 #include "access/subtrans.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, active_gtt_shared_hash_size());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* global temporary table */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 3630006..0c8ef80 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -63,6 +63,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -4086,3 +4087,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 5aa19d3..b3bc455 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index b1f7f6e..c87b8ef 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -711,7 +711,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 			break;
 
 		case T_TruncateStmt:
-			ExecuteTruncate((TruncateStmt *) parsetree);
+			ExecuteTruncate((TruncateStmt *) parsetree, isTopLevel);
 			break;
 
 		case T_CopyStmt:
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 8406644..0d99321 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -1008,6 +1008,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index cfb0568..5913efc 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -112,6 +112,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -4821,12 +4822,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))
 							{
@@ -4951,15 +4965,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))
 		{
@@ -6341,6 +6367,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;
@@ -6358,6 +6385,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,
@@ -6369,6 +6403,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;
@@ -6384,6 +6420,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,
@@ -7302,6 +7345,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))
@@ -7314,6 +7359,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 =
@@ -7326,6 +7379,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.
@@ -7345,6 +7400,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index a7d63f1..d8a52cb 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -2939,6 +2940,18 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 9f1f11d..aeb2a78 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -65,6 +65,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1133,6 +1134,24 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1187,6 +1206,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			gtt_fix_index_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -1317,7 +1337,17 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+			if (newrelnode != InvalidOid &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2246,6 +2276,8 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		gtt_fix_index_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3472,6 +3504,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3579,28 +3615,34 @@ void
 RelationSetNewRelfilenode(Relation relation, char persistence)
 {
 	Oid			newrelfilenode;
-	Relation	pg_class;
-	HeapTuple	tuple;
+	Relation	pg_class = NULL;
+	HeapTuple	tuple = NULL;
 	Form_pg_class classform;
 	MultiXactId minmulti = InvalidMultiXactId;
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
+	bool		modify_pg_class = !RELATION_IS_GLOBAL_TEMP(relation);
 
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
 
-	/*
-	 * Get a writable copy of the pg_class tuple for the given relation.
-	 */
-	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+	if (modify_pg_class)
+	{
+		/*
+		 * Get a writable copy of the pg_class tuple for the given relation.
+		 */
+		pg_class = table_open(RelationRelationId, RowExclusiveLock);
 
-	tuple = SearchSysCacheCopy1(RELOID,
-								ObjectIdGetDatum(RelationGetRelid(relation)));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "could not find tuple for relation %u",
-			 RelationGetRelid(relation));
-	classform = (Form_pg_class) GETSTRUCT(tuple);
+		tuple = SearchSysCacheCopy1(RELOID,
+									ObjectIdGetDatum(RelationGetRelid(relation)));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u",
+				 RelationGetRelid(relation));
+		classform = (Form_pg_class) GETSTRUCT(tuple);
+	}
+	else
+		memset(&classform, 0, sizeof(classform));
 
 	/*
 	 * Schedule unlinking of the old storage at transaction commit.
@@ -3626,7 +3668,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;
@@ -3646,6 +3688,15 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 			break;
 	}
 
+	if (!modify_pg_class)
+	{
+		Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+		Assert(RELATION_IS_GLOBAL_TEMP(relation));
+		Assert(!RelationIsMapped(relation));
+		relation->rd_node.relNode = relnode;
+		CacheInvalidateRelcache(relation);
+	}
 	/*
 	 * If we're dealing with a mapped index, pg_class.relfilenode doesn't
 	 * change; instead we have to send the update to the relation mapper.
@@ -3655,7 +3706,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	 * possibly-inaccurate values of relpages etc, but those will be fixed up
 	 * later.
 	 */
-	if (RelationIsMapped(relation))
+	else if (RelationIsMapped(relation))
 	{
 		/* This case is only supported for indexes */
 		Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3701,9 +3752,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 		CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 	}
 
-	heap_freetuple(tuple);
+	if (modify_pg_class)
+	{
+		heap_freetuple(tuple);
 
-	table_close(pg_class, RowExclusiveLock);
+		table_close(pg_class, RowExclusiveLock);
+	}
 
 	/*
 	 * Make the pg_class row change or relation map change visible.  This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 5bdc02f..b52b51d 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -37,6 +37,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "commands/prepare.h"
 #include "commands/trigger.h"
@@ -142,6 +143,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temp table feature.
+ * global temp table define can still storage in catalog
+ * just can not use.
+ * num > 0 means database can management num active global temp table.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2070,6 +2083,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."),
@@ -2577,6 +2599,16 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"vacuum_gtt_defer_check_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("The defer check age of GTT, used to check expired data after vacuum."),
+			NULL
+		},
+		&vacuum_gtt_defer_check_age,
+		10000, 0, 1000000,
+		NULL, NULL, NULL
+	},
+
 	/*
 	 * See also CheckRequiredParameterValues() if this parameter changes
 	 */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 5db4f57..018a9bf 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2424,6 +2424,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
 		dopt->no_unlogged_table_data)
 		return;
 
+	/* Don't dump data in global temp table/sequence */
+	if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		return;
+
 	/* Check that the data is not explicitly excluded */
 	if (simple_oid_list_member(&tabledata_exclude_oids,
 							   tbinfo->dobj.catId.oid))
@@ -15777,6 +15781,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
 		char	   *foreign = "";
+		char	   *table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15830,9 +15835,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);
 
@@ -16226,7 +16237,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
-			 tbinfo->relkind == RELKIND_MATVIEW))
+			 tbinfo->relkind == RELKIND_MATVIEW) &&
+			 tbinfo->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
 		{
 			appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
 			appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
@@ -17131,6 +17143,7 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	PQExpBuffer query = createPQExpBuffer();
 	PQExpBuffer delqry = createPQExpBuffer();
 	char	   *qseqname;
+	bool	   global_temp_seq = false;
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
@@ -17140,9 +17153,11 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
-						  "FROM pg_catalog.pg_sequence "
-						  "WHERE seqrelid = '%u'::oid",
+						  "seqcache, seqcycle, c.relpersistence "
+						  "FROM pg_catalog.pg_sequence s, "
+						  "pg_catalog.pg_class c "
+						  "WHERE seqrelid = '%u'::oid "
+						  "and s.seqrelid = c.oid",
 						  tbinfo->dobj.catId.oid);
 	}
 	else if (fout->remoteVersion >= 80400)
@@ -17187,6 +17202,9 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
 
+	if (fout->remoteVersion >= 130000)
+		global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
 	if (strcmp(seqtype, "smallint") == 0)
@@ -17261,9 +17279,13 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	}
 	else
 	{
-		appendPQExpBuffer(query,
-						  "CREATE SEQUENCE %s\n",
-						  fmtQualifiedDumpable(tbinfo));
+		appendPQExpBuffer(query, "CREATE ");
+
+		if (global_temp_seq)
+			appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+		appendPQExpBuffer(query, "SEQUENCE %s\n",
+								  fmtQualifiedDumpable(tbinfo));
 
 		if (strcmp(seqtype, "bigint") != 0)
 			appendPQExpBuffer(query, "    AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 00aef85..9ad71c8 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -84,7 +84,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -157,7 +157,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 7e524ea..371168f 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -441,22 +443,46 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 	 * output, so we have to copy that system table.  It's easiest to do that
 	 * by treating it as a user table.
 	 */
-	snprintf(query + strlen(query), sizeof(query) - strlen(query),
-			 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
-			 "  SELECT c.oid, 0::oid, 0::oid "
-			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
-			 "         ON c.relnamespace = n.oid "
-			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
-	/* exclude possible orphaned temp tables */
-			 "    ((n.nspname !~ '^pg_temp_' AND "
-			 "      n.nspname !~ '^pg_toast_temp_' AND "
-			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
-			 "                        'binary_upgrade', 'pg_toast') AND "
-			 "      c.oid >= %u::pg_catalog.oid) OR "
-			 "     (n.nspname = 'pg_catalog' AND "
-			 "      relname IN ('pg_largeobject') ))), ",
-			 FirstNormalObjectId);
+	if (skip_gtt)
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude global temp tables */
+				 "    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
+	else
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
 
 	/*
 	 * Add a CTE that collects OIDs of toast tables belonging to the tables
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 70194eb..8dd9e66 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -411,7 +411,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -642,7 +642,9 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -653,7 +655,9 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 8b90cef..d155205 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -389,7 +389,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 8dca6d8..6b3f571 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3738,7 +3738,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index f6fd623..ea87bc4 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1023,6 +1023,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2394,6 +2396,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2602,6 +2607,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index cbfdfe2..be39472 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -59,7 +59,8 @@ extern Relation heap_create(const char *relname,
 							bool mapped_relation,
 							bool allow_system_table_mods,
 							TransactionId *relfrozenxid,
-							MultiXactId *relminmxid);
+							MultiXactId *relminmxid,
+							bool skip_create_storage);
 
 extern Oid	heap_create_with_catalog(const char *relname,
 									 Oid relnamespace,
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 78b33b2..4be89f6 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -168,6 +168,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad..c8236a1 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5558,6 +5558,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4388',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4389',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4390',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4391',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 30c38e0..7ff2408 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,7 +22,7 @@
 /* GUC variables */
 extern int	wal_skip_threshold;
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationPreTruncate(Relation rel);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..7d92154
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,48 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern int		vacuum_gtt_defer_check_age;
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Relation relation,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void gtt_force_enable_index(Relation index);
+extern void gtt_fix_index_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+extern void gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint);
+extern void gtt_drop_storage(Relation rel);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index e2638ab..89a5ce4 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index c1581ad..f469a65 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -55,7 +55,7 @@ extern void AlterRelationNamespaceInternal(Relation classRel, Oid relOid,
 
 extern void CheckTableNotInUse(Relation rel, const char *stmt);
 
-extern void ExecuteTruncate(TruncateStmt *stmt);
+extern void ExecuteTruncate(TruncateStmt *stmt, bool isTopLevel);
 extern void ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
 								DropBehavior behavior, bool restart_seqs);
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 5e1ffaf..57a4d47 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1764,7 +1764,8 @@ typedef struct CreateSchemaStmt
 typedef enum DropBehavior
 {
 	DROP_RESTRICT,				/* drop fails if any dependent objects */
-	DROP_CASCADE				/* remove dependent objects too */
+	DROP_CASCADE,				/* remove dependent objects too */
+	DROP_STORAGE				/* drop storage only for global temp table */
 } DropBehavior;
 
 /* ----------------------
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 3f88683..f870e9a 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 8fda8e4..172eae9 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_TBM,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_SXACT,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index ae4f573..69cce92 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -117,6 +117,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId session_gtt_frozenxid;	/* session level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 2819282..363fc33 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -282,6 +282,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 0b5957b..0720a4c 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	struct SMgrRelationData *rd_smgr;	/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -305,6 +305,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -570,11 +571,13 @@ typedef struct ViewOptions
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -582,6 +585,7 @@ typedef struct ViewOptions
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -594,6 +598,14 @@ typedef struct ViewOptions
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is either local temp relation of this session
+ * 		or global temp relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -637,6 +649,17 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..ca2d135
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,14 @@
+reset search_path;
+select pg_sleep(5);
+ pg_sleep 
+----------
+ 
+(1 row)
+
+drop schema gtt cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
+drop cascades to table gtt.gtt_with_seq
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..d6a675a
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,383 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+ n 
+---
+ 9
+(1 row)
+
+commit;
+-- 0 row
+select * from gtt6;
+ n 
+---
+(0 rows)
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temp table
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  could not create global temporary table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR:  insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count 
+-------
+     1
+(1 row)
+
+select count(*) from orders;
+ count 
+-------
+     0
+(1 row)
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  2 |  3
+(2 rows)
+
+commit;
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+select * from gtt_s_2 order by c1;
+ c1 | c2 
+----+----
+  1 |  2
+  2 |  4
+(2 rows)
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 1)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 300)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 33 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test_new
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.gtt6
+drop cascades to table gtt_function.foo
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to sequence gtt_function.seq_1
+drop cascades to table gtt_function.gtt_s_1
+drop cascades to table gtt_function.gtt_s_2
+drop cascades to table gtt_function.gt1
+drop cascades to table gtt_function.gtt_test1
+drop cascades to table gtt_function.gtt_test2
+drop cascades to table gtt_function.gtt_test3
+drop cascades to table gtt_function.gtt_test4
+drop cascades to table gtt_function.gtt_test5
+drop cascades to table gtt_function.gtt_test6
+drop cascades to table gtt_function.gtt_test7
+drop cascades to table gtt_function.gtt_test8
+drop cascades to table gtt_function.gtt_test9
+drop cascades to table gtt_function.gtt_test10
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..0646aae
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,90 @@
+set search_path=gtt,sys;
+select nextval('gtt_with_seq_c2_seq');
+ nextval 
+---------
+       1
+(1 row)
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..baa01d3
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,371 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+rollback;
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+commit;
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          114688 |                 614400
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |            49152 |         49152 |                  49152
+(2 rows)
+
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2000
+(1 row)
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+select count(*) from pg_gtt_relstats;
+ count 
+-------
+     9
+(1 row)
+
+begin;
+truncate gtt_t_kenyon drop;
+ERROR:  Truncate global temporary table cannot run inside a transaction block
+rollback;
+select count(*) from pg_gtt_relstats;
+ count 
+-------
+     9
+(1 row)
+
+truncate gtt_t_kenyon drop;
+select count(*) from pg_gtt_relstats;
+ count 
+-------
+     4
+(1 row)
+
+--error
+create table test_fail(a int);
+truncate test_fail drop;
+ERROR:  only global temporary table support truncate table drop
+drop table test_fail;
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+ c1 | c2 
+----+----
+  1 |  1
+(1 row)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..8c0c376
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,10 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..4420fdb
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,80 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+ gtt_stats  | gtt_pkey  |        1 |         0 |             0
+(2 rows)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+reindex table gtt;
+reindex index gtt_pkey;
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+ gtt_stats  | gtt_pkey  |       30 |     10000 |             0
+(2 rows)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index ac31840..55b221e 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1349,6 +1349,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 95f1925..76b2374 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..2c8e586
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,8 @@
+
+
+reset search_path;
+
+select pg_sleep(5);
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..81f0bfc
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,253 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+commit;
+-- 0 row
+select * from gtt6;
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+commit;
+select * from gtt_s_1 order by c1;
+select * from gtt_s_2 order by c1;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d05745e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,44 @@
+
+
+set search_path=gtt,sys;
+
+select nextval('gtt_with_seq_c2_seq');
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..14e901f
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,167 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+rollback;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+commit;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+select count(*) from gtt_t_kenyon;
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+select count(*) from pg_gtt_relstats;
+begin;
+truncate gtt_t_kenyon drop;
+rollback;
+select count(*) from pg_gtt_relstats;
+truncate gtt_t_kenyon drop;
+select count(*) from pg_gtt_relstats;
+
+--error
+create table test_fail(a int);
+truncate test_fail drop;
+drop table test_fail;
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..dbe84d1
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,19 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..d61b0ff
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,46 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+reindex table gtt;
+
+reindex index gtt_pkey;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#254Prabhat Sahu
prabhat.sahu@enterprisedb.com
In reply to: 曾文旌 (#253)
Re: [Proposal] Global temporary tables

On Wed, Apr 22, 2020 at 2:49 PM 曾文旌 <wenjing.zwj@alibaba-inc.com> wrote:

Although the implementation of GTT is different, I think so TRUNCATE on
Postgres (when it is really finalized) can remove session metadata of GTT
too (and reduce usage's counter). It is not critical feature, but I think
so it should not be hard to implement. From practical reason can be nice to
have a tool how to refresh GTT without a necessity to close session.
TRUNCATE can be this tool.

Yes, I think we need a way to delete the GTT local storage without closing
the session.

I provide the TRUNCATE tablename DROP to clear the data in the GTT and
delete the storage files.
This feature requires the current transaction to commit immediately after
it finishes truncate.

Hi Wenjing,
Thanks for the patch(v30) for the new syntax support for (TRUNCATE
table_name DROP) for deleting storage files after TRUNCATE on GTT.

Please check below scenarios:

*Case1:*-- session1:
postgres=# create global temporary table gtt2 (c1 integer) on commit
preserve rows;
CREATE TABLE
postgres=# create index idx1 on gtt2 (c1);
CREATE INDEX
postgres=# create index idx2 on gtt2 (c1) where c1%2 =0;
CREATE INDEX
postgres=#
postgres=# CLUSTER gtt2 USING idx1;
CLUSTER
postgres=# CLUSTER gtt2 USING idx2;
ERROR: cannot cluster on partial index "idx2"

*Case2:*-- Session2:
postgres=# CLUSTER gtt2 USING idx1;
CLUSTER
postgres=# CLUSTER gtt2 USING idx2;
CLUSTER

postgres=# insert into gtt2 values(1);
INSERT 0 1
postgres=# CLUSTER gtt2 USING idx1;
CLUSTER
postgres=# CLUSTER gtt2 USING idx2;
ERROR: cannot cluster on partial index "idx2"

*Case3:*-- Session2:
postgres=# TRUNCATE gtt2 DROP;
TRUNCATE TABLE
postgres=# CLUSTER gtt2 USING idx1;
CLUSTER
postgres=# CLUSTER gtt2 USING idx2;
CLUSTER

In Case2, Case3 we can observe, with the absence of data in GTT, we are
able to "CLUSTER gtt2 USING idx2;" (having partial index)
But why does the same query fail for Case1 (absence of data)?

Thanks,
Prabhat Sahu

Wenjing

Regards

Pavel

All in all, I think the current implementation is sufficient for dba to
manage GTT.

2020年4月2日 下午4:45,Prabhat Sahu <prabhat.sahu@enterprisedb.com> 写道:

Hi All,

I have noted down few behavioral difference in our GTT implementation in
PG as compared to Oracle DB:
As per my understanding, the behavior of DROP TABLE in case of "Normal
table and GTT" in Oracle DB are as below:

1. Any tables(Normal table / GTT) without having data in a session,
we will be able to DROP from another session.
2. For a completed transaction on a normal table having data, we will
be able to DROP from another session. If the transaction is not yet
complete, and we are trying to drop the table from another session, then we
will get an error. (working as expected)
3. For a completed transaction on GTT with(on commit delete rows)
(i.e. no data in GTT) in a session, we will be able to DROP from another
session.
4. For a completed transaction on GTT with(on commit preserve rows)
with data in a session, we will not be able to DROP from any session(not
even from the session in which GTT is created), we need to truncate the
table data first from all the session(session1, session2) which is having
data.

*1. Any tables(Normal table / GTT) without having data in a session, we
will be able to DROP from another session.*
*Session1:*
create table t1 (c1 integer);
create global temporary table gtt1 (c1 integer) on commit delete rows;
create global temporary table gtt2 (c1 integer) on commit preserve rows;

*Session2:*
drop table t1;
drop table gtt1;
drop table gtt2;

-- *Issue 1:* But we are able to drop a simple table and failed to drop
GTT as below.

postgres=# drop table t1;
DROP TABLE
postgres=# drop table gtt1;
ERROR: can not drop relation gtt1 when other backend attached this
global temp table
postgres=# drop table gtt2;
ERROR: can not drop relation gtt2 when other backend attached this
global temp table

*3. For a completed transaction on GTT with(on commit delete rows) (i.e.
no data in GTT) in a session, we will be able to DROP from another session.*

*Session1:*create global temporary table gtt1 (c1 integer) on commit
delete rows;

*Session2:*
drop table gtt1;

-- *Issue 2:* But we are getting error for GTT
with(on_commit_delete_rows) without data.

postgres=# drop table gtt1;
ERROR: can not drop relation gtt1 when other backend attached this
global temp table

*4. For a completed transaction on GTT with(on commit preserve rows) with
data in any session, we will not be able to DROP from any session(not even
from the session in which GTT is created)*

*Case1:*
create global temporary table gtt2 (c1 integer) on commit preserve rows;
insert into gtt2 values(100);
drop table gtt2;

SQL> drop table gtt2;
drop table gtt2
*
ERROR at line 1:
ORA-14452: attempt to create, alter or drop an index on temporary table
already in use

-- *Issue 3:* But, we are able to drop the GTT(having data) which we
have created in the same session.

postgres=# drop table gtt2;
DROP TABLE

*Case2: GTT with(on commit preserve rows) having data in both session1
and session2Session1:*create global temporary table gtt2 (c1 integer) on
commit preserve rows;
insert into gtt2 values(100);

*Session2:*insert into gtt2 values(200);

-- If we try to drop the table from any session we should get an error,
it is working fine.
drop table gtt2;

SQL> drop table gtt2;
drop table gtt2
*
ERROR at line 1:
ORA-14452: attempt to create, alter or drop an index on temporary table
already in use

postgres=# drop table gtt2 ;
ERROR: can not drop relation gtt2 when other backend attached this
global temp table

-- To drop the table gtt2 from any session1/session2, we need to truncate
the table data first from all the session(session1, session2) which is
having data.
*Session1:*
truncate table gtt2;
-- Session2:
truncate table gtt2;

*Session 2:*
SQL> drop table gtt2;

Table dropped.

-- *Issue 4:* But we are not able to drop the GTT, even after TRUNCATE
the table in all the sessions.
-- truncate from all sessions where GTT have data.
postgres=# truncate gtt2 ;
TRUNCATE TABLE

-- *try to DROP GTT still, we are getting error.*

postgres=# drop table gtt2 ;
ERROR: can not drop relation gtt2 when other backend attached this
global temp table

To drop the GTT from any session, we need to exit from all other sessions.
postgres=# drop table gtt2 ;
DROP TABLE

Kindly let me know if I am missing something.

On Wed, Apr 1, 2020 at 6:26 PM Prabhat Sahu <
prabhat.sahu@enterprisedb.com> wrote:

Hi Wenjing,
I hope we need to change the below error message.

postgres=# create global temporary table gtt(c1 int) on commit preserve
rows;
CREATE TABLE

postgres=# create materialized view mvw as select * from gtt;
ERROR: materialized views must not use global temporary tables* or
views*

Anyways we are not allowed to create a "global temporary view",
so the above ERROR message should change(i.e. *" or view"* need to be
removed from the error message) something like:
*"ERROR: materialized views must not use global temporary tables"*

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

#255Pavel Stehule
pavel.stehule@gmail.com
In reply to: Prabhat Sahu (#254)
Re: [Proposal] Global temporary tables

st 22. 4. 2020 v 16:38 odesílatel Prabhat Sahu <
prabhat.sahu@enterprisedb.com> napsal:

On Wed, Apr 22, 2020 at 2:49 PM 曾文旌 <wenjing.zwj@alibaba-inc.com> wrote:

Although the implementation of GTT is different, I think so TRUNCATE on
Postgres (when it is really finalized) can remove session metadata of GTT
too (and reduce usage's counter). It is not critical feature, but I think
so it should not be hard to implement. From practical reason can be nice to
have a tool how to refresh GTT without a necessity to close session.
TRUNCATE can be this tool.

Yes, I think we need a way to delete the GTT local storage without
closing the session.

I provide the TRUNCATE tablename DROP to clear the data in the GTT and
delete the storage files.
This feature requires the current transaction to commit immediately after
it finishes truncate.

Hi Wenjing,
Thanks for the patch(v30) for the new syntax support for (TRUNCATE
table_name DROP) for deleting storage files after TRUNCATE on GTT.

This syntax looks strange, and I don't think so it solve anything in
practical life, because without lock the table will be used in few seconds
by other sessions.

This is same topic when we talked about ALTER - when and where the changes
should be applied.

The CLUSTER commands works only on session private data, so it should not
to need some special lock or some special cleaning before.

Regards

Pavel

Show quoted text

Please check below scenarios:

*Case1:*-- session1:
postgres=# create global temporary table gtt2 (c1 integer) on commit
preserve rows;
CREATE TABLE
postgres=# create index idx1 on gtt2 (c1);
CREATE INDEX
postgres=# create index idx2 on gtt2 (c1) where c1%2 =0;
CREATE INDEX
postgres=#
postgres=# CLUSTER gtt2 USING idx1;
CLUSTER
postgres=# CLUSTER gtt2 USING idx2;
ERROR: cannot cluster on partial index "idx2"

*Case2:*-- Session2:
postgres=# CLUSTER gtt2 USING idx1;
CLUSTER
postgres=# CLUSTER gtt2 USING idx2;
CLUSTER

postgres=# insert into gtt2 values(1);
INSERT 0 1
postgres=# CLUSTER gtt2 USING idx1;
CLUSTER
postgres=# CLUSTER gtt2 USING idx2;
ERROR: cannot cluster on partial index "idx2"

*Case3:*-- Session2:
postgres=# TRUNCATE gtt2 DROP;
TRUNCATE TABLE
postgres=# CLUSTER gtt2 USING idx1;
CLUSTER
postgres=# CLUSTER gtt2 USING idx2;
CLUSTER

In Case2, Case3 we can observe, with the absence of data in GTT, we are
able to "CLUSTER gtt2 USING idx2;" (having partial index)
But why does the same query fail for Case1 (absence of data)?

Thanks,
Prabhat Sahu

Wenjing

Regards

Pavel

All in all, I think the current implementation is sufficient for dba to
manage GTT.

2020年4月2日 下午4:45,Prabhat Sahu <prabhat.sahu@enterprisedb.com> 写道:

Hi All,

I have noted down few behavioral difference in our GTT implementation in
PG as compared to Oracle DB:
As per my understanding, the behavior of DROP TABLE in case of "Normal
table and GTT" in Oracle DB are as below:

1. Any tables(Normal table / GTT) without having data in a session,
we will be able to DROP from another session.
2. For a completed transaction on a normal table having data, we
will be able to DROP from another session. If the transaction is not yet
complete, and we are trying to drop the table from another session, then we
will get an error. (working as expected)
3. For a completed transaction on GTT with(on commit delete rows)
(i.e. no data in GTT) in a session, we will be able to DROP from another
session.
4. For a completed transaction on GTT with(on commit preserve rows)
with data in a session, we will not be able to DROP from any session(not
even from the session in which GTT is created), we need to truncate the
table data first from all the session(session1, session2) which is having
data.

*1. Any tables(Normal table / GTT) without having data in a session, we
will be able to DROP from another session.*
*Session1:*
create table t1 (c1 integer);
create global temporary table gtt1 (c1 integer) on commit delete rows;
create global temporary table gtt2 (c1 integer) on commit preserve rows;

*Session2:*
drop table t1;
drop table gtt1;
drop table gtt2;

-- *Issue 1:* But we are able to drop a simple table and failed to drop
GTT as below.

postgres=# drop table t1;
DROP TABLE
postgres=# drop table gtt1;
ERROR: can not drop relation gtt1 when other backend attached this
global temp table
postgres=# drop table gtt2;
ERROR: can not drop relation gtt2 when other backend attached this
global temp table

*3. For a completed transaction on GTT with(on commit delete rows) (i.e.
no data in GTT) in a session, we will be able to DROP from another session.*

*Session1:*create global temporary table gtt1 (c1 integer) on commit
delete rows;

*Session2:*
drop table gtt1;

-- *Issue 2:* But we are getting error for GTT
with(on_commit_delete_rows) without data.

postgres=# drop table gtt1;
ERROR: can not drop relation gtt1 when other backend attached this
global temp table

*4. For a completed transaction on GTT with(on commit preserve
rows) with data in any session, we will not be able to DROP from any
session(not even from the session in which GTT is created)*

*Case1:*
create global temporary table gtt2 (c1 integer) on commit preserve rows;
insert into gtt2 values(100);
drop table gtt2;

SQL> drop table gtt2;
drop table gtt2
*
ERROR at line 1:
ORA-14452: attempt to create, alter or drop an index on temporary table
already in use

-- *Issue 3:* But, we are able to drop the GTT(having data) which we
have created in the same session.

postgres=# drop table gtt2;
DROP TABLE

*Case2: GTT with(on commit preserve rows) having data in both session1
and session2Session1:*create global temporary table gtt2 (c1 integer)
on commit preserve rows;
insert into gtt2 values(100);

*Session2:*insert into gtt2 values(200);

-- If we try to drop the table from any session we should get an error,
it is working fine.
drop table gtt2;

SQL> drop table gtt2;
drop table gtt2
*
ERROR at line 1:
ORA-14452: attempt to create, alter or drop an index on temporary table
already in use

postgres=# drop table gtt2 ;
ERROR: can not drop relation gtt2 when other backend attached this
global temp table

-- To drop the table gtt2 from any session1/session2, we need to
truncate the table data first from all the session(session1, session2)
which is having data.
*Session1:*
truncate table gtt2;
-- Session2:
truncate table gtt2;

*Session 2:*
SQL> drop table gtt2;

Table dropped.

-- *Issue 4:* But we are not able to drop the GTT, even after TRUNCATE
the table in all the sessions.
-- truncate from all sessions where GTT have data.
postgres=# truncate gtt2 ;
TRUNCATE TABLE

-- *try to DROP GTT still, we are getting error.*

postgres=# drop table gtt2 ;
ERROR: can not drop relation gtt2 when other backend attached this
global temp table

To drop the GTT from any session, we need to exit from all other
sessions.
postgres=# drop table gtt2 ;
DROP TABLE

Kindly let me know if I am missing something.

On Wed, Apr 1, 2020 at 6:26 PM Prabhat Sahu <
prabhat.sahu@enterprisedb.com> wrote:

Hi Wenjing,
I hope we need to change the below error message.

postgres=# create global temporary table gtt(c1 int) on commit preserve
rows;
CREATE TABLE

postgres=# create materialized view mvw as select * from gtt;
ERROR: materialized views must not use global temporary tables* or
views*

Anyways we are not allowed to create a "global temporary view",
so the above ERROR message should change(i.e. *" or view"* need to be
removed from the error message) something like:
*"ERROR: materialized views must not use global temporary tables"*

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

#256曾文旌
wenjing.zwj@alibaba-inc.com
In reply to: Prabhat Sahu (#254)
1 attachment(s)
Re: [Proposal] Global temporary tables

2020年4月22日 下午10:38,Prabhat Sahu <prabhat.sahu@enterprisedb.com> 写道:

On Wed, Apr 22, 2020 at 2:49 PM 曾文旌 <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> wrote:

Although the implementation of GTT is different, I think so TRUNCATE on Postgres (when it is really finalized) can remove session metadata of GTT too (and reduce usage's counter). It is not critical feature, but I think so it should not be hard to implement. From practical reason can be nice to have a tool how to refresh GTT without a necessity to close session. TRUNCATE can be this tool.

Yes, I think we need a way to delete the GTT local storage without closing the session.

I provide the TRUNCATE tablename DROP to clear the data in the GTT and delete the storage files.
This feature requires the current transaction to commit immediately after it finishes truncate.

Hi Wenjing,
Thanks for the patch(v30) for the new syntax support for (TRUNCATE table_name DROP) for deleting storage files after TRUNCATE on GTT.

Please check below scenarios:

Case1:
-- session1:
postgres=# create global temporary table gtt2 (c1 integer) on commit preserve rows;
CREATE TABLE
postgres=# create index idx1 on gtt2 (c1);
CREATE INDEX
postgres=# create index idx2 on gtt2 (c1) where c1%2 =0;
CREATE INDEX
postgres=#
postgres=# CLUSTER gtt2 USING idx1;
CLUSTER
postgres=# CLUSTER gtt2 USING idx2;
ERROR: cannot cluster on partial index "idx2"

Case2:
-- Session2:
postgres=# CLUSTER gtt2 USING idx1;
CLUSTER
postgres=# CLUSTER gtt2 USING idx2;
CLUSTER

postgres=# insert into gtt2 values(1);
INSERT 0 1
postgres=# CLUSTER gtt2 USING idx1;
CLUSTER
postgres=# CLUSTER gtt2 USING idx2;
ERROR: cannot cluster on partial index "idx2"

Case3:
-- Session2:
postgres=# TRUNCATE gtt2 DROP;
TRUNCATE TABLE
postgres=# CLUSTER gtt2 USING idx1;
CLUSTER
postgres=# CLUSTER gtt2 USING idx2;
CLUSTER

In Case2, Case3 we can observe, with the absence of data in GTT, we are able to "CLUSTER gtt2 USING idx2;" (having partial index)
But why does the same query fail for Case1 (absence of data)?

This is expected
Because TRUNCATE gtt2 DROP; The local storage file was deleted, so CLUSTER checked that there were no local files and ended the process.

Wenjing

Show quoted text

Thanks,
Prabhat Sahu

Wenjing

Regards

Pavel

All in all, I think the current implementation is sufficient for dba to manage GTT.

2020年4月2日 下午4:45,Prabhat Sahu <prabhat.sahu@enterprisedb.com <mailto:prabhat.sahu@enterprisedb.com>> 写道:

Hi All,

I have noted down few behavioral difference in our GTT implementation in PG as compared to Oracle DB:
As per my understanding, the behavior of DROP TABLE in case of "Normal table and GTT" in Oracle DB are as below:
Any tables(Normal table / GTT) without having data in a session, we will be able to DROP from another session.
For a completed transaction on a normal table having data, we will be able to DROP from another session. If the transaction is not yet complete, and we are trying to drop the table from another session, then we will get an error. (working as expected)
For a completed transaction on GTT with(on commit delete rows) (i.e. no data in GTT) in a session, we will be able to DROP from another session.
For a completed transaction on GTT with(on commit preserve rows) with data in a session, we will not be able to DROP from any session(not even from the session in which GTT is created), we need to truncate the table data first from all the session(session1, session2) which is having data.
1. Any tables(Normal table / GTT) without having data in a session, we will be able to DROP from another session.
Session1:
create table t1 (c1 integer);
create global temporary table gtt1 (c1 integer) on commit delete rows;
create global temporary table gtt2 (c1 integer) on commit preserve rows;

Session2:
drop table t1;
drop table gtt1;
drop table gtt2;

-- Issue 1: But we are able to drop a simple table and failed to drop GTT as below.
postgres=# drop table t1;
DROP TABLE
postgres=# drop table gtt1;
ERROR: can not drop relation gtt1 when other backend attached this global temp table
postgres=# drop table gtt2;
ERROR: can not drop relation gtt2 when other backend attached this global temp table

3. For a completed transaction on GTT with(on commit delete rows) (i.e. no data in GTT) in a session, we will be able to DROP from another session.
Session1:
create global temporary table gtt1 (c1 integer) on commit delete rows;

Session2:
drop table gtt1;

-- Issue 2: But we are getting error for GTT with(on_commit_delete_rows) without data.
postgres=# drop table gtt1;
ERROR: can not drop relation gtt1 when other backend attached this global temp table

4. For a completed transaction on GTT with(on commit preserve rows) with data in any session, we will not be able to DROP from any session(not even from the session in which GTT is created)

Case1:
create global temporary table gtt2 (c1 integer) on commit preserve rows;
insert into gtt2 values(100);
drop table gtt2;

SQL> drop table gtt2;
drop table gtt2
*
ERROR at line 1:
ORA-14452: attempt to create, alter or drop an index on temporary table already in use

-- Issue 3: But, we are able to drop the GTT(having data) which we have created in the same session.
postgres=# drop table gtt2;
DROP TABLE

Case2: GTT with(on commit preserve rows) having data in both session1 and session2
Session1:
create global temporary table gtt2 (c1 integer) on commit preserve rows;
insert into gtt2 values(100);

Session2:
insert into gtt2 values(200);

-- If we try to drop the table from any session we should get an error, it is working fine.
drop table gtt2;
SQL> drop table gtt2;
drop table gtt2
*
ERROR at line 1:
ORA-14452: attempt to create, alter or drop an index on temporary table already in use

postgres=# drop table gtt2 ;
ERROR: can not drop relation gtt2 when other backend attached this global temp table

-- To drop the table gtt2 from any session1/session2, we need to truncate the table data first from all the session(session1, session2) which is having data.
Session1:
truncate table gtt2;
-- Session2:
truncate table gtt2;

Session 2:
SQL> drop table gtt2;

Table dropped.

-- Issue 4: But we are not able to drop the GTT, even after TRUNCATE the table in all the sessions.
-- truncate from all sessions where GTT have data.
postgres=# truncate gtt2 ;
TRUNCATE TABLE

-- try to DROP GTT still, we are getting error.
postgres=# drop table gtt2 ;
ERROR: can not drop relation gtt2 when other backend attached this global temp table

To drop the GTT from any session, we need to exit from all other sessions.
postgres=# drop table gtt2 ;
DROP TABLE

Kindly let me know if I am missing something.

On Wed, Apr 1, 2020 at 6:26 PM Prabhat Sahu <prabhat.sahu@enterprisedb.com <mailto:prabhat.sahu@enterprisedb.com>> wrote:
Hi Wenjing,
I hope we need to change the below error message.

postgres=# create global temporary table gtt(c1 int) on commit preserve rows;
CREATE TABLE

postgres=# create materialized view mvw as select * from gtt;
ERROR: materialized views must not use global temporary tables or views

Anyways we are not allowed to create a "global temporary view",
so the above ERROR message should change(i.e. " or view" need to be removed from the error message) something like:
"ERROR: materialized views must not use global temporary tables"

--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

Attachments:

smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#257曾文旌
wenjing.zwj@alibaba-inc.com
In reply to: Pavel Stehule (#255)
1 attachment(s)
Re: [Proposal] Global temporary tables

2020年4月22日 下午10:50,Pavel Stehule <pavel.stehule@gmail.com> 写道:

st 22. 4. 2020 v 16:38 odesílatel Prabhat Sahu <prabhat.sahu@enterprisedb.com <mailto:prabhat.sahu@enterprisedb.com>> napsal:

On Wed, Apr 22, 2020 at 2:49 PM 曾文旌 <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> wrote:

Although the implementation of GTT is different, I think so TRUNCATE on Postgres (when it is really finalized) can remove session metadata of GTT too (and reduce usage's counter). It is not critical feature, but I think so it should not be hard to implement. From practical reason can be nice to have a tool how to refresh GTT without a necessity to close session. TRUNCATE can be this tool.

Sorry, I don't quite understand what you mean, could you describe it in detail?
In my opinion the TRUNCATE GTT cannot clean up data in other sessions, especially clean up local buffers in other sessions.

Yes, I think we need a way to delete the GTT local storage without closing the session.

I provide the TRUNCATE tablename DROP to clear the data in the GTT and delete the storage files.
This feature requires the current transaction to commit immediately after it finishes truncate.

Hi Wenjing,
Thanks for the patch(v30) for the new syntax support for (TRUNCATE table_name DROP) for deleting storage files after TRUNCATE on GTT.

This syntax looks strange, and I don't think so it solve anything in practical life, because without lock the table will be used in few seconds by other sessions.

If a dba wants to delete or modify a GTT, he can use locks to help him make the change.

postgres=# begin;
BEGIN
postgres=*# LOCK TABLE gtt2 IN ACCESS EXCLUSIVE MODE;
postgres=*# select * from pg_gtt_attached_pids ;

Kill session or let session do TRUNCATE tablename DROP

postgres=*# drop table gtt2;
DROP TABLE
postgres=*# commit;
COMMIT

Show quoted text

This is same topic when we talked about ALTER - when and where the changes should be applied.

The CLUSTER commands works only on session private data, so it should not to need some special lock or some special cleaning before.

Regards

Pavel

Please check below scenarios:

Case1:
-- session1:
postgres=# create global temporary table gtt2 (c1 integer) on commit preserve rows;
CREATE TABLE
postgres=# create index idx1 on gtt2 (c1);
CREATE INDEX
postgres=# create index idx2 on gtt2 (c1) where c1%2 =0;
CREATE INDEX
postgres=#
postgres=# CLUSTER gtt2 USING idx1;
CLUSTER
postgres=# CLUSTER gtt2 USING idx2;
ERROR: cannot cluster on partial index "idx2"

Case2:
-- Session2:
postgres=# CLUSTER gtt2 USING idx1;
CLUSTER
postgres=# CLUSTER gtt2 USING idx2;
CLUSTER

postgres=# insert into gtt2 values(1);
INSERT 0 1
postgres=# CLUSTER gtt2 USING idx1;
CLUSTER
postgres=# CLUSTER gtt2 USING idx2;
ERROR: cannot cluster on partial index "idx2"

Case3:
-- Session2:
postgres=# TRUNCATE gtt2 DROP;
TRUNCATE TABLE
postgres=# CLUSTER gtt2 USING idx1;
CLUSTER
postgres=# CLUSTER gtt2 USING idx2;
CLUSTER

In Case2, Case3 we can observe, with the absence of data in GTT, we are able to "CLUSTER gtt2 USING idx2;" (having partial index)
But why does the same query fail for Case1 (absence of data)?

Thanks,
Prabhat Sahu

Wenjing

Regards

Pavel

All in all, I think the current implementation is sufficient for dba to manage GTT.

2020年4月2日 下午4:45,Prabhat Sahu <prabhat.sahu@enterprisedb.com <mailto:prabhat.sahu@enterprisedb.com>> 写道:

Hi All,

I have noted down few behavioral difference in our GTT implementation in PG as compared to Oracle DB:
As per my understanding, the behavior of DROP TABLE in case of "Normal table and GTT" in Oracle DB are as below:
Any tables(Normal table / GTT) without having data in a session, we will be able to DROP from another session.
For a completed transaction on a normal table having data, we will be able to DROP from another session. If the transaction is not yet complete, and we are trying to drop the table from another session, then we will get an error. (working as expected)
For a completed transaction on GTT with(on commit delete rows) (i.e. no data in GTT) in a session, we will be able to DROP from another session.
For a completed transaction on GTT with(on commit preserve rows) with data in a session, we will not be able to DROP from any session(not even from the session in which GTT is created), we need to truncate the table data first from all the session(session1, session2) which is having data.
1. Any tables(Normal table / GTT) without having data in a session, we will be able to DROP from another session.
Session1:
create table t1 (c1 integer);
create global temporary table gtt1 (c1 integer) on commit delete rows;
create global temporary table gtt2 (c1 integer) on commit preserve rows;

Session2:
drop table t1;
drop table gtt1;
drop table gtt2;

-- Issue 1: But we are able to drop a simple table and failed to drop GTT as below.
postgres=# drop table t1;
DROP TABLE
postgres=# drop table gtt1;
ERROR: can not drop relation gtt1 when other backend attached this global temp table
postgres=# drop table gtt2;
ERROR: can not drop relation gtt2 when other backend attached this global temp table

3. For a completed transaction on GTT with(on commit delete rows) (i.e. no data in GTT) in a session, we will be able to DROP from another session.
Session1:
create global temporary table gtt1 (c1 integer) on commit delete rows;

Session2:
drop table gtt1;

-- Issue 2: But we are getting error for GTT with(on_commit_delete_rows) without data.
postgres=# drop table gtt1;
ERROR: can not drop relation gtt1 when other backend attached this global temp table

4. For a completed transaction on GTT with(on commit preserve rows) with data in any session, we will not be able to DROP from any session(not even from the session in which GTT is created)

Case1:
create global temporary table gtt2 (c1 integer) on commit preserve rows;
insert into gtt2 values(100);
drop table gtt2;

SQL> drop table gtt2;
drop table gtt2
*
ERROR at line 1:
ORA-14452: attempt to create, alter or drop an index on temporary table already in use

-- Issue 3: But, we are able to drop the GTT(having data) which we have created in the same session.
postgres=# drop table gtt2;
DROP TABLE

Case2: GTT with(on commit preserve rows) having data in both session1 and session2
Session1:
create global temporary table gtt2 (c1 integer) on commit preserve rows;
insert into gtt2 values(100);

Session2:
insert into gtt2 values(200);

-- If we try to drop the table from any session we should get an error, it is working fine.
drop table gtt2;
SQL> drop table gtt2;
drop table gtt2
*
ERROR at line 1:
ORA-14452: attempt to create, alter or drop an index on temporary table already in use

postgres=# drop table gtt2 ;
ERROR: can not drop relation gtt2 when other backend attached this global temp table

-- To drop the table gtt2 from any session1/session2, we need to truncate the table data first from all the session(session1, session2) which is having data.
Session1:
truncate table gtt2;
-- Session2:
truncate table gtt2;

Session 2:
SQL> drop table gtt2;

Table dropped.

-- Issue 4: But we are not able to drop the GTT, even after TRUNCATE the table in all the sessions.
-- truncate from all sessions where GTT have data.
postgres=# truncate gtt2 ;
TRUNCATE TABLE

-- try to DROP GTT still, we are getting error.
postgres=# drop table gtt2 ;
ERROR: can not drop relation gtt2 when other backend attached this global temp table

To drop the GTT from any session, we need to exit from all other sessions.
postgres=# drop table gtt2 ;
DROP TABLE

Kindly let me know if I am missing something.

On Wed, Apr 1, 2020 at 6:26 PM Prabhat Sahu <prabhat.sahu@enterprisedb.com <mailto:prabhat.sahu@enterprisedb.com>> wrote:
Hi Wenjing,
I hope we need to change the below error message.

postgres=# create global temporary table gtt(c1 int) on commit preserve rows;
CREATE TABLE

postgres=# create materialized view mvw as select * from gtt;
ERROR: materialized views must not use global temporary tables or views

Anyways we are not allowed to create a "global temporary view",
so the above ERROR message should change(i.e. " or view" need to be removed from the error message) something like:
"ERROR: materialized views must not use global temporary tables"

--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

Attachments:

smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#258Pavel Stehule
pavel.stehule@gmail.com
In reply to: 曾文旌 (#257)
Re: [Proposal] Global temporary tables

čt 23. 4. 2020 v 9:10 odesílatel 曾文旌 <wenjing.zwj@alibaba-inc.com> napsal:

2020年4月22日 下午10:50,Pavel Stehule <pavel.stehule@gmail.com> 写道:

st 22. 4. 2020 v 16:38 odesílatel Prabhat Sahu <
prabhat.sahu@enterprisedb.com> napsal:

On Wed, Apr 22, 2020 at 2:49 PM 曾文旌 <wenjing.zwj@alibaba-inc.com> wrote:

Although the implementation of GTT is different, I think so TRUNCATE on
Postgres (when it is really finalized) can remove session metadata of GTT
too (and reduce usage's counter). It is not critical feature, but I think
so it should not be hard to implement. From practical reason can be nice to
have a tool how to refresh GTT without a necessity to close session.
TRUNCATE can be this tool.

Sorry, I don't quite understand what you mean, could you describe it in

detail?
In my opinion the TRUNCATE GTT cannot clean up data in other sessions,
especially clean up local buffers in other sessions.

It is about a possibility to force reset GTT to default empty state for all
sessions.

Maybe it is some what does your TRUNCATE DROP, but I don't think so this
design (TRUNCATE DROP) is good, because then user have to know
implementation detail.

I prefer some like TRUNCATE tab WITH OPTION (GLOBAL, FORCE) - "GLOBAL" ..
apply on all sessions, FORCE try to do without waiting on some global lock,
try to do immediately with possibility to cancel some statements and
rollback some session.

instead GLOBAL maybe we can use "ALLSESSION", or "ALL SESSION" or some else

But I like possible terminology LOCAL x GLOBAL for GTT. What I mean? Some
statements like "TRUNCATE" can works (by default) in "local" mode .. it
has impact to current session only. But sometimes can be executed in
"global" mode with effect on all sessions.

Yes, I think we need a way to delete the GTT local storage without closing

the session.

I provide the TRUNCATE tablename DROP to clear the data in the GTT and
delete the storage files.
This feature requires the current transaction to commit immediately
after it finishes truncate.

Hi Wenjing,
Thanks for the patch(v30) for the new syntax support for (TRUNCATE
table_name DROP) for deleting storage files after TRUNCATE on GTT.

This syntax looks strange, and I don't think so it solve anything in
practical life, because without lock the table will be used in few seconds
by other sessions.

If a dba wants to delete or modify a GTT, he can use locks to help him
make the change.

postgres=# begin;
BEGIN
postgres=*# LOCK TABLE gtt2 IN ACCESS EXCLUSIVE MODE;
postgres=*# select * from pg_gtt_attached_pids ;

Kill session or let session do TRUNCATE tablename DROP

postgres=*# drop table gtt2;
DROP TABLE
postgres=*# commit;
COMMIT

yes, user can lock a tables. But I think so it is user friendly design. I
don't remember any statement in Postgres, where I have to use table locks
explicitly.

For builtin commands it should be done transparently (for user).

Regards

Pavel

Show quoted text

This is same topic when we talked about ALTER - when and where the changes
should be applied.

The CLUSTER commands works only on session private data, so it should not
to need some special lock or some special cleaning before.

Regards

Pavel

Please check below scenarios:

*Case1:*-- session1:
postgres=# create global temporary table gtt2 (c1 integer) on commit
preserve rows;
CREATE TABLE
postgres=# create index idx1 on gtt2 (c1);
CREATE INDEX
postgres=# create index idx2 on gtt2 (c1) where c1%2 =0;
CREATE INDEX
postgres=#
postgres=# CLUSTER gtt2 USING idx1;
CLUSTER
postgres=# CLUSTER gtt2 USING idx2;
ERROR: cannot cluster on partial index "idx2"

*Case2:*-- Session2:
postgres=# CLUSTER gtt2 USING idx1;
CLUSTER
postgres=# CLUSTER gtt2 USING idx2;
CLUSTER

postgres=# insert into gtt2 values(1);
INSERT 0 1
postgres=# CLUSTER gtt2 USING idx1;
CLUSTER
postgres=# CLUSTER gtt2 USING idx2;
ERROR: cannot cluster on partial index "idx2"

*Case3:*-- Session2:
postgres=# TRUNCATE gtt2 DROP;
TRUNCATE TABLE
postgres=# CLUSTER gtt2 USING idx1;
CLUSTER
postgres=# CLUSTER gtt2 USING idx2;
CLUSTER

In Case2, Case3 we can observe, with the absence of data in GTT, we are
able to "CLUSTER gtt2 USING idx2;" (having partial index)
But why does the same query fail for Case1 (absence of data)?

Thanks,
Prabhat Sahu

Wenjing

Regards

Pavel

All in all, I think the current implementation is sufficient for dba to
manage GTT.

2020年4月2日 下午4:45,Prabhat Sahu <prabhat.sahu@enterprisedb.com> 写道:

Hi All,

I have noted down few behavioral difference in our GTT implementation
in PG as compared to Oracle DB:
As per my understanding, the behavior of DROP TABLE in case of "Normal
table and GTT" in Oracle DB are as below:

1. Any tables(Normal table / GTT) without having data in a session,
we will be able to DROP from another session.
2. For a completed transaction on a normal table having data, we
will be able to DROP from another session. If the transaction is not yet
complete, and we are trying to drop the table from another session, then we
will get an error. (working as expected)
3. For a completed transaction on GTT with(on commit delete rows)
(i.e. no data in GTT) in a session, we will be able to DROP from another
session.
4. For a completed transaction on GTT with(on commit preserve rows)
with data in a session, we will not be able to DROP from any session(not
even from the session in which GTT is created), we need to truncate the
table data first from all the session(session1, session2) which is having
data.

*1. Any tables(Normal table / GTT) without having data in a session, we
will be able to DROP from another session.*
*Session1:*
create table t1 (c1 integer);
create global temporary table gtt1 (c1 integer) on commit delete rows;
create global temporary table gtt2 (c1 integer) on commit preserve rows;

*Session2:*
drop table t1;
drop table gtt1;
drop table gtt2;

-- *Issue 1:* But we are able to drop a simple table and failed to
drop GTT as below.

postgres=# drop table t1;
DROP TABLE
postgres=# drop table gtt1;
ERROR: can not drop relation gtt1 when other backend attached this
global temp table
postgres=# drop table gtt2;
ERROR: can not drop relation gtt2 when other backend attached this
global temp table

*3. For a completed transaction on GTT with(on commit delete rows)
(i.e. no data in GTT) in a session, we will be able to DROP from another
session.*

*Session1:*create global temporary table gtt1 (c1 integer) on commit
delete rows;

*Session2:*
drop table gtt1;

-- *Issue 2:* But we are getting error for GTT
with(on_commit_delete_rows) without data.

postgres=# drop table gtt1;
ERROR: can not drop relation gtt1 when other backend attached this
global temp table

*4. For a completed transaction on GTT with(on commit preserve
rows) with data in any session, we will not be able to DROP from any
session(not even from the session in which GTT is created)*

*Case1:*
create global temporary table gtt2 (c1 integer) on commit preserve rows;
insert into gtt2 values(100);
drop table gtt2;

SQL> drop table gtt2;
drop table gtt2
*
ERROR at line 1:
ORA-14452: attempt to create, alter or drop an index on temporary table
already in use

-- *Issue 3:* But, we are able to drop the GTT(having data) which we
have created in the same session.

postgres=# drop table gtt2;
DROP TABLE

*Case2: GTT with(on commit preserve rows) having data in both session1
and session2Session1:*create global temporary table gtt2 (c1 integer)
on commit preserve rows;
insert into gtt2 values(100);

*Session2:*insert into gtt2 values(200);

-- If we try to drop the table from any session we should get an error,
it is working fine.
drop table gtt2;

SQL> drop table gtt2;
drop table gtt2
*
ERROR at line 1:
ORA-14452: attempt to create, alter or drop an index on temporary table
already in use

postgres=# drop table gtt2 ;
ERROR: can not drop relation gtt2 when other backend attached this
global temp table

-- To drop the table gtt2 from any session1/session2, we need to
truncate the table data first from all the session(session1, session2)
which is having data.
*Session1:*
truncate table gtt2;
-- Session2:
truncate table gtt2;

*Session 2:*
SQL> drop table gtt2;

Table dropped.

-- *Issue 4:* But we are not able to drop the GTT, even after TRUNCATE
the table in all the sessions.
-- truncate from all sessions where GTT have data.
postgres=# truncate gtt2 ;
TRUNCATE TABLE

-- *try to DROP GTT still, we are getting error.*

postgres=# drop table gtt2 ;
ERROR: can not drop relation gtt2 when other backend attached this
global temp table

To drop the GTT from any session, we need to exit from all other
sessions.
postgres=# drop table gtt2 ;
DROP TABLE

Kindly let me know if I am missing something.

On Wed, Apr 1, 2020 at 6:26 PM Prabhat Sahu <
prabhat.sahu@enterprisedb.com> wrote:

Hi Wenjing,
I hope we need to change the below error message.

postgres=# create global temporary table gtt(c1 int) on commit
preserve rows;
CREATE TABLE

postgres=# create materialized view mvw as select * from gtt;
ERROR: materialized views must not use global temporary tables* or
views*

Anyways we are not allowed to create a "global temporary view",
so the above ERROR message should change(i.e. *" or view"* need to be
removed from the error message) something like:
*"ERROR: materialized views must not use global temporary tables"*

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

#259Prabhat Sahu
prabhat.sahu@enterprisedb.com
In reply to: Pavel Stehule (#258)
Re: [Proposal] Global temporary tables

Hi Wenjing,

Please check, the server getting crash with the below scenario(CLUSTER gtt
using INDEX).

*-- Session1:*
postgres=# create global temporary table gtt (c1 integer) on commit
preserve rows;
CREATE TABLE
postgres=# create index idx1 on gtt (c1);
CREATE INDEX

*-- Session2:*
postgres=# create index idx2 on gtt (c1);
CREATE INDEX

*-- Session1:*
postgres=# cluster gtt using idx1;
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.
!?>

*-- Below is the stacktrace:*
[edb@localhost bin]$ gdb -q -c data/core.95690 postgres
Reading symbols from
/home/edb/PG/PGsrcNew/postgresql/inst/bin/postgres...done.
[New LWP 95690]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Core was generated by `postgres: edb postgres [local] CLUSTER
'.
Program terminated with signal 6, Aborted.
#0 0x00007f9c574ee337 in raise () from /lib64/libc.so.6
Missing separate debuginfos, use: debuginfo-install
glibc-2.17-292.el7.x86_64 keyutils-libs-1.5.8-3.el7.x86_64
krb5-libs-1.15.1-37.el7_7.2.x86_64 libcom_err-1.42.9-16.el7.x86_64
libgcc-4.8.5-39.el7.x86_64 libselinux-2.5-14.1.el7.x86_64
openssl-libs-1.0.2k-19.el7.x86_64 pcre-8.32-17.el7.x86_64
zlib-1.2.7-18.el7.x86_64
(gdb) bt
#0 0x00007f9c574ee337 in raise () from /lib64/libc.so.6
#1 0x00007f9c574efa28 in abort () from /lib64/libc.so.6
#2 0x0000000000ab3a3c in ExceptionalCondition (conditionName=0xb5e2e8
"!ReindexIsProcessingIndex(indexOid)", errorType=0xb5d365
"FailedAssertion",
fileName=0xb5d4e9 "index.c", lineNumber=3825) at assert.c:67
#3 0x00000000005b0412 in reindex_relation (relid=16384, flags=2,
options=0) at index.c:3825
#4 0x000000000065e36d in finish_heap_swap (OIDOldHeap=16384,
OIDNewHeap=16389, is_system_catalog=false, swap_toast_by_content=false,
check_constraints=false, is_internal=true, frozenXid=491,
cutoffMulti=1, newrelpersistence=103 'g') at cluster.c:1448
#5 0x000000000065ccef in rebuild_relation (OldHeap=0x7f9c589adef0,
indexOid=16387, verbose=false) at cluster.c:602
#6 0x000000000065c757 in cluster_rel (tableOid=16384, indexOid=16387,
options=0) at cluster.c:418
#7 0x000000000065c2cf in cluster (stmt=0x2cd1600, isTopLevel=true) at
cluster.c:180
#8 0x000000000093b213 in standard_ProcessUtility (pstmt=0x2cd16c8,
queryString=0x2cd0b30 "cluster gtt using idx1;",
context=PROCESS_UTILITY_TOPLEVEL,
params=0x0, queryEnv=0x0, dest=0x2cd19a8, qc=0x7ffcd32604b0) at
utility.c:819
#9 0x000000000093aa50 in ProcessUtility (pstmt=0x2cd16c8,
queryString=0x2cd0b30 "cluster gtt using idx1;",
context=PROCESS_UTILITY_TOPLEVEL, params=0x0,
queryEnv=0x0, dest=0x2cd19a8, qc=0x7ffcd32604b0) at utility.c:522
#10 0x00000000009398c2 in PortalRunUtility (portal=0x2d36ba0,
pstmt=0x2cd16c8, isTopLevel=true, setHoldSnapshot=false, dest=0x2cd19a8,
qc=0x7ffcd32604b0)
at pquery.c:1157
#11 0x0000000000939ad8 in PortalRunMulti (portal=0x2d36ba0,
isTopLevel=true, setHoldSnapshot=false, dest=0x2cd19a8, altdest=0x2cd19a8,
qc=0x7ffcd32604b0)
at pquery.c:1303
#12 0x0000000000938ff6 in PortalRun (portal=0x2d36ba0,
count=9223372036854775807, isTopLevel=true, run_once=true, dest=0x2cd19a8,
altdest=0x2cd19a8,
qc=0x7ffcd32604b0) at pquery.c:779
#13 0x00000000009331b0 in exec_simple_query (query_string=0x2cd0b30
"cluster gtt using idx1;") at postgres.c:1239
#14 0x00000000009371bc in PostgresMain (argc=1, argv=0x2cfab80,
dbname=0x2cfaa78 "postgres", username=0x2cfaa58 "edb") at postgres.c:4315
#15 0x00000000008872a9 in BackendRun (port=0x2cf2b50) at postmaster.c:4510
#16 0x0000000000886a9e in BackendStartup (port=0x2cf2b50) at
postmaster.c:4202
#17 0x000000000088301c in ServerLoop () at postmaster.c:1727
#18 0x00000000008828f3 in PostmasterMain (argc=3, argv=0x2ccb460) at
postmaster.c:1400
#19 0x0000000000789c54 in main (argc=3, argv=0x2ccb460) at main.c:210
(gdb)

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

#260Prabhat Sahu
prabhat.sahu@enterprisedb.com
In reply to: Prabhat Sahu (#259)
Re: [Proposal] Global temporary tables

Hi Wenjing,

With the new patch(v30) as you mentioned the new syntax support for
"TRUNCATE TABLE gtt DROP", but we also observe the syntax "DROP TABLE gtt
DROP" is working as below:

postgres=# create global temporary table gtt(c1 int) on commit preserve
rows;
CREATE TABLE
postgres=# DROP TABLE gtt DROP;
DROP TABLE

Does this syntax intensional? If not, we should get a syntax error.

On Fri, Apr 24, 2020 at 10:25 AM Prabhat Sahu <prabhat.sahu@enterprisedb.com>
wrote:

Hi Wenjing,

Please check, the server getting crash with the below scenario(CLUSTER gtt
using INDEX).

*-- Session1:*
postgres=# create global temporary table gtt (c1 integer) on commit
preserve rows;
CREATE TABLE
postgres=# create index idx1 on gtt (c1);
CREATE INDEX

*-- Session2:*
postgres=# create index idx2 on gtt (c1);
CREATE INDEX

*-- Session1:*
postgres=# cluster gtt using idx1;
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.
!?>

*-- Below is the stacktrace:*
[edb@localhost bin]$ gdb -q -c data/core.95690 postgres
Reading symbols from
/home/edb/PG/PGsrcNew/postgresql/inst/bin/postgres...done.
[New LWP 95690]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Core was generated by `postgres: edb postgres [local] CLUSTER
'.
Program terminated with signal 6, Aborted.
#0 0x00007f9c574ee337 in raise () from /lib64/libc.so.6
Missing separate debuginfos, use: debuginfo-install
glibc-2.17-292.el7.x86_64 keyutils-libs-1.5.8-3.el7.x86_64
krb5-libs-1.15.1-37.el7_7.2.x86_64 libcom_err-1.42.9-16.el7.x86_64
libgcc-4.8.5-39.el7.x86_64 libselinux-2.5-14.1.el7.x86_64
openssl-libs-1.0.2k-19.el7.x86_64 pcre-8.32-17.el7.x86_64
zlib-1.2.7-18.el7.x86_64
(gdb) bt
#0 0x00007f9c574ee337 in raise () from /lib64/libc.so.6
#1 0x00007f9c574efa28 in abort () from /lib64/libc.so.6
#2 0x0000000000ab3a3c in ExceptionalCondition (conditionName=0xb5e2e8
"!ReindexIsProcessingIndex(indexOid)", errorType=0xb5d365
"FailedAssertion",
fileName=0xb5d4e9 "index.c", lineNumber=3825) at assert.c:67
#3 0x00000000005b0412 in reindex_relation (relid=16384, flags=2,
options=0) at index.c:3825
#4 0x000000000065e36d in finish_heap_swap (OIDOldHeap=16384,
OIDNewHeap=16389, is_system_catalog=false, swap_toast_by_content=false,
check_constraints=false, is_internal=true, frozenXid=491,
cutoffMulti=1, newrelpersistence=103 'g') at cluster.c:1448
#5 0x000000000065ccef in rebuild_relation (OldHeap=0x7f9c589adef0,
indexOid=16387, verbose=false) at cluster.c:602
#6 0x000000000065c757 in cluster_rel (tableOid=16384, indexOid=16387,
options=0) at cluster.c:418
#7 0x000000000065c2cf in cluster (stmt=0x2cd1600, isTopLevel=true) at
cluster.c:180
#8 0x000000000093b213 in standard_ProcessUtility (pstmt=0x2cd16c8,
queryString=0x2cd0b30 "cluster gtt using idx1;",
context=PROCESS_UTILITY_TOPLEVEL,
params=0x0, queryEnv=0x0, dest=0x2cd19a8, qc=0x7ffcd32604b0) at
utility.c:819
#9 0x000000000093aa50 in ProcessUtility (pstmt=0x2cd16c8,
queryString=0x2cd0b30 "cluster gtt using idx1;",
context=PROCESS_UTILITY_TOPLEVEL, params=0x0,
queryEnv=0x0, dest=0x2cd19a8, qc=0x7ffcd32604b0) at utility.c:522
#10 0x00000000009398c2 in PortalRunUtility (portal=0x2d36ba0,
pstmt=0x2cd16c8, isTopLevel=true, setHoldSnapshot=false, dest=0x2cd19a8,
qc=0x7ffcd32604b0)
at pquery.c:1157
#11 0x0000000000939ad8 in PortalRunMulti (portal=0x2d36ba0,
isTopLevel=true, setHoldSnapshot=false, dest=0x2cd19a8, altdest=0x2cd19a8,
qc=0x7ffcd32604b0)
at pquery.c:1303
#12 0x0000000000938ff6 in PortalRun (portal=0x2d36ba0,
count=9223372036854775807, isTopLevel=true, run_once=true, dest=0x2cd19a8,
altdest=0x2cd19a8,
qc=0x7ffcd32604b0) at pquery.c:779
#13 0x00000000009331b0 in exec_simple_query (query_string=0x2cd0b30
"cluster gtt using idx1;") at postgres.c:1239
#14 0x00000000009371bc in PostgresMain (argc=1, argv=0x2cfab80,
dbname=0x2cfaa78 "postgres", username=0x2cfaa58 "edb") at postgres.c:4315
#15 0x00000000008872a9 in BackendRun (port=0x2cf2b50) at postmaster.c:4510
#16 0x0000000000886a9e in BackendStartup (port=0x2cf2b50) at
postmaster.c:4202
#17 0x000000000088301c in ServerLoop () at postmaster.c:1727
#18 0x00000000008828f3 in PostmasterMain (argc=3, argv=0x2ccb460) at
postmaster.c:1400
#19 0x0000000000789c54 in main (argc=3, argv=0x2ccb460) at main.c:210
(gdb)

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

#261tushar
tushar.ahuja@enterprisedb.com
In reply to: 曾文旌 (#253)
Re: [Proposal] Global temporary tables

On 4/22/20 2:49 PM, 曾文旌 wrote:

I provide the TRUNCATE tablename DROP to clear the data in the GTT and
delete the storage files.
This feature requires the current transaction to commit immediately
after it finishes truncate.

Thanks Wenjing , Please refer this scenario

postgres=# create global temp table testing (a int);
CREATE TABLE
postgres=# begin;
BEGIN
postgres=*# truncate testing;      -- working   [1]
TRUNCATE TABLE
postgres=*# truncate testing drop;
ERROR:  Truncate global temporary table cannot run inside a transaction
block    --that is throwing an error claiming something which i did 
successfully [1]
postgres=!#

--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company

#262曾文旌
wenjing.zwj@alibaba-inc.com
In reply to: Pavel Stehule (#258)
1 attachment(s)
Re: [Proposal] Global temporary tables

2020年4月23日 下午3:43,Pavel Stehule <pavel.stehule@gmail.com> 写道:

čt 23. 4. 2020 v 9:10 odesílatel 曾文旌 <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:

2020年4月22日 下午10:50,Pavel Stehule <pavel.stehule@gmail.com <mailto:pavel.stehule@gmail.com>> 写道:

st 22. 4. 2020 v 16:38 odesílatel Prabhat Sahu <prabhat.sahu@enterprisedb.com <mailto:prabhat.sahu@enterprisedb.com>> napsal:

On Wed, Apr 22, 2020 at 2:49 PM 曾文旌 <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> wrote:

Although the implementation of GTT is different, I think so TRUNCATE on Postgres (when it is really finalized) can remove session metadata of GTT too (and reduce usage's counter). It is not critical feature, but I think so it should not be hard to implement. From practical reason can be nice to have a tool how to refresh GTT without a necessity to close session. TRUNCATE can be this tool.

Sorry, I don't quite understand what you mean, could you describe it in detail?
In my opinion the TRUNCATE GTT cannot clean up data in other sessions, especially clean up local buffers in other sessions.

It is about a possibility to force reset GTT to default empty state for all sessions.

Maybe it is some what does your TRUNCATE DROP, but I don't think so this design (TRUNCATE DROP) is good, because then user have to know implementation detail.

I prefer some like TRUNCATE tab WITH OPTION (GLOBAL, FORCE) - "GLOBAL" .. apply on all sessions, FORCE try to do without waiting on some global lock, try to do immediately with possibility to cancel some statements and rollback some session.

instead GLOBAL maybe we can use "ALLSESSION", or "ALL SESSION" or some else

But I like possible terminology LOCAL x GLOBAL for GTT. What I mean? Some statements like "TRUNCATE" can works (by default) in "local" mode .. it has impact to current session only. But sometimes can be executed in "global" mode with effect on all sessions.

The TRUNCATE GTT GLOBAL like DROP GTT FORCE you mentioned that before.
I think this requires identifying sessions that have initialized the stored file and no actual data.
And Handling local buffers on other session and locks is also difficult.
It may be harder than dropping the GTT force, which can kill other sessions, but TRUNCATE GTT would prefer not to.
This doesn't seem to complete the basic conditions, it's not easy.
So, I want to put this feature in next releases, along with DROP GTT FORCE.
Also, in view of your comments, I roll back the feature of TRUNCATE GTT DROP.

Wenjing

Yes, I think we need a way to delete the GTT local storage without closing the session.

I provide the TRUNCATE tablename DROP to clear the data in the GTT and delete the storage files.
This feature requires the current transaction to commit immediately after it finishes truncate.

Hi Wenjing,
Thanks for the patch(v30) for the new syntax support for (TRUNCATE table_name DROP) for deleting storage files after TRUNCATE on GTT.

This syntax looks strange, and I don't think so it solve anything in practical life, because without lock the table will be used in few seconds by other sessions.

If a dba wants to delete or modify a GTT, he can use locks to help him make the change.

postgres=# begin;
BEGIN
postgres=*# LOCK TABLE gtt2 IN ACCESS EXCLUSIVE MODE;
postgres=*# select * from pg_gtt_attached_pids ;

Kill session or let session do TRUNCATE tablename DROP

postgres=*# drop table gtt2;
DROP TABLE
postgres=*# commit;
COMMIT

yes, user can lock a tables. But I think so it is user friendly design. I don't remember any statement in Postgres, where I have to use table locks explicitly.

For builtin commands it should be done transparently (for user).

It can be improved ,like DROP GTT FORCE.

Show quoted text

Regards

Pavel

This is same topic when we talked about ALTER - when and where the changes should be applied.

The CLUSTER commands works only on session private data, so it should not to need some special lock or some special cleaning before.

Regards

Pavel

Please check below scenarios:

Case1:
-- session1:
postgres=# create global temporary table gtt2 (c1 integer) on commit preserve rows;
CREATE TABLE
postgres=# create index idx1 on gtt2 (c1);
CREATE INDEX
postgres=# create index idx2 on gtt2 (c1) where c1%2 =0;
CREATE INDEX
postgres=#
postgres=# CLUSTER gtt2 USING idx1;
CLUSTER
postgres=# CLUSTER gtt2 USING idx2;
ERROR: cannot cluster on partial index "idx2"

Case2:
-- Session2:
postgres=# CLUSTER gtt2 USING idx1;
CLUSTER
postgres=# CLUSTER gtt2 USING idx2;
CLUSTER

postgres=# insert into gtt2 values(1);
INSERT 0 1
postgres=# CLUSTER gtt2 USING idx1;
CLUSTER
postgres=# CLUSTER gtt2 USING idx2;
ERROR: cannot cluster on partial index "idx2"

Case3:
-- Session2:
postgres=# TRUNCATE gtt2 DROP;
TRUNCATE TABLE
postgres=# CLUSTER gtt2 USING idx1;
CLUSTER
postgres=# CLUSTER gtt2 USING idx2;
CLUSTER

In Case2, Case3 we can observe, with the absence of data in GTT, we are able to "CLUSTER gtt2 USING idx2;" (having partial index)
But why does the same query fail for Case1 (absence of data)?

Thanks,
Prabhat Sahu

Wenjing

Regards

Pavel

All in all, I think the current implementation is sufficient for dba to manage GTT.

2020年4月2日 下午4:45,Prabhat Sahu <prabhat.sahu@enterprisedb.com <mailto:prabhat.sahu@enterprisedb.com>> 写道:

Hi All,

I have noted down few behavioral difference in our GTT implementation in PG as compared to Oracle DB:
As per my understanding, the behavior of DROP TABLE in case of "Normal table and GTT" in Oracle DB are as below:
Any tables(Normal table / GTT) without having data in a session, we will be able to DROP from another session.
For a completed transaction on a normal table having data, we will be able to DROP from another session. If the transaction is not yet complete, and we are trying to drop the table from another session, then we will get an error. (working as expected)
For a completed transaction on GTT with(on commit delete rows) (i.e. no data in GTT) in a session, we will be able to DROP from another session.
For a completed transaction on GTT with(on commit preserve rows) with data in a session, we will not be able to DROP from any session(not even from the session in which GTT is created), we need to truncate the table data first from all the session(session1, session2) which is having data.
1. Any tables(Normal table / GTT) without having data in a session, we will be able to DROP from another session.
Session1:
create table t1 (c1 integer);
create global temporary table gtt1 (c1 integer) on commit delete rows;
create global temporary table gtt2 (c1 integer) on commit preserve rows;

Session2:
drop table t1;
drop table gtt1;
drop table gtt2;

-- Issue 1: But we are able to drop a simple table and failed to drop GTT as below.
postgres=# drop table t1;
DROP TABLE
postgres=# drop table gtt1;
ERROR: can not drop relation gtt1 when other backend attached this global temp table
postgres=# drop table gtt2;
ERROR: can not drop relation gtt2 when other backend attached this global temp table

3. For a completed transaction on GTT with(on commit delete rows) (i.e. no data in GTT) in a session, we will be able to DROP from another session.
Session1:
create global temporary table gtt1 (c1 integer) on commit delete rows;

Session2:
drop table gtt1;

-- Issue 2: But we are getting error for GTT with(on_commit_delete_rows) without data.
postgres=# drop table gtt1;
ERROR: can not drop relation gtt1 when other backend attached this global temp table

4. For a completed transaction on GTT with(on commit preserve rows) with data in any session, we will not be able to DROP from any session(not even from the session in which GTT is created)

Case1:
create global temporary table gtt2 (c1 integer) on commit preserve rows;
insert into gtt2 values(100);
drop table gtt2;

SQL> drop table gtt2;
drop table gtt2
*
ERROR at line 1:
ORA-14452: attempt to create, alter or drop an index on temporary table already in use

-- Issue 3: But, we are able to drop the GTT(having data) which we have created in the same session.
postgres=# drop table gtt2;
DROP TABLE

Case2: GTT with(on commit preserve rows) having data in both session1 and session2
Session1:
create global temporary table gtt2 (c1 integer) on commit preserve rows;
insert into gtt2 values(100);

Session2:
insert into gtt2 values(200);

-- If we try to drop the table from any session we should get an error, it is working fine.
drop table gtt2;
SQL> drop table gtt2;
drop table gtt2
*
ERROR at line 1:
ORA-14452: attempt to create, alter or drop an index on temporary table already in use

postgres=# drop table gtt2 ;
ERROR: can not drop relation gtt2 when other backend attached this global temp table

-- To drop the table gtt2 from any session1/session2, we need to truncate the table data first from all the session(session1, session2) which is having data.
Session1:
truncate table gtt2;
-- Session2:
truncate table gtt2;

Session 2:
SQL> drop table gtt2;

Table dropped.

-- Issue 4: But we are not able to drop the GTT, even after TRUNCATE the table in all the sessions.
-- truncate from all sessions where GTT have data.
postgres=# truncate gtt2 ;
TRUNCATE TABLE

-- try to DROP GTT still, we are getting error.
postgres=# drop table gtt2 ;
ERROR: can not drop relation gtt2 when other backend attached this global temp table

To drop the GTT from any session, we need to exit from all other sessions.
postgres=# drop table gtt2 ;
DROP TABLE

Kindly let me know if I am missing something.

On Wed, Apr 1, 2020 at 6:26 PM Prabhat Sahu <prabhat.sahu@enterprisedb.com <mailto:prabhat.sahu@enterprisedb.com>> wrote:
Hi Wenjing,
I hope we need to change the below error message.

postgres=# create global temporary table gtt(c1 int) on commit preserve rows;
CREATE TABLE

postgres=# create materialized view mvw as select * from gtt;
ERROR: materialized views must not use global temporary tables or views

Anyways we are not allowed to create a "global temporary view",
so the above ERROR message should change(i.e. " or view" need to be removed from the error message) something like:
"ERROR: materialized views must not use global temporary tables"

--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

Attachments:

smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#263Prabhat Sahu
prabhat.sahu@enterprisedb.com
In reply to: 曾文旌 (#262)
Re: [Proposal] Global temporary tables

Hi Wenjing,

Please check the below scenario shows different error message with "DROP
TABLE gtt;" for gtt with and without index.

*-- Session1:*postgres=# create global temporary table gtt1 (c1 int);
CREATE TABLE
postgres=# create global temporary table gtt2 (c1 int);
CREATE TABLE
postgres=# create index idx2 on gtt2(c1);
CREATE INDEX

*-- Session2:*postgres=# drop table gtt1;
ERROR: can not drop relation gtt1 when other backend attached this global
temp table
postgres=# drop table gtt2;
ERROR: can not drop index gtt2 when other backend attached this global
temp table.

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

#264曾文旌
wenjing.zwj@alibaba-inc.com
In reply to: Prabhat Sahu (#259)
2 attachment(s)
Re: [Proposal] Global temporary tables

2020年4月24日 下午12:55,Prabhat Sahu <prabhat.sahu@enterprisedb.com> 写道:

Hi Wenjing,

Please check, the server getting crash with the below scenario(CLUSTER gtt using INDEX).

-- Session1:
postgres=# create global temporary table gtt (c1 integer) on commit preserve rows;
CREATE TABLE
postgres=# create index idx1 on gtt (c1);
CREATE INDEX

-- Session2:
postgres=# create index idx2 on gtt (c1);
CREATE INDEX

-- Session1:
postgres=# cluster gtt using idx1;
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.

Thanks for review, I fixed In v31.

Wenjing

Show quoted text

!?>

-- Below is the stacktrace:
[edb@localhost bin]$ gdb -q -c data/core.95690 postgres
Reading symbols from /home/edb/PG/PGsrcNew/postgresql/inst/bin/postgres...done.
[New LWP 95690]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Core was generated by `postgres: edb postgres [local] CLUSTER '.
Program terminated with signal 6, Aborted.
#0 0x00007f9c574ee337 in raise () from /lib64/libc.so.6
Missing separate debuginfos, use: debuginfo-install glibc-2.17-292.el7.x86_64 keyutils-libs-1.5.8-3.el7.x86_64 krb5-libs-1.15.1-37.el7_7.2.x86_64 libcom_err-1.42.9-16.el7.x86_64 libgcc-4.8.5-39.el7.x86_64 libselinux-2.5-14.1.el7.x86_64 openssl-libs-1.0.2k-19.el7.x86_64 pcre-8.32-17.el7.x86_64 zlib-1.2.7-18.el7.x86_64
(gdb) bt
#0 0x00007f9c574ee337 in raise () from /lib64/libc.so.6
#1 0x00007f9c574efa28 in abort () from /lib64/libc.so.6
#2 0x0000000000ab3a3c in ExceptionalCondition (conditionName=0xb5e2e8 "!ReindexIsProcessingIndex(indexOid)", errorType=0xb5d365 "FailedAssertion",
fileName=0xb5d4e9 "index.c", lineNumber=3825) at assert.c:67
#3 0x00000000005b0412 in reindex_relation (relid=16384, flags=2, options=0) at index.c:3825
#4 0x000000000065e36d in finish_heap_swap (OIDOldHeap=16384, OIDNewHeap=16389, is_system_catalog=false, swap_toast_by_content=false,
check_constraints=false, is_internal=true, frozenXid=491, cutoffMulti=1, newrelpersistence=103 'g') at cluster.c:1448
#5 0x000000000065ccef in rebuild_relation (OldHeap=0x7f9c589adef0, indexOid=16387, verbose=false) at cluster.c:602
#6 0x000000000065c757 in cluster_rel (tableOid=16384, indexOid=16387, options=0) at cluster.c:418
#7 0x000000000065c2cf in cluster (stmt=0x2cd1600, isTopLevel=true) at cluster.c:180
#8 0x000000000093b213 in standard_ProcessUtility (pstmt=0x2cd16c8, queryString=0x2cd0b30 "cluster gtt using idx1;", context=PROCESS_UTILITY_TOPLEVEL,
params=0x0, queryEnv=0x0, dest=0x2cd19a8, qc=0x7ffcd32604b0) at utility.c:819
#9 0x000000000093aa50 in ProcessUtility (pstmt=0x2cd16c8, queryString=0x2cd0b30 "cluster gtt using idx1;", context=PROCESS_UTILITY_TOPLEVEL, params=0x0,
queryEnv=0x0, dest=0x2cd19a8, qc=0x7ffcd32604b0) at utility.c:522
#10 0x00000000009398c2 in PortalRunUtility (portal=0x2d36ba0, pstmt=0x2cd16c8, isTopLevel=true, setHoldSnapshot=false, dest=0x2cd19a8, qc=0x7ffcd32604b0)
at pquery.c:1157
#11 0x0000000000939ad8 in PortalRunMulti (portal=0x2d36ba0, isTopLevel=true, setHoldSnapshot=false, dest=0x2cd19a8, altdest=0x2cd19a8, qc=0x7ffcd32604b0)
at pquery.c:1303
#12 0x0000000000938ff6 in PortalRun (portal=0x2d36ba0, count=9223372036854775807, isTopLevel=true, run_once=true, dest=0x2cd19a8, altdest=0x2cd19a8,
qc=0x7ffcd32604b0) at pquery.c:779
#13 0x00000000009331b0 in exec_simple_query (query_string=0x2cd0b30 "cluster gtt using idx1;") at postgres.c:1239
#14 0x00000000009371bc in PostgresMain (argc=1, argv=0x2cfab80, dbname=0x2cfaa78 "postgres", username=0x2cfaa58 "edb") at postgres.c:4315
#15 0x00000000008872a9 in BackendRun (port=0x2cf2b50) at postmaster.c:4510
#16 0x0000000000886a9e in BackendStartup (port=0x2cf2b50) at postmaster.c:4202
#17 0x000000000088301c in ServerLoop () at postmaster.c:1727
#18 0x00000000008828f3 in PostmasterMain (argc=3, argv=0x2ccb460) at postmaster.c:1400
#19 0x0000000000789c54 in main (argc=3, argv=0x2ccb460) at main.c:210
(gdb)

--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

Attachments:

global_temporary_table_v31-pg13.patchapplication/octet-stream; name=global_temporary_table_v31-pg13.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 8ccc228..b061de9 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -168,6 +168,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * For global temp table only
+	 * use ShareUpdateExclusiveLock for ensure safety
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temp table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},	
 	/* list terminator */
 	{{NULL}}
 };
@@ -1817,6 +1830,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1961,13 +1976,18 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	/*
 	 * There are no options for partitioned tables yet, but this is able to do
 	 * some validation.
 	 */
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index 765329b..4761fdc 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1011,7 +1011,9 @@ gistproperty(Oid index_oid, int attno,
 XLogRecPtr
 gistGetFakeLSN(Relation rel)
 {
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	/* global temp is same as local temp table */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 3ec6d52..1222594 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -150,7 +150,9 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	/* global temp table is same as local temp table */
+	if (!(index->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		index->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 56b3562..d3443ec 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -587,7 +587,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -640,7 +640,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 3c18db2..b08a1fc 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -62,6 +62,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -431,9 +432,9 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
 	Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
-	/* not every AM requires these to be valid, but heap does */
-	Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
-	Assert(MultiXactIdIsValid(onerel->rd_rel->relminmxid));
+	/* not every AM requires these to be valid, but regular heap does */
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ MultiXactIdIsValid(onerel->rd_rel->relminmxid));
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index f5962f6..045c0ce 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -601,6 +602,10 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 065eb27..8427894 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6433,6 +6433,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/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 5eaca27..e5002aa 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -231,7 +231,8 @@ Boot_CreateStmt:
 												   mapped_relation,
 												   true,
 												   &relfrozenxid,
-												   &relminmxid);
+												   &relminmxid,
+												   false);
 						elog(DEBUG4, "bootstrap relation created");
 					}
 					else
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 9499bb3..ae47364 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -43,6 +43,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 7d6acae..3be8d63 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -393,7 +393,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/* global temp table is same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 632c058..9511182 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -304,7 +306,8 @@ heap_create(const char *relname,
 			bool mapped_relation,
 			bool allow_system_table_mods,
 			TransactionId *relfrozenxid,
-			MultiXactId *relminmxid)
+			MultiXactId *relminmxid,
+			bool skip_create_storage)
 {
 	bool		create_storage;
 	Relation	rel;
@@ -404,6 +407,9 @@ heap_create(const char *relname,
 									 relpersistence,
 									 relkind);
 
+	if (skip_create_storage)
+		create_storage = false;
+
 	/*
 	 * Have the storage manager create the relation's disk file, if needed.
 	 *
@@ -427,7 +433,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -959,6 +965,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -997,8 +1004,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;
@@ -1261,7 +1278,8 @@ heap_create_with_catalog(const char *relname,
 							   mapped_relation,
 							   allow_system_table_mods,
 							   &relfrozenxid,
-							   &relminmxid);
+							   &relminmxid,
+							   false);
 
 	Assert(relid == RelationGetRelid(new_rel_desc));
 
@@ -1360,6 +1378,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1940,6 +1959,14 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/* We allow to drop global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			elog(ERROR, "can not drop relation %s when other backend attached this global temp table",
+						RelationGetRelationName(rel));
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3167,7 +3194,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3179,7 +3206,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3225,8 +3252,13 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
+
+		/* truncate global temp table only need RowExclusiveLock */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3259,6 +3291,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3267,23 +3300,37 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (!gtt_storage_attached(RelationGetRelid(rel)))
+			return;
+
+		/*
+		 * Truncate global temp table only need RowExclusiveLock
+		 */
+		lockmode = RowExclusiveLock;
+	}
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(rel, 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 7cfbdd5..c70c48f 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -53,6 +53,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -727,6 +728,11 @@ index_create(Relation heapRelation,
 	char		relkind;
 	TransactionId relfrozenxid;
 	MultiXactId relminmxid;
+	bool		skip_create_storage = false;
+
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation) &&
+		!gtt_storage_attached(RelationGetRelid(heapRelation)))
+		skip_create_storage = true;
 
 	/* constraint flags can only be set when a constraint is requested */
 	Assert((constr_flags == 0) ||
@@ -880,6 +886,19 @@ index_create(Relation heapRelation,
 						indexRelationName, RelationGetRelationName(heapRelation))));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temp table use concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot reindex global temporary tables concurrently")));
+
+		/* if global temp table not init storage, then skip build index */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -934,7 +953,8 @@ index_create(Relation heapRelation,
 								mapped_relation,
 								allow_system_table_mods,
 								&relfrozenxid,
-								&relminmxid);
+								&relminmxid,
+								skip_create_storage);
 
 	Assert(relfrozenxid == InvalidTransactionId);
 	Assert(relminmxid == InvalidMultiXactId);
@@ -2040,7 +2060,8 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 * lock (see comments in RemoveRelations), and a non-concurrent DROP is
 	 * more efficient.
 	 */
-	Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+	Assert(!(get_rel_persistence(indexId) == RELPERSISTENCE_TEMP ||
+			 get_rel_persistence(indexId) == RELPERSISTENCE_GLOBAL_TEMP) ||
 		   (!concurrent && !concurrent_lock_mode));
 
 	/*
@@ -2072,6 +2093,14 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/* We allow to drop index on global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+			elog(ERROR, "can not drop index %s when other backend attached this global temp table.",
+						RelationGetRelationName(userHeapRelation));
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2680,6 +2709,11 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	/* update index stats into localhash and rel_rd_rel for global temp table */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		is_gtt = true;
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2765,21 +2799,35 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
+		if (is_gtt)
+			rel->rd_rel->relpages = (int32) relpages;
+		else if (rd_rel->relpages != (int32) relpages)
 		{
 			rd_rel->relpages = (int32) relpages;
 			dirty = true;
 		}
-		if (rd_rel->reltuples != (float4) reltuples)
+
+		if (is_gtt)
+			rel->rd_rel->reltuples = (float4) reltuples;
+		else if (rd_rel->reltuples != (float4) reltuples)
 		{
 			rd_rel->reltuples = (float4) reltuples;
 			dirty = true;
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+
+		if (is_gtt)
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+		else if (rd_rel->relallvisible != (int32) relallvisible)
 		{
 			rd_rel->relallvisible = (int32) relallvisible;
 			dirty = true;
 		}
+
+		if (is_gtt)
+		{
+			up_gtt_relstats(rel, relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
+		}
 	}
 
 	/*
@@ -2893,6 +2941,15 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, index, val);
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			gtt_force_enable_index(indexRelation);
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3448,6 +3505,16 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	 * we only need to be sure no schema or data changes are going on.
 	 */
 	heapId = IndexGetRelation(indexId, false);
+
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		!gtt_storage_attached(indexId))
+	{
+		/* Correction status flag, and return */
+		SetReindexProcessing(heapId, indexId);
+		ResetReindexProcessing();
+		return;
+	}
+
 	heapRelation = table_open(heapId, ShareLock);
 
 	if (progress)
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 2ec2301..1b6061d 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -655,6 +655,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		/* global temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp relations in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index d713d5c..afcf004 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,6 +27,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -61,6 +62,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			relOid;			/* InvalidOid if not a global temp rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -115,7 +117,7 @@ AddPendingSync(const RelFileNode *rnode)
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -127,6 +129,8 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		/* global temp table use same storage strategy as local temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -154,6 +158,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->relOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
@@ -165,6 +170,13 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		AddPendingSync(&rnode);
 	}
 
+	/* remember global temp table storage info to localhash */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel)
+	{
+		pending->relOid = RelationGetRelid(rel);
+		remember_gtt_storage_info(rnode, rel);
+	}
+
 	return srel;
 }
 
@@ -201,11 +213,15 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->relOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->relOid = RelationGetRelid(rel);
+
 	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
@@ -602,6 +618,7 @@ smgrDoPendingDeletes(bool isCommit)
 				i = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -631,14 +648,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->relOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -651,9 +672,18 @@ smgrDoPendingDeletes(bool isCommit)
 		smgrdounlinkall(srels, nrels, false);
 
 		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* clean global temp table flags when transaction commit or rollback */
+			if (SmgrIsTemp(srels[i]) &&
+				reloids[i] != InvalidOid &&
+				gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..451cf82
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1491 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  code to create and destroy physical storage for global temparary table
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_relstats_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+int		vacuum_gtt_defer_check_age = 0;
+
+typedef struct gtt_ctl_data
+{
+	LWLock			lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+	Oid			relfilenode;
+	Oid			spcnode;
+
+	/* pg_class stat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+
+	/* pg_statistic */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+
+	Oid			oldrelid;			/* remember the source of relid, before the switch relfilenode. */
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_reset_statistics(gtt_local_hash_entry *entry);
+static void gtt_free_statistics(gtt_local_hash_entry *entry);
+static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok);
+static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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();
+	}
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(gtt_fnode);
+	info.entrysize = action_gtt_shared_hash_entry_size();
+	active_gtt_shared_hash =
+		ShmemInitHash("active gtt shared hash",
+						gtt_shared_ctl->max_entry,
+						gtt_shared_ctl->max_entry,
+						&info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			found;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *)&(fnode), 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_global_temporary_table.")));
+	}
+
+	if (found == false)
+	{
+		int			wordnum;
+
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode				fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	if (!skiplock)
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		if (!skiplock)
+			LWLockRelease(&gtt_shared_ctl->lock);
+
+		if (isCommit)
+			elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+	bms_del_member(entry->map, MyBackendId);
+
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+
+	if (!skiplock)
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+	return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	Bitmapset	*map_copy = NULL;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool		in_use = false;
+	int			num_use = 0;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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;
+	gtt_relfilenode			*new_node = NULL;
+	Oid						relid = RelationGetRelid(rel);
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		/* First time through: initialize the hash table */
+		HASHCTL		ctl;
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_relstats_context =
+			AllocSetContextCreate(CacheMemoryContext,
+							"gtt relstats context",
+							ALLOCSET_DEFAULT_SIZES);
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+	entry = gtt_search_by_relid(relid, true);
+	if (!entry)
+	{
+		bool		found = false;
+		int 		natts = 0;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash,
+						(void *) &relid, HASH_ENTER, &found);
+
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+		entry->natts = 0;
+		entry->attnum = NULL;
+		entry->att_stat_tups = NULL;
+		entry->oldrelid = InvalidOid;
+
+		natts = RelationGetNumberOfAttributes(rel);
+		entry->attnum = palloc0(sizeof(int) * natts);
+		entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+		entry->natts = natts;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			gtt_storage_checkin(relid);
+		}
+	}
+
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* only heap contain transaction information */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	gtt_reset_statistics(entry);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	gtt_relfilenode *d_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+		return;
+	}
+
+	d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+		else if (entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+			gtt_relfilenode *gttnode2 = NULL;
+
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+			Assert(gttnode2->relfilenode == rnode.relNode);
+			Assert(list_length(entry->relfilenode_list) == 1);
+			/* rollback switch relfilenode */
+			gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+									   entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+									   false);
+			/* clean up footprint */
+			entry2->oldrelid = InvalidOid;
+			d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+			Assert(d_rnode);
+		}
+		else
+		{
+			if (entry->relfilenode_list == NIL)
+			{
+				if (entry->relkind == RELKIND_RELATION ||
+					entry->relkind == RELKIND_SEQUENCE)
+					gtt_storage_checkout(relid, false, isCommit);
+
+				gtt_free_statistics(entry);
+				hash_search(gtt_storage_local_hash,
+						(void *) &(relid), HASH_REMOVE, NULL);
+			}
+
+			return;
+		}
+	}
+
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	pfree(d_rnode);
+	if (entry->relfilenode_list == NIL)
+	{
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(relid, false, isCommit);
+
+		if (isCommit && entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			/* clean up footprint */
+			entry2->oldrelid = InvalidOid;
+		}
+
+		gtt_free_statistics(entry);
+		hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_REMOVE, NULL);
+	}
+	else
+		gtt_reset_statistics(entry);
+
+	return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool found = false;
+	gtt_local_hash_entry *entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry)
+		found = true;
+
+	return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS				status;
+	gtt_local_hash_entry		*entry;
+	SMgrRelation	*srels = NULL;
+	Oid				*relids = NULL;
+	char			*relkinds = NULL;
+	int			nrels = 0,
+				nfiles = 0,
+				maxrels = 0,
+				maxfiles = 0,
+				i = 0;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel;
+			RelFileNode 	rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel = smgropen(rnode, MyBackendId);
+
+			if (maxfiles == 0)
+			{
+				maxfiles = 32;
+				srels = palloc(sizeof(SMgrRelation) * maxfiles);
+			}
+			else if (maxfiles <= nfiles)
+			{
+				maxfiles *= 2;
+				srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+			}
+
+			srels[nfiles++] = srel;
+		}
+
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			relids  = palloc(sizeof(Oid) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			relids  = repalloc(relids , sizeof(Oid) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		relkinds[nrels] = entry->relkind;
+		relids[nrels] = entry->relid;
+		nrels++;
+	}
+
+	if (nfiles > 0)
+	{
+		smgrdounlinkall(srels, nfiles, false);
+		for (i = 0; i < nfiles; i++)
+			smgrclose(srels[i]);
+
+		pfree(srels);
+	}
+
+	if (nrels)
+	{
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+		for (i = 0; i < nrels; i++)
+		{
+			if (relkinds[i] == RELKIND_RELATION ||
+				relkinds[i] == RELKIND_SEQUENCE)
+				gtt_storage_checkout(relids[i], true, false);
+		}
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+		pfree(relids);
+		pfree(relkinds);
+	}
+
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+	return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible) 
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+					BlockNumber num_pages,
+					double num_tuples,
+					BlockNumber num_all_visible_pages,
+					TransactionId relfrozenxid,
+					TransactionId relminmxid)
+{
+	Oid		relid = RelationGetRelid(relation);
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (num_pages >= 0 &&
+		gtt_rnode->relpages != (int32)num_pages)
+		gtt_rnode->relpages = (int32)num_pages;
+
+	if (num_tuples >= 0 &&
+		gtt_rnode->reltuples != (float4)num_tuples)
+		gtt_rnode->reltuples = (float4)num_tuples;
+
+	/* only heap contain transaction information and relallvisible */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (gtt_rnode->relallvisible >= 0 &&
+			gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+		{
+			gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+		}
+
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNewTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible) 
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	MemoryContext			oldcontext;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return;
+
+	if (entry->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	Assert(entry->relid == reloid);
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == 0)
+		{
+			entry->attnum[i] = attnum;
+			break;
+		}
+		else if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < entry->natts);
+	Assert(entry->att_stat_tups[i] == NULL);
+	entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return NULL;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			return entry->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int			i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	HeapTuple	tuple;
+	Relation	rel = NULL;
+	int		attnum = PG_GETARG_INT32(1);
+	Oid		reloid = PG_GETARG_OID(0);
+	char		rel_persistence;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	  tupdesc;
+	MemoryContext oldcontext;
+	Tuplestorestate *tupstore;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+
+	relation_close(rel, NoLock);
+	relation_close(pg_tatistic, AccessShareLock);
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate	*tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Oid			relnode = 0;
+	Relation	rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[6];
+		bool	isnull[6];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(reloid);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(RelationGetRelid(index)));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid relOid = index->rd_index->indrelid;
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	if (gtt_storage_attached(relOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int			i;
+	Oid			toastrelid;
+
+	if (!(operation == CMD_UTILITY || operation == CMD_INSERT))
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+
+		index_build(relation, index, info, true, false);
+	}
+
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid 		indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo 	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+static void
+gtt_reset_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+
+		entry->attnum[i] = 0;
+	}
+
+	return;
+}
+
+static void
+gtt_free_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (entry->attnum)
+		pfree(entry->attnum);
+
+	if (entry->att_stat_tups)
+		pfree(entry->att_stat_tups);
+
+	return;
+}
+
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
+void
+gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint)
+{
+	gtt_local_hash_entry	*entry1;
+	gtt_local_hash_entry	*entry2;
+	gtt_relfilenode			*gtt_rnode1 = NULL;
+	gtt_relfilenode			*gtt_rnode2 = NULL;
+	MemoryContext			oldcontext;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry1 = gtt_search_by_relid(rel1, false);
+	gtt_rnode1 = gtt_search_relfilenode(entry1, relfilenode1, false);
+
+	entry2 = gtt_search_by_relid(rel2, false);
+	gtt_rnode2 = gtt_search_relfilenode(entry2, relfilenode2, false);
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	entry1->relfilenode_list = list_delete_ptr(entry1->relfilenode_list, gtt_rnode1);
+	entry2->relfilenode_list = lappend(entry2->relfilenode_list, gtt_rnode1);
+
+	entry2->relfilenode_list = list_delete_ptr(entry2->relfilenode_list, gtt_rnode2);
+	entry1->relfilenode_list = lappend(entry1->relfilenode_list, gtt_rnode2);
+	MemoryContextSwitchTo(oldcontext);
+
+	if (footprint)
+	{
+		entry1->oldrelid = rel2;
+		entry2->oldrelid = rel1;
+	}
+
+	return;
+}
+
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok)
+{
+	gtt_relfilenode			*rnode = NULL;
+	ListCell				*lc;
+
+	Assert(entry);
+
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == relfilenode)
+		{
+			rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (!missing_ok && rnode == NULL)
+		elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+	return rnode;
+}
+
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry == NULL && !missing_ok)
+		elog(ERROR, "relid %u not found in local hash", relid);
+
+	return entry;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index d406ea8..88be041 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM 
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 924ef37..db12eef 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -184,6 +185,13 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -586,14 +594,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 		 * pg_statistic for columns we didn't process, we leave them alone.)
 		 */
 		update_attstats(RelationGetRelid(onerel), inh,
-						attr_cnt, vacattrstats);
+						attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
 
 		for (ind = 0; ind < nindexes; ind++)
 		{
 			AnlIndexData *thisdata = &indexdata[ind];
 
 			update_attstats(RelationGetRelid(Irel[ind]), false,
-							thisdata->attr_cnt, thisdata->vacattrstats);
+							thisdata->attr_cnt, thisdata->vacattrstats,
+							RelationGetRelPersistence(Irel[ind]));
 		}
 
 		/*
@@ -1456,7 +1465,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
  *		by taking a self-exclusive lock on the relation in analyze_rel().
  */
 static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
 {
 	Relation	sd;
 	int			attno;
@@ -1558,31 +1567,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/* Update column statistic to localhash, not catalog */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 04d12a7..42c82c8 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/toasting.h"
 #include "commands/cluster.h"
 #include "commands/progress.h"
@@ -72,6 +73,12 @@ static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
 							bool verbose, bool *pSwapToastByContent,
 							TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
 static List *get_tables_to_cluster(MemoryContext cluster_context);
+static void gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables);
 
 
 /*---------------------------------------------------------------------------
@@ -366,6 +373,14 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap) &&
+		!gtt_storage_attached(RelationGetRelid(OldHeap)))
+	{
+		relation_close(OldHeap, AccessExclusiveLock);
+		pgstat_progress_end_command();
+		return;
+	}
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
@@ -750,6 +765,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	BlockNumber num_pages;
 	int			elevel = verbose ? INFO : DEBUG2;
 	PGRUsage	ru0;
+	bool		is_gtt = false;
+	uint32		gtt_relfrozenxid = 0;
+	uint32		gtt_relminmxid = 0;
 
 	pg_rusage_init(&ru0);
 
@@ -763,6 +781,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	else
 		OldIndex = NULL;
 
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		is_gtt = true;
+
 	/*
 	 * Their tuple descriptors should be exactly alike, but here we only need
 	 * assume that they have the same number of columns.
@@ -830,20 +851,37 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 						  &OldestXmin, &FreezeXid, NULL, &MultiXactCutoff,
 						  NULL);
 
-	/*
-	 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
-	 * backwards, so take the max.
-	 */
-	if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
-		TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
-		FreezeXid = OldHeap->rd_rel->relfrozenxid;
+	if (is_gtt)
+	{
+		get_gtt_relstats(OIDOldHeap,
+					NULL, NULL, NULL,
+					&gtt_relfrozenxid, &gtt_relminmxid);
 
-	/*
-	 * MultiXactCutoff, similarly, shouldn't go backwards either.
-	 */
-	if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
-		MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
-		MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+		if (TransactionIdIsValid(gtt_relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, gtt_relfrozenxid))
+			FreezeXid = gtt_relfrozenxid;
+
+		if (MultiXactIdIsValid(gtt_relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, gtt_relminmxid))
+			MultiXactCutoff = gtt_relminmxid;
+	}
+	else
+	{
+		/*
+		 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
+		 * backwards, so take the max.
+		 */
+		if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
+			FreezeXid = OldHeap->rd_rel->relfrozenxid;
+
+		/*
+		 * MultiXactCutoff, similarly, shouldn't go backwards either.
+		 */
+		if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
+			MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+	}
 
 	/*
 	 * Decide whether to use an indexscan or seqscan-and-optional-sort to scan
@@ -911,6 +949,12 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	table_close(OldHeap, NoLock);
 	table_close(NewHeap, NoLock);
 
+	if (is_gtt)
+	{
+		CommandCounterIncrement();
+		return;
+	}
+
 	/* Update pg_class to reflect the correct values of pages and tuples. */
 	relRelation = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1346,10 +1390,21 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 	 * Swap the contents of the heap relations (including any toast tables).
 	 * Also set old heap's relfrozenxid to frozenXid.
 	 */
-	swap_relation_files(OIDOldHeap, OIDNewHeap,
+	if (newrelpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		Assert(!is_system_catalog);
+		gtt_swap_relation_files(OIDOldHeap, OIDNewHeap,
+								(OIDOldHeap == RelationRelationId),
+								swap_toast_by_content, is_internal,
+								frozenXid, cutoffMulti, mapped_tables);
+	}
+	else
+	{
+		swap_relation_files(OIDOldHeap, OIDNewHeap,
 						(OIDOldHeap == RelationRelationId),
 						swap_toast_by_content, is_internal,
 						frozenXid, cutoffMulti, mapped_tables);
+	}
 
 	/*
 	 * If it's a system catalog, queue a sinval message to flush all catcaches
@@ -1557,3 +1612,141 @@ get_tables_to_cluster(MemoryContext cluster_context)
 
 	return rvs;
 }
+
+static void
+gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables)
+{
+	Relation	relRelation;
+	Oid			relfilenode1,
+				relfilenode2;
+	Relation	rel1;
+	Relation	rel2;
+
+	relRelation = table_open(RelationRelationId, RowExclusiveLock);
+
+	rel1 = relation_open(r1, AccessExclusiveLock);
+	rel2 = relation_open(r2, AccessExclusiveLock);
+
+	relfilenode1 = gtt_fetch_current_relfilenode(r1);
+	relfilenode2 = gtt_fetch_current_relfilenode(r2);
+
+	Assert(OidIsValid(relfilenode1) && OidIsValid(relfilenode2));
+	gtt_switch_rel_relfilenode(r1, relfilenode1, r2, relfilenode2, true);
+
+	CacheInvalidateRelcache(rel1);
+	CacheInvalidateRelcache(rel2);
+
+	InvokeObjectPostAlterHookArg(RelationRelationId, r1, 0,
+								 InvalidOid, is_internal);
+	InvokeObjectPostAlterHookArg(RelationRelationId, r2, 0,
+								 InvalidOid, true);
+
+	if (rel1->rd_rel->reltoastrelid || rel2->rd_rel->reltoastrelid)
+	{
+		if (swap_toast_by_content)
+		{
+			if (rel1->rd_rel->reltoastrelid && rel2->rd_rel->reltoastrelid)
+			{
+				gtt_swap_relation_files(rel1->rd_rel->reltoastrelid,
+									rel2->rd_rel->reltoastrelid,
+									target_is_pg_class,
+									swap_toast_by_content,
+									is_internal,
+									frozenXid,
+									cutoffMulti,
+									mapped_tables);
+			}
+			else
+				elog(ERROR, "cannot swap toast files by content when there's only one");
+		}
+		else
+		{
+			ObjectAddress baseobject,
+						toastobject;
+			long		count;
+
+			if (IsSystemRelation(rel1))
+				elog(ERROR, "cannot swap toast files by links for system catalogs");
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel1->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel2->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+
+			/* Register new dependencies */
+			baseobject.classId = RelationRelationId;
+			baseobject.objectSubId = 0;
+			toastobject.classId = RelationRelationId;
+			toastobject.objectSubId = 0;
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r1;
+				toastobject.objectId = rel1->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r2;
+				toastobject.objectId = rel2->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+		}
+	}
+
+	if (swap_toast_by_content &&
+		rel1->rd_rel->relkind == RELKIND_TOASTVALUE &&
+		rel2->rd_rel->relkind == RELKIND_TOASTVALUE)
+	{
+		Oid			toastIndex1,
+					toastIndex2;
+
+		/* Get valid index for each relation */
+		toastIndex1 = toast_get_valid_index(r1,
+											AccessExclusiveLock);
+		toastIndex2 = toast_get_valid_index(r2,
+											AccessExclusiveLock);
+
+		gtt_swap_relation_files(toastIndex1,
+							toastIndex2,
+							target_is_pg_class,
+							swap_toast_by_content,
+							is_internal,
+							InvalidTransactionId,
+							InvalidMultiXactId,
+							mapped_tables);
+	}
+
+	relation_close(rel1, NoLock);
+	relation_close(rel2, NoLock);
+
+	table_close(relRelation, RowExclusiveLock);
+
+	RelationCloseSmgrByOid(r1);
+	RelationCloseSmgrByOid(r2);
+
+	CommandCounterIncrement();
+}
+
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index ac07f75..655184d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -27,6 +27,7 @@
 #include "catalog/dependency.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -1063,7 +1064,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, stmt->filename, stmt->is_program,
@@ -2794,6 +2795,8 @@ CopyFrom(CopyState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	estate->es_result_relations = resultRelInfo;
 	estate->es_num_result_relations = 1;
 	estate->es_result_relation_info = resultRelInfo;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 2baca12..5338db9 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -521,6 +521,7 @@ DefineIndex(Oid relationId,
 	Snapshot	snapshot;
 	int			save_nestlevel = -1;
 	int			i;
+	char		rel_persistence;
 
 	/*
 	 * Some callers need us to run with an empty default_tablespace; this is a
@@ -542,7 +543,9 @@ DefineIndex(Oid relationId,
 	 * is more efficient.  Do this before any use of the concurrent option is
 	 * done.
 	 */
-	if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+	rel_persistence = get_rel_persistence(relationId);
+	if (stmt->concurrent &&
+		!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		concurrent = true;
 	else
 		concurrent = false;
@@ -2460,7 +2463,8 @@ ReindexIndex(RangeVar *indexRelation, int options, bool concurrent)
 	persistence = irel->rd_rel->relpersistence;
 	index_close(irel, NoLock);
 
-	if (concurrent && persistence != RELPERSISTENCE_TEMP)
+	if (concurrent &&
+		!(persistence == RELPERSISTENCE_TEMP || persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		ReindexRelationConcurrently(indOid, options);
 	else
 		reindex_index(indOid, false, persistence,
@@ -2546,6 +2550,7 @@ ReindexTable(RangeVar *relation, int options, bool concurrent)
 {
 	Oid			heapOid;
 	bool		result;
+	char		rel_persistence;
 
 	/*
 	 * The lock level used here should match reindex_relation().
@@ -2560,7 +2565,9 @@ ReindexTable(RangeVar *relation, int options, bool concurrent)
 									   0,
 									   RangeVarCallbackOwnsTable, NULL);
 
-	if (concurrent && get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+	rel_persistence = get_rel_persistence(heapOid);
+	if (concurrent &&
+		!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 	{
 		result = ReindexRelationConcurrently(heapOid, options);
 
@@ -2761,12 +2768,15 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 	foreach(l, relids)
 	{
 		Oid			relid = lfirst_oid(l);
+		char		rel_persistence;
 
 		StartTransactionCommand();
 		/* functions in indexes may want a snapshot set */
 		PushActiveSnapshot(GetTransactionSnapshot());
 
-		if (concurrent && get_rel_persistence(relid) != RELPERSISTENCE_TEMP)
+		rel_persistence = get_rel_persistence(relid);
+		if (concurrent &&
+			!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		{
 			(void) ReindexRelationConcurrently(relid, options);
 			/* ReindexRelationConcurrently() does the verbose output */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..a45863a 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -94,7 +96,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -222,7 +225,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* now initialize the sequence's data */
 	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -327,7 +330,7 @@ ResetSequence(Oid seq_relid)
 	/*
 	 * Insert the modified tuple into the new storage file.
 	 */
-	fill_seq_with_data(seq_rel, tuple);
+	fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -340,17 +343,21 @@ ResetSequence(Oid seq_relid)
  * Initialize a sequence's relation with the specified tuple as content
  */
 static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 	sequence_magic *sm;
 	OffsetNumber offnum;
+	bool lockBuffer = false;
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ReadBuffer(rel, P_NEW);
-	Assert(BufferGetBlockNumber(buf) == 0);
+	if (buf == InvalidBuffer)
+	{
+		buf = ReadBuffer(rel, P_NEW);
+		Assert(BufferGetBlockNumber(buf) == 0);
+		lockBuffer = true;
+	}
 
 	page = BufferGetPage(buf);
 
@@ -360,7 +367,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* Now insert sequence tuple */
 
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+	if (lockBuffer)
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +418,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	END_CRIT_SECTION();
 
-	UnlockReleaseBuffer(buf);
+	if (lockBuffer)
+		UnlockReleaseBuffer(buf);
 }
 
 /*
@@ -451,6 +460,13 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 
 	init_sequence(relid, &elm, &seqrel);
 
+	if (RELATION_IS_GLOBAL_TEMP(seqrel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(seqrel)))
+			elog(ERROR, "cannot alter global temp sequence %s when other backend attached it",
+						RelationGetRelationName(seqrel));
+	}
+
 	rel = table_open(SequenceRelationId, RowExclusiveLock);
 	seqtuple = SearchSysCacheCopy1(SEQRELID,
 								   ObjectIdGetDatum(relid));
@@ -502,7 +518,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * Insert the modified tuple into the new storage file.
 		 */
-		fill_seq_with_data(seqrel, newdatatuple);
+		fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
 	}
 
 	/* process OWNED BY if given */
@@ -611,7 +627,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +952,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1154,6 +1170,13 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1954,3 +1977,46 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+void
+gtt_init_seq(Relation rel)
+{
+	/* Initialize sequence for global temporary tables */
+	Datum		value[SEQ_COL_LASTCOL] = {0};
+	bool		null[SEQ_COL_LASTCOL] = {false};
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5745cd6..0e9c9cd 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -556,6 +557,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 								  Relation partitionTbl);
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static OnCommitAction gtt_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -601,6 +603,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -611,8 +614,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")));
@@ -642,7 +647,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	/* global temp table same as local temp table */
+	if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+		 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -743,6 +750,55 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* global temp table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* check parent table*/
+		if (inheritOids)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			table_close(relation, NoLock);
+		}
+
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temp table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1330,6 +1386,7 @@ RemoveRelations(DropStmt *drop)
 		Oid			relOid;
 		ObjectAddress obj;
 		struct DropRelationCallbackState state;
+		char		rel_persistence;
 
 		/*
 		 * These next few steps are a great deal like relation_openrv, but we
@@ -1363,8 +1420,9 @@ RemoveRelations(DropStmt *drop)
 		 * Decide if concurrent mode needs to be used here or not.  The
 		 * relation persistence cannot be known without its OID.
 		 */
+		rel_persistence = get_rel_persistence(relOid);
 		if (drop->concurrent &&
-			get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+			!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		{
 			Assert(list_length(drop->objects) == 1 &&
 				   drop->removeType == OBJECT_INDEX);
@@ -1819,6 +1877,10 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
 		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 			continue;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			continue;
+
 		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
@@ -3570,6 +3632,14 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			elog(ERROR, "can not alter table %s when other backend attached this global temp table",
+						RelationGetRelationName(rel));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -4847,6 +4917,38 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot rewrite temporary tables of other sessions")));
 
+			if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+			{
+				if (tab->chgPersistence)
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot change global temporary table persistence setting")));
+
+				/* gtt may not attached, create it */
+				if(!gtt_storage_attached(tab->relid))
+				{
+					ResultRelInfo *resultRelInfo;
+					MemoryContext oldcontext;
+					MemoryContext ctx_alter_gtt;
+
+					ctx_alter_gtt = AllocSetContextCreate(CurrentMemoryContext,
+											"gtt alter table", ALLOCSET_DEFAULT_SIZES);
+
+					oldcontext = MemoryContextSwitchTo(ctx_alter_gtt);
+					resultRelInfo = makeNode(ResultRelInfo);
+					InitResultRelInfo(resultRelInfo, OldHeap,
+									1, NULL, 0);
+					if (resultRelInfo->ri_RelationDesc->rd_rel->relhasindex &&
+						resultRelInfo->ri_IndexRelationDescs == NULL)
+						ExecOpenIndices(resultRelInfo, false);
+
+					init_gtt_storage(CMD_UTILITY, resultRelInfo);
+					ExecCloseIndices(resultRelInfo);
+					MemoryContextSwitchTo(oldcontext);
+					MemoryContextDelete(ctx_alter_gtt);
+				}
+			}
+
 			/*
 			 * Select destination tablespace (same as original unless user
 			 * requested a change)
@@ -8184,6 +8286,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -12740,6 +12848,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -12942,6 +13053,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temp table");
+
 	/*
 	 * No work if no change in tablespace.
 	 */
@@ -13316,7 +13430,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,
@@ -14723,7 +14837,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",
@@ -17379,3 +17495,36 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
 		table_close(rel, NoLock);
 	}
 }
+
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			bool	res = false;
+			char	*sval = defGetString(def);
+
+			if (!parse_bool(sval, &res))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
+
+			if (res)
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 5a110ed..02e6f1e 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"
@@ -1215,6 +1216,17 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	 /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		is_gtt = true;
+		up_gtt_relstats(relation,
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1228,17 +1240,26 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relpages = (int32) num_pages;
+	else if (pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (is_gtt)
+		relation->rd_rel->reltuples = (float4) num_tuples;
+	else if (pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	else if (pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1283,7 +1304,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1294,7 +1316,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1392,6 +1415,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
@@ -1449,6 +1476,42 @@ 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	safe_age;
+		TransactionId	oldest_gtt_frozenxid =
+			list_all_session_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid))
+		{
+			safe_age = oldest_gtt_frozenxid + vacuum_gtt_defer_check_age;
+			if (safe_age < FirstNormalTransactionId)
+				safe_age += FirstNormalTransactionId;
+
+			/*
+			 * We tolerate that the minimum age of gtt is less than
+			 * the minimum age of conventional tables, otherwise it will
+			 * throw warning message.
+			 */
+			if (TransactionIdIsNormal(safe_age) &&
+				TransactionIdPrecedes(safe_age, newFrozenXid))
+			{
+				ereport(WARNING,
+					(errmsg("global temp table oldest relfrozenxid %u is the oldest in the entire db",
+							oldest_gtt_frozenxid),
+					 errdetail("The oldest relfrozenxid in pg_class is %u", newFrozenXid),
+					 errhint("If they differ greatly, please consider cleaning up the data in global temp table.")));
+			}
+
+			if (TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+				newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1776,6 +1839,15 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6e65103..04706ee 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -530,6 +530,11 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 4fdffad..0aa7559 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index fb6ce49..06d9237 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
@@ -545,6 +546,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	init_gtt_storage(mtstate->operation, leaf_part_rri);
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 20a4c47..e9f6dcd 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,6 +42,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -2379,6 +2380,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			ExecOpenIndices(resultRelInfo,
 							node->onConflictAction != ONCONFLICT_NONE);
 
+		init_gtt_storage(operation, resultRelInfo);
+
 		/*
 		 * If this is an UPDATE and a BEFORE UPDATE trigger is present, the
 		 * trigger itself might modify the partition-key values. So arrange
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 255f56b..d94d6f0 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 e664eb1..e03bfa0 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6419,7 +6419,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
+	/* global temp table is same as local temp table */
 	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 2554502..04fb5d4 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -28,6 +28,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temp table */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index eee9c33..1891c53 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2594,6 +2594,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 3c78f2d..edb8d9f 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3302,17 +3302,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; }
@@ -11648,19 +11642,13 @@ OptTempTableName:
 				}
 			| GLOBAL TEMPORARY opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED opt_table qualified_name
 				{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b875a50..123945b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 75c122f..97213ed 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
@@ -3108,6 +3111,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		cxt.isforeign = false;
 	}
 	cxt.relation = stmt->relation;
+	cxt.relation->relpersistence = RelationGetRelPersistence(rel);
 	cxt.rel = rel;
 	cxt.inhRelations = NIL;
 	cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 7e97ffa..1f2ac58 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2089,6 +2089,11 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* autovacuum skip vacuum global temp table */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2155,7 +2160,9 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		/* autovacuum skip vacuum global temp table */
+		if (classForm->relpersistence == RELPERSISTENCE_TEMP || 
+			classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index f9980cf..406a25c 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -53,6 +54,7 @@
 #include "utils/rel.h"
 #include "utils/resowner_private.h"
 #include "utils/timestamp.h"
+#include "utils/guc.h"
 
 
 /* Note: these two macros only work on shared buffers, not local ones! */
@@ -2819,6 +2821,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d5..7adb969 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -21,6 +21,7 @@
 #include "access/nbtree.h"
 #include "access/subtrans.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, active_gtt_shared_hash_size());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* global temporary table */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 3630006..0c8ef80 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -63,6 +63,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -4086,3 +4087,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 5aa19d3..b3bc455 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 8406644..0d99321 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -1008,6 +1008,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index cfb0568..5913efc 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -112,6 +112,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -4821,12 +4822,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))
 							{
@@ -4951,15 +4965,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))
 		{
@@ -6341,6 +6367,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;
@@ -6358,6 +6385,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,
@@ -6369,6 +6403,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;
@@ -6384,6 +6420,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,
@@ -7302,6 +7345,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))
@@ -7314,6 +7359,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 =
@@ -7326,6 +7379,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.
@@ -7345,6 +7400,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index a7d63f1..d8a52cb 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -2939,6 +2940,18 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 9f1f11d..aeb2a78 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -65,6 +65,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1133,6 +1134,24 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1187,6 +1206,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			gtt_fix_index_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -1317,7 +1337,17 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+			if (newrelnode != InvalidOid &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2246,6 +2276,8 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		gtt_fix_index_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3472,6 +3504,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3579,28 +3615,34 @@ void
 RelationSetNewRelfilenode(Relation relation, char persistence)
 {
 	Oid			newrelfilenode;
-	Relation	pg_class;
-	HeapTuple	tuple;
+	Relation	pg_class = NULL;
+	HeapTuple	tuple = NULL;
 	Form_pg_class classform;
 	MultiXactId minmulti = InvalidMultiXactId;
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
+	bool		modify_pg_class = !RELATION_IS_GLOBAL_TEMP(relation);
 
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
 
-	/*
-	 * Get a writable copy of the pg_class tuple for the given relation.
-	 */
-	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+	if (modify_pg_class)
+	{
+		/*
+		 * Get a writable copy of the pg_class tuple for the given relation.
+		 */
+		pg_class = table_open(RelationRelationId, RowExclusiveLock);
 
-	tuple = SearchSysCacheCopy1(RELOID,
-								ObjectIdGetDatum(RelationGetRelid(relation)));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "could not find tuple for relation %u",
-			 RelationGetRelid(relation));
-	classform = (Form_pg_class) GETSTRUCT(tuple);
+		tuple = SearchSysCacheCopy1(RELOID,
+									ObjectIdGetDatum(RelationGetRelid(relation)));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u",
+				 RelationGetRelid(relation));
+		classform = (Form_pg_class) GETSTRUCT(tuple);
+	}
+	else
+		memset(&classform, 0, sizeof(classform));
 
 	/*
 	 * Schedule unlinking of the old storage at transaction commit.
@@ -3626,7 +3668,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;
@@ -3646,6 +3688,15 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 			break;
 	}
 
+	if (!modify_pg_class)
+	{
+		Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+		Assert(RELATION_IS_GLOBAL_TEMP(relation));
+		Assert(!RelationIsMapped(relation));
+		relation->rd_node.relNode = relnode;
+		CacheInvalidateRelcache(relation);
+	}
 	/*
 	 * If we're dealing with a mapped index, pg_class.relfilenode doesn't
 	 * change; instead we have to send the update to the relation mapper.
@@ -3655,7 +3706,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	 * possibly-inaccurate values of relpages etc, but those will be fixed up
 	 * later.
 	 */
-	if (RelationIsMapped(relation))
+	else if (RelationIsMapped(relation))
 	{
 		/* This case is only supported for indexes */
 		Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3701,9 +3752,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 		CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 	}
 
-	heap_freetuple(tuple);
+	if (modify_pg_class)
+	{
+		heap_freetuple(tuple);
 
-	table_close(pg_class, RowExclusiveLock);
+		table_close(pg_class, RowExclusiveLock);
+	}
 
 	/*
 	 * Make the pg_class row change or relation map change visible.  This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 5bdc02f..b52b51d 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -37,6 +37,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "commands/prepare.h"
 #include "commands/trigger.h"
@@ -142,6 +143,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temp table feature.
+ * global temp table define can still storage in catalog
+ * just can not use.
+ * num > 0 means database can management num active global temp table.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2070,6 +2083,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."),
@@ -2577,6 +2599,16 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"vacuum_gtt_defer_check_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("The defer check age of GTT, used to check expired data after vacuum."),
+			NULL
+		},
+		&vacuum_gtt_defer_check_age,
+		10000, 0, 1000000,
+		NULL, NULL, NULL
+	},
+
 	/*
 	 * See also CheckRequiredParameterValues() if this parameter changes
 	 */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 5db4f57..018a9bf 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2424,6 +2424,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
 		dopt->no_unlogged_table_data)
 		return;
 
+	/* Don't dump data in global temp table/sequence */
+	if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		return;
+
 	/* Check that the data is not explicitly excluded */
 	if (simple_oid_list_member(&tabledata_exclude_oids,
 							   tbinfo->dobj.catId.oid))
@@ -15777,6 +15781,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
 		char	   *foreign = "";
+		char	   *table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15830,9 +15835,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);
 
@@ -16226,7 +16237,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
-			 tbinfo->relkind == RELKIND_MATVIEW))
+			 tbinfo->relkind == RELKIND_MATVIEW) &&
+			 tbinfo->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
 		{
 			appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
 			appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
@@ -17131,6 +17143,7 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	PQExpBuffer query = createPQExpBuffer();
 	PQExpBuffer delqry = createPQExpBuffer();
 	char	   *qseqname;
+	bool	   global_temp_seq = false;
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
@@ -17140,9 +17153,11 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
-						  "FROM pg_catalog.pg_sequence "
-						  "WHERE seqrelid = '%u'::oid",
+						  "seqcache, seqcycle, c.relpersistence "
+						  "FROM pg_catalog.pg_sequence s, "
+						  "pg_catalog.pg_class c "
+						  "WHERE seqrelid = '%u'::oid "
+						  "and s.seqrelid = c.oid",
 						  tbinfo->dobj.catId.oid);
 	}
 	else if (fout->remoteVersion >= 80400)
@@ -17187,6 +17202,9 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
 
+	if (fout->remoteVersion >= 130000)
+		global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
 	if (strcmp(seqtype, "smallint") == 0)
@@ -17261,9 +17279,13 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	}
 	else
 	{
-		appendPQExpBuffer(query,
-						  "CREATE SEQUENCE %s\n",
-						  fmtQualifiedDumpable(tbinfo));
+		appendPQExpBuffer(query, "CREATE ");
+
+		if (global_temp_seq)
+			appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+		appendPQExpBuffer(query, "SEQUENCE %s\n",
+								  fmtQualifiedDumpable(tbinfo));
 
 		if (strcmp(seqtype, "bigint") != 0)
 			appendPQExpBuffer(query, "    AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 00aef85..9ad71c8 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -84,7 +84,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -157,7 +157,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 7e524ea..371168f 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -441,22 +443,46 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 	 * output, so we have to copy that system table.  It's easiest to do that
 	 * by treating it as a user table.
 	 */
-	snprintf(query + strlen(query), sizeof(query) - strlen(query),
-			 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
-			 "  SELECT c.oid, 0::oid, 0::oid "
-			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
-			 "         ON c.relnamespace = n.oid "
-			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
-	/* exclude possible orphaned temp tables */
-			 "    ((n.nspname !~ '^pg_temp_' AND "
-			 "      n.nspname !~ '^pg_toast_temp_' AND "
-			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
-			 "                        'binary_upgrade', 'pg_toast') AND "
-			 "      c.oid >= %u::pg_catalog.oid) OR "
-			 "     (n.nspname = 'pg_catalog' AND "
-			 "      relname IN ('pg_largeobject') ))), ",
-			 FirstNormalObjectId);
+	if (skip_gtt)
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude global temp tables */
+				 "    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
+	else
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
 
 	/*
 	 * Add a CTE that collects OIDs of toast tables belonging to the tables
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 70194eb..8dd9e66 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -411,7 +411,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -642,7 +642,9 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -653,7 +655,9 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 8b90cef..d155205 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -389,7 +389,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 8dca6d8..6b3f571 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3738,7 +3738,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index f6fd623..ea87bc4 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1023,6 +1023,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2394,6 +2396,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2602,6 +2607,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index cbfdfe2..be39472 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -59,7 +59,8 @@ extern Relation heap_create(const char *relname,
 							bool mapped_relation,
 							bool allow_system_table_mods,
 							TransactionId *relfrozenxid,
-							MultiXactId *relminmxid);
+							MultiXactId *relminmxid,
+							bool skip_create_storage);
 
 extern Oid	heap_create_with_catalog(const char *relname,
 									 Oid relnamespace,
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 78b33b2..4be89f6 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -168,6 +168,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad..c8236a1 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5558,6 +5558,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4388',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4389',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4390',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4391',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 30c38e0..7ff2408 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,7 +22,7 @@
 /* GUC variables */
 extern int	wal_skip_threshold;
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationPreTruncate(Relation rel);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..902375d
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern int		vacuum_gtt_defer_check_age;
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Relation relation,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void gtt_force_enable_index(Relation index);
+extern void gtt_fix_index_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+extern void gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index e2638ab..89a5ce4 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 3f88683..f870e9a 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 8fda8e4..172eae9 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_TBM,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_SXACT,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index ae4f573..69cce92 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -117,6 +117,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId session_gtt_frozenxid;	/* session level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 2819282..363fc33 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -282,6 +282,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 0b5957b..0720a4c 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	struct SMgrRelationData *rd_smgr;	/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -305,6 +305,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -570,11 +571,13 @@ typedef struct ViewOptions
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -582,6 +585,7 @@ typedef struct ViewOptions
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -594,6 +598,14 @@ typedef struct ViewOptions
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is either local temp relation of this session
+ * 		or global temp relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -637,6 +649,17 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..ca2d135
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,14 @@
+reset search_path;
+select pg_sleep(5);
+ pg_sleep 
+----------
+ 
+(1 row)
+
+drop schema gtt cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
+drop cascades to table gtt.gtt_with_seq
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..d6a675a
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,383 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+ n 
+---
+ 9
+(1 row)
+
+commit;
+-- 0 row
+select * from gtt6;
+ n 
+---
+(0 rows)
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temp table
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  could not create global temporary table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR:  insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count 
+-------
+     1
+(1 row)
+
+select count(*) from orders;
+ count 
+-------
+     0
+(1 row)
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  2 |  3
+(2 rows)
+
+commit;
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+select * from gtt_s_2 order by c1;
+ c1 | c2 
+----+----
+  1 |  2
+  2 |  4
+(2 rows)
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 1)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 300)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 33 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test_new
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.gtt6
+drop cascades to table gtt_function.foo
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to sequence gtt_function.seq_1
+drop cascades to table gtt_function.gtt_s_1
+drop cascades to table gtt_function.gtt_s_2
+drop cascades to table gtt_function.gt1
+drop cascades to table gtt_function.gtt_test1
+drop cascades to table gtt_function.gtt_test2
+drop cascades to table gtt_function.gtt_test3
+drop cascades to table gtt_function.gtt_test4
+drop cascades to table gtt_function.gtt_test5
+drop cascades to table gtt_function.gtt_test6
+drop cascades to table gtt_function.gtt_test7
+drop cascades to table gtt_function.gtt_test8
+drop cascades to table gtt_function.gtt_test9
+drop cascades to table gtt_function.gtt_test10
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..0646aae
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,90 @@
+set search_path=gtt,sys;
+select nextval('gtt_with_seq_c2_seq');
+ nextval 
+---------
+       1
+(1 row)
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..2f084be
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,343 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+rollback;
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+commit;
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          114688 |                 614400
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |            49152 |         49152 |                  49152
+(2 rows)
+
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2000
+(1 row)
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+ c1 | c2 
+----+----
+  1 |  1
+(1 row)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..8c0c376
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,10 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..4420fdb
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,80 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+ gtt_stats  | gtt_pkey  |        1 |         0 |             0
+(2 rows)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+reindex table gtt;
+reindex index gtt_pkey;
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+ gtt_stats  | gtt_pkey  |       30 |     10000 |             0
+(2 rows)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index ac31840..55b221e 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1349,6 +1349,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 95f1925..76b2374 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..2c8e586
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,8 @@
+
+
+reset search_path;
+
+select pg_sleep(5);
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..81f0bfc
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,253 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+commit;
+-- 0 row
+select * from gtt6;
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+commit;
+select * from gtt_s_1 order by c1;
+select * from gtt_s_2 order by c1;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d05745e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,44 @@
+
+
+set search_path=gtt,sys;
+
+select nextval('gtt_with_seq_c2_seq');
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..39cca5e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,154 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+rollback;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+commit;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+select count(*) from gtt_t_kenyon;
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..dbe84d1
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,19 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..d61b0ff
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,46 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+reindex table gtt;
+
+reindex index gtt_pkey;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#265曾文旌
wenjing.zwj@alibaba-inc.com
In reply to: Prabhat Sahu (#260)
2 attachment(s)
Re: [Proposal] Global temporary tables

2020年4月24日 下午3:28,Prabhat Sahu <prabhat.sahu@enterprisedb.com> 写道:

Hi Wenjing,

With the new patch(v30) as you mentioned the new syntax support for "TRUNCATE TABLE gtt DROP", but we also observe the syntax "DROP TABLE gtt DROP" is working as below:

postgres=# create global temporary table gtt(c1 int) on commit preserve rows;
CREATE TABLE
postgres=# DROP TABLE gtt DROP;
DROP TABLE

Fixed in v31.
The truncate GTT drop was also removed.

Wenjing

Show quoted text

Does this syntax intensional? If not, we should get a syntax error.

On Fri, Apr 24, 2020 at 10:25 AM Prabhat Sahu <prabhat.sahu@enterprisedb.com <mailto:prabhat.sahu@enterprisedb.com>> wrote:
Hi Wenjing,

Please check, the server getting crash with the below scenario(CLUSTER gtt using INDEX).

-- Session1:
postgres=# create global temporary table gtt (c1 integer) on commit preserve rows;
CREATE TABLE
postgres=# create index idx1 on gtt (c1);
CREATE INDEX

-- Session2:
postgres=# create index idx2 on gtt (c1);
CREATE INDEX

-- Session1:
postgres=# cluster gtt using idx1;
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.
!?>

-- Below is the stacktrace:
[edb@localhost bin]$ gdb -q -c data/core.95690 postgres
Reading symbols from /home/edb/PG/PGsrcNew/postgresql/inst/bin/postgres...done.
[New LWP 95690]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Core was generated by `postgres: edb postgres [local] CLUSTER '.
Program terminated with signal 6, Aborted.
#0 0x00007f9c574ee337 in raise () from /lib64/libc.so.6
Missing separate debuginfos, use: debuginfo-install glibc-2.17-292.el7.x86_64 keyutils-libs-1.5.8-3.el7.x86_64 krb5-libs-1.15.1-37.el7_7.2.x86_64 libcom_err-1.42.9-16.el7.x86_64 libgcc-4.8.5-39.el7.x86_64 libselinux-2.5-14.1.el7.x86_64 openssl-libs-1.0.2k-19.el7.x86_64 pcre-8.32-17.el7.x86_64 zlib-1.2.7-18.el7.x86_64
(gdb) bt
#0 0x00007f9c574ee337 in raise () from /lib64/libc.so.6
#1 0x00007f9c574efa28 in abort () from /lib64/libc.so.6
#2 0x0000000000ab3a3c in ExceptionalCondition (conditionName=0xb5e2e8 "!ReindexIsProcessingIndex(indexOid)", errorType=0xb5d365 "FailedAssertion",
fileName=0xb5d4e9 "index.c", lineNumber=3825) at assert.c:67
#3 0x00000000005b0412 in reindex_relation (relid=16384, flags=2, options=0) at index.c:3825
#4 0x000000000065e36d in finish_heap_swap (OIDOldHeap=16384, OIDNewHeap=16389, is_system_catalog=false, swap_toast_by_content=false,
check_constraints=false, is_internal=true, frozenXid=491, cutoffMulti=1, newrelpersistence=103 'g') at cluster.c:1448
#5 0x000000000065ccef in rebuild_relation (OldHeap=0x7f9c589adef0, indexOid=16387, verbose=false) at cluster.c:602
#6 0x000000000065c757 in cluster_rel (tableOid=16384, indexOid=16387, options=0) at cluster.c:418
#7 0x000000000065c2cf in cluster (stmt=0x2cd1600, isTopLevel=true) at cluster.c:180
#8 0x000000000093b213 in standard_ProcessUtility (pstmt=0x2cd16c8, queryString=0x2cd0b30 "cluster gtt using idx1;", context=PROCESS_UTILITY_TOPLEVEL,
params=0x0, queryEnv=0x0, dest=0x2cd19a8, qc=0x7ffcd32604b0) at utility.c:819
#9 0x000000000093aa50 in ProcessUtility (pstmt=0x2cd16c8, queryString=0x2cd0b30 "cluster gtt using idx1;", context=PROCESS_UTILITY_TOPLEVEL, params=0x0,
queryEnv=0x0, dest=0x2cd19a8, qc=0x7ffcd32604b0) at utility.c:522
#10 0x00000000009398c2 in PortalRunUtility (portal=0x2d36ba0, pstmt=0x2cd16c8, isTopLevel=true, setHoldSnapshot=false, dest=0x2cd19a8, qc=0x7ffcd32604b0)
at pquery.c:1157
#11 0x0000000000939ad8 in PortalRunMulti (portal=0x2d36ba0, isTopLevel=true, setHoldSnapshot=false, dest=0x2cd19a8, altdest=0x2cd19a8, qc=0x7ffcd32604b0)
at pquery.c:1303
#12 0x0000000000938ff6 in PortalRun (portal=0x2d36ba0, count=9223372036854775807, isTopLevel=true, run_once=true, dest=0x2cd19a8, altdest=0x2cd19a8,
qc=0x7ffcd32604b0) at pquery.c:779
#13 0x00000000009331b0 in exec_simple_query (query_string=0x2cd0b30 "cluster gtt using idx1;") at postgres.c:1239
#14 0x00000000009371bc in PostgresMain (argc=1, argv=0x2cfab80, dbname=0x2cfaa78 "postgres", username=0x2cfaa58 "edb") at postgres.c:4315
#15 0x00000000008872a9 in BackendRun (port=0x2cf2b50) at postmaster.c:4510
#16 0x0000000000886a9e in BackendStartup (port=0x2cf2b50) at postmaster.c:4202
#17 0x000000000088301c in ServerLoop () at postmaster.c:1727
#18 0x00000000008828f3 in PostmasterMain (argc=3, argv=0x2ccb460) at postmaster.c:1400
#19 0x0000000000789c54 in main (argc=3, argv=0x2ccb460) at main.c:210
(gdb)

--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

Attachments:

global_temporary_table_v31-pg13.patchapplication/octet-stream; name=global_temporary_table_v31-pg13.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 8ccc228..b061de9 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -168,6 +168,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * For global temp table only
+	 * use ShareUpdateExclusiveLock for ensure safety
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temp table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},	
 	/* list terminator */
 	{{NULL}}
 };
@@ -1817,6 +1830,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1961,13 +1976,18 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	/*
 	 * There are no options for partitioned tables yet, but this is able to do
 	 * some validation.
 	 */
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index 765329b..4761fdc 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1011,7 +1011,9 @@ gistproperty(Oid index_oid, int attno,
 XLogRecPtr
 gistGetFakeLSN(Relation rel)
 {
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	/* global temp is same as local temp table */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 3ec6d52..1222594 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -150,7 +150,9 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	/* global temp table is same as local temp table */
+	if (!(index->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		index->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 56b3562..d3443ec 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -587,7 +587,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -640,7 +640,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 3c18db2..b08a1fc 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -62,6 +62,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -431,9 +432,9 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
 	Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
-	/* not every AM requires these to be valid, but heap does */
-	Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
-	Assert(MultiXactIdIsValid(onerel->rd_rel->relminmxid));
+	/* not every AM requires these to be valid, but regular heap does */
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ MultiXactIdIsValid(onerel->rd_rel->relminmxid));
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index f5962f6..045c0ce 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -601,6 +602,10 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 065eb27..8427894 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6433,6 +6433,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/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 5eaca27..e5002aa 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -231,7 +231,8 @@ Boot_CreateStmt:
 												   mapped_relation,
 												   true,
 												   &relfrozenxid,
-												   &relminmxid);
+												   &relminmxid,
+												   false);
 						elog(DEBUG4, "bootstrap relation created");
 					}
 					else
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 9499bb3..ae47364 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -43,6 +43,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 7d6acae..3be8d63 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -393,7 +393,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/* global temp table is same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 632c058..9511182 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -304,7 +306,8 @@ heap_create(const char *relname,
 			bool mapped_relation,
 			bool allow_system_table_mods,
 			TransactionId *relfrozenxid,
-			MultiXactId *relminmxid)
+			MultiXactId *relminmxid,
+			bool skip_create_storage)
 {
 	bool		create_storage;
 	Relation	rel;
@@ -404,6 +407,9 @@ heap_create(const char *relname,
 									 relpersistence,
 									 relkind);
 
+	if (skip_create_storage)
+		create_storage = false;
+
 	/*
 	 * Have the storage manager create the relation's disk file, if needed.
 	 *
@@ -427,7 +433,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -959,6 +965,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -997,8 +1004,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;
@@ -1261,7 +1278,8 @@ heap_create_with_catalog(const char *relname,
 							   mapped_relation,
 							   allow_system_table_mods,
 							   &relfrozenxid,
-							   &relminmxid);
+							   &relminmxid,
+							   false);
 
 	Assert(relid == RelationGetRelid(new_rel_desc));
 
@@ -1360,6 +1378,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1940,6 +1959,14 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/* We allow to drop global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			elog(ERROR, "can not drop relation %s when other backend attached this global temp table",
+						RelationGetRelationName(rel));
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3167,7 +3194,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3179,7 +3206,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3225,8 +3252,13 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
+
+		/* truncate global temp table only need RowExclusiveLock */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3259,6 +3291,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3267,23 +3300,37 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (!gtt_storage_attached(RelationGetRelid(rel)))
+			return;
+
+		/*
+		 * Truncate global temp table only need RowExclusiveLock
+		 */
+		lockmode = RowExclusiveLock;
+	}
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(rel, 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 7cfbdd5..c70c48f 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -53,6 +53,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -727,6 +728,11 @@ index_create(Relation heapRelation,
 	char		relkind;
 	TransactionId relfrozenxid;
 	MultiXactId relminmxid;
+	bool		skip_create_storage = false;
+
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation) &&
+		!gtt_storage_attached(RelationGetRelid(heapRelation)))
+		skip_create_storage = true;
 
 	/* constraint flags can only be set when a constraint is requested */
 	Assert((constr_flags == 0) ||
@@ -880,6 +886,19 @@ index_create(Relation heapRelation,
 						indexRelationName, RelationGetRelationName(heapRelation))));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temp table use concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot reindex global temporary tables concurrently")));
+
+		/* if global temp table not init storage, then skip build index */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -934,7 +953,8 @@ index_create(Relation heapRelation,
 								mapped_relation,
 								allow_system_table_mods,
 								&relfrozenxid,
-								&relminmxid);
+								&relminmxid,
+								skip_create_storage);
 
 	Assert(relfrozenxid == InvalidTransactionId);
 	Assert(relminmxid == InvalidMultiXactId);
@@ -2040,7 +2060,8 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 * lock (see comments in RemoveRelations), and a non-concurrent DROP is
 	 * more efficient.
 	 */
-	Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+	Assert(!(get_rel_persistence(indexId) == RELPERSISTENCE_TEMP ||
+			 get_rel_persistence(indexId) == RELPERSISTENCE_GLOBAL_TEMP) ||
 		   (!concurrent && !concurrent_lock_mode));
 
 	/*
@@ -2072,6 +2093,14 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/* We allow to drop index on global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+			elog(ERROR, "can not drop index %s when other backend attached this global temp table.",
+						RelationGetRelationName(userHeapRelation));
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2680,6 +2709,11 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	/* update index stats into localhash and rel_rd_rel for global temp table */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		is_gtt = true;
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2765,21 +2799,35 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
+		if (is_gtt)
+			rel->rd_rel->relpages = (int32) relpages;
+		else if (rd_rel->relpages != (int32) relpages)
 		{
 			rd_rel->relpages = (int32) relpages;
 			dirty = true;
 		}
-		if (rd_rel->reltuples != (float4) reltuples)
+
+		if (is_gtt)
+			rel->rd_rel->reltuples = (float4) reltuples;
+		else if (rd_rel->reltuples != (float4) reltuples)
 		{
 			rd_rel->reltuples = (float4) reltuples;
 			dirty = true;
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+
+		if (is_gtt)
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+		else if (rd_rel->relallvisible != (int32) relallvisible)
 		{
 			rd_rel->relallvisible = (int32) relallvisible;
 			dirty = true;
 		}
+
+		if (is_gtt)
+		{
+			up_gtt_relstats(rel, relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
+		}
 	}
 
 	/*
@@ -2893,6 +2941,15 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, index, val);
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			gtt_force_enable_index(indexRelation);
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3448,6 +3505,16 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	 * we only need to be sure no schema or data changes are going on.
 	 */
 	heapId = IndexGetRelation(indexId, false);
+
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		!gtt_storage_attached(indexId))
+	{
+		/* Correction status flag, and return */
+		SetReindexProcessing(heapId, indexId);
+		ResetReindexProcessing();
+		return;
+	}
+
 	heapRelation = table_open(heapId, ShareLock);
 
 	if (progress)
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 2ec2301..1b6061d 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -655,6 +655,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		/* global temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp relations in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index d713d5c..afcf004 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,6 +27,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -61,6 +62,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			relOid;			/* InvalidOid if not a global temp rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -115,7 +117,7 @@ AddPendingSync(const RelFileNode *rnode)
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -127,6 +129,8 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		/* global temp table use same storage strategy as local temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -154,6 +158,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->relOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
@@ -165,6 +170,13 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		AddPendingSync(&rnode);
 	}
 
+	/* remember global temp table storage info to localhash */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel)
+	{
+		pending->relOid = RelationGetRelid(rel);
+		remember_gtt_storage_info(rnode, rel);
+	}
+
 	return srel;
 }
 
@@ -201,11 +213,15 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->relOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->relOid = RelationGetRelid(rel);
+
 	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
@@ -602,6 +618,7 @@ smgrDoPendingDeletes(bool isCommit)
 				i = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -631,14 +648,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->relOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -651,9 +672,18 @@ smgrDoPendingDeletes(bool isCommit)
 		smgrdounlinkall(srels, nrels, false);
 
 		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* clean global temp table flags when transaction commit or rollback */
+			if (SmgrIsTemp(srels[i]) &&
+				reloids[i] != InvalidOid &&
+				gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..451cf82
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1491 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  code to create and destroy physical storage for global temparary table
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_relstats_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+int		vacuum_gtt_defer_check_age = 0;
+
+typedef struct gtt_ctl_data
+{
+	LWLock			lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+	Oid			relfilenode;
+	Oid			spcnode;
+
+	/* pg_class stat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+
+	/* pg_statistic */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+
+	Oid			oldrelid;			/* remember the source of relid, before the switch relfilenode. */
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_reset_statistics(gtt_local_hash_entry *entry);
+static void gtt_free_statistics(gtt_local_hash_entry *entry);
+static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok);
+static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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();
+	}
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(gtt_fnode);
+	info.entrysize = action_gtt_shared_hash_entry_size();
+	active_gtt_shared_hash =
+		ShmemInitHash("active gtt shared hash",
+						gtt_shared_ctl->max_entry,
+						gtt_shared_ctl->max_entry,
+						&info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			found;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *)&(fnode), 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_global_temporary_table.")));
+	}
+
+	if (found == false)
+	{
+		int			wordnum;
+
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode				fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	if (!skiplock)
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		if (!skiplock)
+			LWLockRelease(&gtt_shared_ctl->lock);
+
+		if (isCommit)
+			elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+	bms_del_member(entry->map, MyBackendId);
+
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+
+	if (!skiplock)
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+	return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	Bitmapset	*map_copy = NULL;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool		in_use = false;
+	int			num_use = 0;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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;
+	gtt_relfilenode			*new_node = NULL;
+	Oid						relid = RelationGetRelid(rel);
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		/* First time through: initialize the hash table */
+		HASHCTL		ctl;
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_relstats_context =
+			AllocSetContextCreate(CacheMemoryContext,
+							"gtt relstats context",
+							ALLOCSET_DEFAULT_SIZES);
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+	entry = gtt_search_by_relid(relid, true);
+	if (!entry)
+	{
+		bool		found = false;
+		int 		natts = 0;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash,
+						(void *) &relid, HASH_ENTER, &found);
+
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+		entry->natts = 0;
+		entry->attnum = NULL;
+		entry->att_stat_tups = NULL;
+		entry->oldrelid = InvalidOid;
+
+		natts = RelationGetNumberOfAttributes(rel);
+		entry->attnum = palloc0(sizeof(int) * natts);
+		entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+		entry->natts = natts;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			gtt_storage_checkin(relid);
+		}
+	}
+
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* only heap contain transaction information */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	gtt_reset_statistics(entry);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	gtt_relfilenode *d_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+		return;
+	}
+
+	d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+		else if (entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+			gtt_relfilenode *gttnode2 = NULL;
+
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+			Assert(gttnode2->relfilenode == rnode.relNode);
+			Assert(list_length(entry->relfilenode_list) == 1);
+			/* rollback switch relfilenode */
+			gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+									   entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+									   false);
+			/* clean up footprint */
+			entry2->oldrelid = InvalidOid;
+			d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+			Assert(d_rnode);
+		}
+		else
+		{
+			if (entry->relfilenode_list == NIL)
+			{
+				if (entry->relkind == RELKIND_RELATION ||
+					entry->relkind == RELKIND_SEQUENCE)
+					gtt_storage_checkout(relid, false, isCommit);
+
+				gtt_free_statistics(entry);
+				hash_search(gtt_storage_local_hash,
+						(void *) &(relid), HASH_REMOVE, NULL);
+			}
+
+			return;
+		}
+	}
+
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	pfree(d_rnode);
+	if (entry->relfilenode_list == NIL)
+	{
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(relid, false, isCommit);
+
+		if (isCommit && entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			/* clean up footprint */
+			entry2->oldrelid = InvalidOid;
+		}
+
+		gtt_free_statistics(entry);
+		hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_REMOVE, NULL);
+	}
+	else
+		gtt_reset_statistics(entry);
+
+	return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool found = false;
+	gtt_local_hash_entry *entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry)
+		found = true;
+
+	return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS				status;
+	gtt_local_hash_entry		*entry;
+	SMgrRelation	*srels = NULL;
+	Oid				*relids = NULL;
+	char			*relkinds = NULL;
+	int			nrels = 0,
+				nfiles = 0,
+				maxrels = 0,
+				maxfiles = 0,
+				i = 0;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel;
+			RelFileNode 	rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel = smgropen(rnode, MyBackendId);
+
+			if (maxfiles == 0)
+			{
+				maxfiles = 32;
+				srels = palloc(sizeof(SMgrRelation) * maxfiles);
+			}
+			else if (maxfiles <= nfiles)
+			{
+				maxfiles *= 2;
+				srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+			}
+
+			srels[nfiles++] = srel;
+		}
+
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			relids  = palloc(sizeof(Oid) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			relids  = repalloc(relids , sizeof(Oid) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		relkinds[nrels] = entry->relkind;
+		relids[nrels] = entry->relid;
+		nrels++;
+	}
+
+	if (nfiles > 0)
+	{
+		smgrdounlinkall(srels, nfiles, false);
+		for (i = 0; i < nfiles; i++)
+			smgrclose(srels[i]);
+
+		pfree(srels);
+	}
+
+	if (nrels)
+	{
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+		for (i = 0; i < nrels; i++)
+		{
+			if (relkinds[i] == RELKIND_RELATION ||
+				relkinds[i] == RELKIND_SEQUENCE)
+				gtt_storage_checkout(relids[i], true, false);
+		}
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+		pfree(relids);
+		pfree(relkinds);
+	}
+
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+	return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible) 
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+					BlockNumber num_pages,
+					double num_tuples,
+					BlockNumber num_all_visible_pages,
+					TransactionId relfrozenxid,
+					TransactionId relminmxid)
+{
+	Oid		relid = RelationGetRelid(relation);
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (num_pages >= 0 &&
+		gtt_rnode->relpages != (int32)num_pages)
+		gtt_rnode->relpages = (int32)num_pages;
+
+	if (num_tuples >= 0 &&
+		gtt_rnode->reltuples != (float4)num_tuples)
+		gtt_rnode->reltuples = (float4)num_tuples;
+
+	/* only heap contain transaction information and relallvisible */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (gtt_rnode->relallvisible >= 0 &&
+			gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+		{
+			gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+		}
+
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNewTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible) 
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	MemoryContext			oldcontext;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return;
+
+	if (entry->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	Assert(entry->relid == reloid);
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == 0)
+		{
+			entry->attnum[i] = attnum;
+			break;
+		}
+		else if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < entry->natts);
+	Assert(entry->att_stat_tups[i] == NULL);
+	entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return NULL;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			return entry->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int			i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	HeapTuple	tuple;
+	Relation	rel = NULL;
+	int		attnum = PG_GETARG_INT32(1);
+	Oid		reloid = PG_GETARG_OID(0);
+	char		rel_persistence;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	  tupdesc;
+	MemoryContext oldcontext;
+	Tuplestorestate *tupstore;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+
+	relation_close(rel, NoLock);
+	relation_close(pg_tatistic, AccessShareLock);
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate	*tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Oid			relnode = 0;
+	Relation	rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[6];
+		bool	isnull[6];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(reloid);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(RelationGetRelid(index)));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid relOid = index->rd_index->indrelid;
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	if (gtt_storage_attached(relOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int			i;
+	Oid			toastrelid;
+
+	if (!(operation == CMD_UTILITY || operation == CMD_INSERT))
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+
+		index_build(relation, index, info, true, false);
+	}
+
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid 		indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo 	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+static void
+gtt_reset_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+
+		entry->attnum[i] = 0;
+	}
+
+	return;
+}
+
+static void
+gtt_free_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (entry->attnum)
+		pfree(entry->attnum);
+
+	if (entry->att_stat_tups)
+		pfree(entry->att_stat_tups);
+
+	return;
+}
+
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
+void
+gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint)
+{
+	gtt_local_hash_entry	*entry1;
+	gtt_local_hash_entry	*entry2;
+	gtt_relfilenode			*gtt_rnode1 = NULL;
+	gtt_relfilenode			*gtt_rnode2 = NULL;
+	MemoryContext			oldcontext;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry1 = gtt_search_by_relid(rel1, false);
+	gtt_rnode1 = gtt_search_relfilenode(entry1, relfilenode1, false);
+
+	entry2 = gtt_search_by_relid(rel2, false);
+	gtt_rnode2 = gtt_search_relfilenode(entry2, relfilenode2, false);
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	entry1->relfilenode_list = list_delete_ptr(entry1->relfilenode_list, gtt_rnode1);
+	entry2->relfilenode_list = lappend(entry2->relfilenode_list, gtt_rnode1);
+
+	entry2->relfilenode_list = list_delete_ptr(entry2->relfilenode_list, gtt_rnode2);
+	entry1->relfilenode_list = lappend(entry1->relfilenode_list, gtt_rnode2);
+	MemoryContextSwitchTo(oldcontext);
+
+	if (footprint)
+	{
+		entry1->oldrelid = rel2;
+		entry2->oldrelid = rel1;
+	}
+
+	return;
+}
+
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok)
+{
+	gtt_relfilenode			*rnode = NULL;
+	ListCell				*lc;
+
+	Assert(entry);
+
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == relfilenode)
+		{
+			rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (!missing_ok && rnode == NULL)
+		elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+	return rnode;
+}
+
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry == NULL && !missing_ok)
+		elog(ERROR, "relid %u not found in local hash", relid);
+
+	return entry;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index d406ea8..88be041 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM 
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 924ef37..db12eef 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -184,6 +185,13 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -586,14 +594,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 		 * pg_statistic for columns we didn't process, we leave them alone.)
 		 */
 		update_attstats(RelationGetRelid(onerel), inh,
-						attr_cnt, vacattrstats);
+						attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
 
 		for (ind = 0; ind < nindexes; ind++)
 		{
 			AnlIndexData *thisdata = &indexdata[ind];
 
 			update_attstats(RelationGetRelid(Irel[ind]), false,
-							thisdata->attr_cnt, thisdata->vacattrstats);
+							thisdata->attr_cnt, thisdata->vacattrstats,
+							RelationGetRelPersistence(Irel[ind]));
 		}
 
 		/*
@@ -1456,7 +1465,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
  *		by taking a self-exclusive lock on the relation in analyze_rel().
  */
 static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
 {
 	Relation	sd;
 	int			attno;
@@ -1558,31 +1567,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/* Update column statistic to localhash, not catalog */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 04d12a7..42c82c8 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/toasting.h"
 #include "commands/cluster.h"
 #include "commands/progress.h"
@@ -72,6 +73,12 @@ static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
 							bool verbose, bool *pSwapToastByContent,
 							TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
 static List *get_tables_to_cluster(MemoryContext cluster_context);
+static void gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables);
 
 
 /*---------------------------------------------------------------------------
@@ -366,6 +373,14 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap) &&
+		!gtt_storage_attached(RelationGetRelid(OldHeap)))
+	{
+		relation_close(OldHeap, AccessExclusiveLock);
+		pgstat_progress_end_command();
+		return;
+	}
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
@@ -750,6 +765,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	BlockNumber num_pages;
 	int			elevel = verbose ? INFO : DEBUG2;
 	PGRUsage	ru0;
+	bool		is_gtt = false;
+	uint32		gtt_relfrozenxid = 0;
+	uint32		gtt_relminmxid = 0;
 
 	pg_rusage_init(&ru0);
 
@@ -763,6 +781,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	else
 		OldIndex = NULL;
 
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		is_gtt = true;
+
 	/*
 	 * Their tuple descriptors should be exactly alike, but here we only need
 	 * assume that they have the same number of columns.
@@ -830,20 +851,37 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 						  &OldestXmin, &FreezeXid, NULL, &MultiXactCutoff,
 						  NULL);
 
-	/*
-	 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
-	 * backwards, so take the max.
-	 */
-	if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
-		TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
-		FreezeXid = OldHeap->rd_rel->relfrozenxid;
+	if (is_gtt)
+	{
+		get_gtt_relstats(OIDOldHeap,
+					NULL, NULL, NULL,
+					&gtt_relfrozenxid, &gtt_relminmxid);
 
-	/*
-	 * MultiXactCutoff, similarly, shouldn't go backwards either.
-	 */
-	if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
-		MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
-		MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+		if (TransactionIdIsValid(gtt_relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, gtt_relfrozenxid))
+			FreezeXid = gtt_relfrozenxid;
+
+		if (MultiXactIdIsValid(gtt_relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, gtt_relminmxid))
+			MultiXactCutoff = gtt_relminmxid;
+	}
+	else
+	{
+		/*
+		 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
+		 * backwards, so take the max.
+		 */
+		if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
+			FreezeXid = OldHeap->rd_rel->relfrozenxid;
+
+		/*
+		 * MultiXactCutoff, similarly, shouldn't go backwards either.
+		 */
+		if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
+			MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+	}
 
 	/*
 	 * Decide whether to use an indexscan or seqscan-and-optional-sort to scan
@@ -911,6 +949,12 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	table_close(OldHeap, NoLock);
 	table_close(NewHeap, NoLock);
 
+	if (is_gtt)
+	{
+		CommandCounterIncrement();
+		return;
+	}
+
 	/* Update pg_class to reflect the correct values of pages and tuples. */
 	relRelation = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1346,10 +1390,21 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 	 * Swap the contents of the heap relations (including any toast tables).
 	 * Also set old heap's relfrozenxid to frozenXid.
 	 */
-	swap_relation_files(OIDOldHeap, OIDNewHeap,
+	if (newrelpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		Assert(!is_system_catalog);
+		gtt_swap_relation_files(OIDOldHeap, OIDNewHeap,
+								(OIDOldHeap == RelationRelationId),
+								swap_toast_by_content, is_internal,
+								frozenXid, cutoffMulti, mapped_tables);
+	}
+	else
+	{
+		swap_relation_files(OIDOldHeap, OIDNewHeap,
 						(OIDOldHeap == RelationRelationId),
 						swap_toast_by_content, is_internal,
 						frozenXid, cutoffMulti, mapped_tables);
+	}
 
 	/*
 	 * If it's a system catalog, queue a sinval message to flush all catcaches
@@ -1557,3 +1612,141 @@ get_tables_to_cluster(MemoryContext cluster_context)
 
 	return rvs;
 }
+
+static void
+gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables)
+{
+	Relation	relRelation;
+	Oid			relfilenode1,
+				relfilenode2;
+	Relation	rel1;
+	Relation	rel2;
+
+	relRelation = table_open(RelationRelationId, RowExclusiveLock);
+
+	rel1 = relation_open(r1, AccessExclusiveLock);
+	rel2 = relation_open(r2, AccessExclusiveLock);
+
+	relfilenode1 = gtt_fetch_current_relfilenode(r1);
+	relfilenode2 = gtt_fetch_current_relfilenode(r2);
+
+	Assert(OidIsValid(relfilenode1) && OidIsValid(relfilenode2));
+	gtt_switch_rel_relfilenode(r1, relfilenode1, r2, relfilenode2, true);
+
+	CacheInvalidateRelcache(rel1);
+	CacheInvalidateRelcache(rel2);
+
+	InvokeObjectPostAlterHookArg(RelationRelationId, r1, 0,
+								 InvalidOid, is_internal);
+	InvokeObjectPostAlterHookArg(RelationRelationId, r2, 0,
+								 InvalidOid, true);
+
+	if (rel1->rd_rel->reltoastrelid || rel2->rd_rel->reltoastrelid)
+	{
+		if (swap_toast_by_content)
+		{
+			if (rel1->rd_rel->reltoastrelid && rel2->rd_rel->reltoastrelid)
+			{
+				gtt_swap_relation_files(rel1->rd_rel->reltoastrelid,
+									rel2->rd_rel->reltoastrelid,
+									target_is_pg_class,
+									swap_toast_by_content,
+									is_internal,
+									frozenXid,
+									cutoffMulti,
+									mapped_tables);
+			}
+			else
+				elog(ERROR, "cannot swap toast files by content when there's only one");
+		}
+		else
+		{
+			ObjectAddress baseobject,
+						toastobject;
+			long		count;
+
+			if (IsSystemRelation(rel1))
+				elog(ERROR, "cannot swap toast files by links for system catalogs");
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel1->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel2->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+
+			/* Register new dependencies */
+			baseobject.classId = RelationRelationId;
+			baseobject.objectSubId = 0;
+			toastobject.classId = RelationRelationId;
+			toastobject.objectSubId = 0;
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r1;
+				toastobject.objectId = rel1->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r2;
+				toastobject.objectId = rel2->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+		}
+	}
+
+	if (swap_toast_by_content &&
+		rel1->rd_rel->relkind == RELKIND_TOASTVALUE &&
+		rel2->rd_rel->relkind == RELKIND_TOASTVALUE)
+	{
+		Oid			toastIndex1,
+					toastIndex2;
+
+		/* Get valid index for each relation */
+		toastIndex1 = toast_get_valid_index(r1,
+											AccessExclusiveLock);
+		toastIndex2 = toast_get_valid_index(r2,
+											AccessExclusiveLock);
+
+		gtt_swap_relation_files(toastIndex1,
+							toastIndex2,
+							target_is_pg_class,
+							swap_toast_by_content,
+							is_internal,
+							InvalidTransactionId,
+							InvalidMultiXactId,
+							mapped_tables);
+	}
+
+	relation_close(rel1, NoLock);
+	relation_close(rel2, NoLock);
+
+	table_close(relRelation, RowExclusiveLock);
+
+	RelationCloseSmgrByOid(r1);
+	RelationCloseSmgrByOid(r2);
+
+	CommandCounterIncrement();
+}
+
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index ac07f75..655184d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -27,6 +27,7 @@
 #include "catalog/dependency.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -1063,7 +1064,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, stmt->filename, stmt->is_program,
@@ -2794,6 +2795,8 @@ CopyFrom(CopyState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	estate->es_result_relations = resultRelInfo;
 	estate->es_num_result_relations = 1;
 	estate->es_result_relation_info = resultRelInfo;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 2baca12..5338db9 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -521,6 +521,7 @@ DefineIndex(Oid relationId,
 	Snapshot	snapshot;
 	int			save_nestlevel = -1;
 	int			i;
+	char		rel_persistence;
 
 	/*
 	 * Some callers need us to run with an empty default_tablespace; this is a
@@ -542,7 +543,9 @@ DefineIndex(Oid relationId,
 	 * is more efficient.  Do this before any use of the concurrent option is
 	 * done.
 	 */
-	if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+	rel_persistence = get_rel_persistence(relationId);
+	if (stmt->concurrent &&
+		!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		concurrent = true;
 	else
 		concurrent = false;
@@ -2460,7 +2463,8 @@ ReindexIndex(RangeVar *indexRelation, int options, bool concurrent)
 	persistence = irel->rd_rel->relpersistence;
 	index_close(irel, NoLock);
 
-	if (concurrent && persistence != RELPERSISTENCE_TEMP)
+	if (concurrent &&
+		!(persistence == RELPERSISTENCE_TEMP || persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		ReindexRelationConcurrently(indOid, options);
 	else
 		reindex_index(indOid, false, persistence,
@@ -2546,6 +2550,7 @@ ReindexTable(RangeVar *relation, int options, bool concurrent)
 {
 	Oid			heapOid;
 	bool		result;
+	char		rel_persistence;
 
 	/*
 	 * The lock level used here should match reindex_relation().
@@ -2560,7 +2565,9 @@ ReindexTable(RangeVar *relation, int options, bool concurrent)
 									   0,
 									   RangeVarCallbackOwnsTable, NULL);
 
-	if (concurrent && get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+	rel_persistence = get_rel_persistence(heapOid);
+	if (concurrent &&
+		!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 	{
 		result = ReindexRelationConcurrently(heapOid, options);
 
@@ -2761,12 +2768,15 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 	foreach(l, relids)
 	{
 		Oid			relid = lfirst_oid(l);
+		char		rel_persistence;
 
 		StartTransactionCommand();
 		/* functions in indexes may want a snapshot set */
 		PushActiveSnapshot(GetTransactionSnapshot());
 
-		if (concurrent && get_rel_persistence(relid) != RELPERSISTENCE_TEMP)
+		rel_persistence = get_rel_persistence(relid);
+		if (concurrent &&
+			!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		{
 			(void) ReindexRelationConcurrently(relid, options);
 			/* ReindexRelationConcurrently() does the verbose output */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..a45863a 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -94,7 +96,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -222,7 +225,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* now initialize the sequence's data */
 	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -327,7 +330,7 @@ ResetSequence(Oid seq_relid)
 	/*
 	 * Insert the modified tuple into the new storage file.
 	 */
-	fill_seq_with_data(seq_rel, tuple);
+	fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -340,17 +343,21 @@ ResetSequence(Oid seq_relid)
  * Initialize a sequence's relation with the specified tuple as content
  */
 static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 	sequence_magic *sm;
 	OffsetNumber offnum;
+	bool lockBuffer = false;
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ReadBuffer(rel, P_NEW);
-	Assert(BufferGetBlockNumber(buf) == 0);
+	if (buf == InvalidBuffer)
+	{
+		buf = ReadBuffer(rel, P_NEW);
+		Assert(BufferGetBlockNumber(buf) == 0);
+		lockBuffer = true;
+	}
 
 	page = BufferGetPage(buf);
 
@@ -360,7 +367,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* Now insert sequence tuple */
 
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+	if (lockBuffer)
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +418,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	END_CRIT_SECTION();
 
-	UnlockReleaseBuffer(buf);
+	if (lockBuffer)
+		UnlockReleaseBuffer(buf);
 }
 
 /*
@@ -451,6 +460,13 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 
 	init_sequence(relid, &elm, &seqrel);
 
+	if (RELATION_IS_GLOBAL_TEMP(seqrel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(seqrel)))
+			elog(ERROR, "cannot alter global temp sequence %s when other backend attached it",
+						RelationGetRelationName(seqrel));
+	}
+
 	rel = table_open(SequenceRelationId, RowExclusiveLock);
 	seqtuple = SearchSysCacheCopy1(SEQRELID,
 								   ObjectIdGetDatum(relid));
@@ -502,7 +518,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * Insert the modified tuple into the new storage file.
 		 */
-		fill_seq_with_data(seqrel, newdatatuple);
+		fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
 	}
 
 	/* process OWNED BY if given */
@@ -611,7 +627,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +952,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1154,6 +1170,13 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1954,3 +1977,46 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+void
+gtt_init_seq(Relation rel)
+{
+	/* Initialize sequence for global temporary tables */
+	Datum		value[SEQ_COL_LASTCOL] = {0};
+	bool		null[SEQ_COL_LASTCOL] = {false};
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5745cd6..0e9c9cd 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -556,6 +557,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 								  Relation partitionTbl);
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static OnCommitAction gtt_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -601,6 +603,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -611,8 +614,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")));
@@ -642,7 +647,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	/* global temp table same as local temp table */
+	if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+		 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -743,6 +750,55 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* global temp table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* check parent table*/
+		if (inheritOids)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			table_close(relation, NoLock);
+		}
+
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temp table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1330,6 +1386,7 @@ RemoveRelations(DropStmt *drop)
 		Oid			relOid;
 		ObjectAddress obj;
 		struct DropRelationCallbackState state;
+		char		rel_persistence;
 
 		/*
 		 * These next few steps are a great deal like relation_openrv, but we
@@ -1363,8 +1420,9 @@ RemoveRelations(DropStmt *drop)
 		 * Decide if concurrent mode needs to be used here or not.  The
 		 * relation persistence cannot be known without its OID.
 		 */
+		rel_persistence = get_rel_persistence(relOid);
 		if (drop->concurrent &&
-			get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+			!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		{
 			Assert(list_length(drop->objects) == 1 &&
 				   drop->removeType == OBJECT_INDEX);
@@ -1819,6 +1877,10 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
 		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 			continue;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			continue;
+
 		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
@@ -3570,6 +3632,14 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			elog(ERROR, "can not alter table %s when other backend attached this global temp table",
+						RelationGetRelationName(rel));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -4847,6 +4917,38 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot rewrite temporary tables of other sessions")));
 
+			if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+			{
+				if (tab->chgPersistence)
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot change global temporary table persistence setting")));
+
+				/* gtt may not attached, create it */
+				if(!gtt_storage_attached(tab->relid))
+				{
+					ResultRelInfo *resultRelInfo;
+					MemoryContext oldcontext;
+					MemoryContext ctx_alter_gtt;
+
+					ctx_alter_gtt = AllocSetContextCreate(CurrentMemoryContext,
+											"gtt alter table", ALLOCSET_DEFAULT_SIZES);
+
+					oldcontext = MemoryContextSwitchTo(ctx_alter_gtt);
+					resultRelInfo = makeNode(ResultRelInfo);
+					InitResultRelInfo(resultRelInfo, OldHeap,
+									1, NULL, 0);
+					if (resultRelInfo->ri_RelationDesc->rd_rel->relhasindex &&
+						resultRelInfo->ri_IndexRelationDescs == NULL)
+						ExecOpenIndices(resultRelInfo, false);
+
+					init_gtt_storage(CMD_UTILITY, resultRelInfo);
+					ExecCloseIndices(resultRelInfo);
+					MemoryContextSwitchTo(oldcontext);
+					MemoryContextDelete(ctx_alter_gtt);
+				}
+			}
+
 			/*
 			 * Select destination tablespace (same as original unless user
 			 * requested a change)
@@ -8184,6 +8286,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -12740,6 +12848,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -12942,6 +13053,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temp table");
+
 	/*
 	 * No work if no change in tablespace.
 	 */
@@ -13316,7 +13430,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,
@@ -14723,7 +14837,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",
@@ -17379,3 +17495,36 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
 		table_close(rel, NoLock);
 	}
 }
+
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			bool	res = false;
+			char	*sval = defGetString(def);
+
+			if (!parse_bool(sval, &res))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
+
+			if (res)
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 5a110ed..02e6f1e 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"
@@ -1215,6 +1216,17 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	 /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		is_gtt = true;
+		up_gtt_relstats(relation,
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1228,17 +1240,26 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relpages = (int32) num_pages;
+	else if (pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (is_gtt)
+		relation->rd_rel->reltuples = (float4) num_tuples;
+	else if (pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	else if (pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1283,7 +1304,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1294,7 +1316,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1392,6 +1415,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
@@ -1449,6 +1476,42 @@ 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	safe_age;
+		TransactionId	oldest_gtt_frozenxid =
+			list_all_session_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid))
+		{
+			safe_age = oldest_gtt_frozenxid + vacuum_gtt_defer_check_age;
+			if (safe_age < FirstNormalTransactionId)
+				safe_age += FirstNormalTransactionId;
+
+			/*
+			 * We tolerate that the minimum age of gtt is less than
+			 * the minimum age of conventional tables, otherwise it will
+			 * throw warning message.
+			 */
+			if (TransactionIdIsNormal(safe_age) &&
+				TransactionIdPrecedes(safe_age, newFrozenXid))
+			{
+				ereport(WARNING,
+					(errmsg("global temp table oldest relfrozenxid %u is the oldest in the entire db",
+							oldest_gtt_frozenxid),
+					 errdetail("The oldest relfrozenxid in pg_class is %u", newFrozenXid),
+					 errhint("If they differ greatly, please consider cleaning up the data in global temp table.")));
+			}
+
+			if (TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+				newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1776,6 +1839,15 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6e65103..04706ee 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -530,6 +530,11 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 4fdffad..0aa7559 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index fb6ce49..06d9237 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
@@ -545,6 +546,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	init_gtt_storage(mtstate->operation, leaf_part_rri);
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 20a4c47..e9f6dcd 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,6 +42,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -2379,6 +2380,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			ExecOpenIndices(resultRelInfo,
 							node->onConflictAction != ONCONFLICT_NONE);
 
+		init_gtt_storage(operation, resultRelInfo);
+
 		/*
 		 * If this is an UPDATE and a BEFORE UPDATE trigger is present, the
 		 * trigger itself might modify the partition-key values. So arrange
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 255f56b..d94d6f0 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 e664eb1..e03bfa0 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6419,7 +6419,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
+	/* global temp table is same as local temp table */
 	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 2554502..04fb5d4 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -28,6 +28,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temp table */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index eee9c33..1891c53 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2594,6 +2594,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 3c78f2d..edb8d9f 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3302,17 +3302,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; }
@@ -11648,19 +11642,13 @@ OptTempTableName:
 				}
 			| GLOBAL TEMPORARY opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED opt_table qualified_name
 				{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b875a50..123945b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 75c122f..97213ed 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
@@ -3108,6 +3111,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		cxt.isforeign = false;
 	}
 	cxt.relation = stmt->relation;
+	cxt.relation->relpersistence = RelationGetRelPersistence(rel);
 	cxt.rel = rel;
 	cxt.inhRelations = NIL;
 	cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 7e97ffa..1f2ac58 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2089,6 +2089,11 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* autovacuum skip vacuum global temp table */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2155,7 +2160,9 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		/* autovacuum skip vacuum global temp table */
+		if (classForm->relpersistence == RELPERSISTENCE_TEMP || 
+			classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index f9980cf..406a25c 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -53,6 +54,7 @@
 #include "utils/rel.h"
 #include "utils/resowner_private.h"
 #include "utils/timestamp.h"
+#include "utils/guc.h"
 
 
 /* Note: these two macros only work on shared buffers, not local ones! */
@@ -2819,6 +2821,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d5..7adb969 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -21,6 +21,7 @@
 #include "access/nbtree.h"
 #include "access/subtrans.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, active_gtt_shared_hash_size());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* global temporary table */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 3630006..0c8ef80 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -63,6 +63,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -4086,3 +4087,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 5aa19d3..b3bc455 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 8406644..0d99321 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -1008,6 +1008,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index cfb0568..5913efc 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -112,6 +112,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -4821,12 +4822,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))
 							{
@@ -4951,15 +4965,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))
 		{
@@ -6341,6 +6367,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;
@@ -6358,6 +6385,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,
@@ -6369,6 +6403,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;
@@ -6384,6 +6420,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,
@@ -7302,6 +7345,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))
@@ -7314,6 +7359,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 =
@@ -7326,6 +7379,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.
@@ -7345,6 +7400,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index a7d63f1..d8a52cb 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -2939,6 +2940,18 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 9f1f11d..aeb2a78 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -65,6 +65,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1133,6 +1134,24 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1187,6 +1206,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			gtt_fix_index_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -1317,7 +1337,17 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+			if (newrelnode != InvalidOid &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2246,6 +2276,8 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		gtt_fix_index_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3472,6 +3504,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3579,28 +3615,34 @@ void
 RelationSetNewRelfilenode(Relation relation, char persistence)
 {
 	Oid			newrelfilenode;
-	Relation	pg_class;
-	HeapTuple	tuple;
+	Relation	pg_class = NULL;
+	HeapTuple	tuple = NULL;
 	Form_pg_class classform;
 	MultiXactId minmulti = InvalidMultiXactId;
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
+	bool		modify_pg_class = !RELATION_IS_GLOBAL_TEMP(relation);
 
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
 
-	/*
-	 * Get a writable copy of the pg_class tuple for the given relation.
-	 */
-	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+	if (modify_pg_class)
+	{
+		/*
+		 * Get a writable copy of the pg_class tuple for the given relation.
+		 */
+		pg_class = table_open(RelationRelationId, RowExclusiveLock);
 
-	tuple = SearchSysCacheCopy1(RELOID,
-								ObjectIdGetDatum(RelationGetRelid(relation)));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "could not find tuple for relation %u",
-			 RelationGetRelid(relation));
-	classform = (Form_pg_class) GETSTRUCT(tuple);
+		tuple = SearchSysCacheCopy1(RELOID,
+									ObjectIdGetDatum(RelationGetRelid(relation)));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u",
+				 RelationGetRelid(relation));
+		classform = (Form_pg_class) GETSTRUCT(tuple);
+	}
+	else
+		memset(&classform, 0, sizeof(classform));
 
 	/*
 	 * Schedule unlinking of the old storage at transaction commit.
@@ -3626,7 +3668,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;
@@ -3646,6 +3688,15 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 			break;
 	}
 
+	if (!modify_pg_class)
+	{
+		Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+		Assert(RELATION_IS_GLOBAL_TEMP(relation));
+		Assert(!RelationIsMapped(relation));
+		relation->rd_node.relNode = relnode;
+		CacheInvalidateRelcache(relation);
+	}
 	/*
 	 * If we're dealing with a mapped index, pg_class.relfilenode doesn't
 	 * change; instead we have to send the update to the relation mapper.
@@ -3655,7 +3706,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	 * possibly-inaccurate values of relpages etc, but those will be fixed up
 	 * later.
 	 */
-	if (RelationIsMapped(relation))
+	else if (RelationIsMapped(relation))
 	{
 		/* This case is only supported for indexes */
 		Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3701,9 +3752,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 		CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 	}
 
-	heap_freetuple(tuple);
+	if (modify_pg_class)
+	{
+		heap_freetuple(tuple);
 
-	table_close(pg_class, RowExclusiveLock);
+		table_close(pg_class, RowExclusiveLock);
+	}
 
 	/*
 	 * Make the pg_class row change or relation map change visible.  This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 5bdc02f..b52b51d 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -37,6 +37,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "commands/prepare.h"
 #include "commands/trigger.h"
@@ -142,6 +143,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temp table feature.
+ * global temp table define can still storage in catalog
+ * just can not use.
+ * num > 0 means database can management num active global temp table.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2070,6 +2083,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."),
@@ -2577,6 +2599,16 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"vacuum_gtt_defer_check_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("The defer check age of GTT, used to check expired data after vacuum."),
+			NULL
+		},
+		&vacuum_gtt_defer_check_age,
+		10000, 0, 1000000,
+		NULL, NULL, NULL
+	},
+
 	/*
 	 * See also CheckRequiredParameterValues() if this parameter changes
 	 */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 5db4f57..018a9bf 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2424,6 +2424,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
 		dopt->no_unlogged_table_data)
 		return;
 
+	/* Don't dump data in global temp table/sequence */
+	if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		return;
+
 	/* Check that the data is not explicitly excluded */
 	if (simple_oid_list_member(&tabledata_exclude_oids,
 							   tbinfo->dobj.catId.oid))
@@ -15777,6 +15781,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
 		char	   *foreign = "";
+		char	   *table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15830,9 +15835,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);
 
@@ -16226,7 +16237,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
-			 tbinfo->relkind == RELKIND_MATVIEW))
+			 tbinfo->relkind == RELKIND_MATVIEW) &&
+			 tbinfo->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
 		{
 			appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
 			appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
@@ -17131,6 +17143,7 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	PQExpBuffer query = createPQExpBuffer();
 	PQExpBuffer delqry = createPQExpBuffer();
 	char	   *qseqname;
+	bool	   global_temp_seq = false;
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
@@ -17140,9 +17153,11 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
-						  "FROM pg_catalog.pg_sequence "
-						  "WHERE seqrelid = '%u'::oid",
+						  "seqcache, seqcycle, c.relpersistence "
+						  "FROM pg_catalog.pg_sequence s, "
+						  "pg_catalog.pg_class c "
+						  "WHERE seqrelid = '%u'::oid "
+						  "and s.seqrelid = c.oid",
 						  tbinfo->dobj.catId.oid);
 	}
 	else if (fout->remoteVersion >= 80400)
@@ -17187,6 +17202,9 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
 
+	if (fout->remoteVersion >= 130000)
+		global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
 	if (strcmp(seqtype, "smallint") == 0)
@@ -17261,9 +17279,13 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	}
 	else
 	{
-		appendPQExpBuffer(query,
-						  "CREATE SEQUENCE %s\n",
-						  fmtQualifiedDumpable(tbinfo));
+		appendPQExpBuffer(query, "CREATE ");
+
+		if (global_temp_seq)
+			appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+		appendPQExpBuffer(query, "SEQUENCE %s\n",
+								  fmtQualifiedDumpable(tbinfo));
 
 		if (strcmp(seqtype, "bigint") != 0)
 			appendPQExpBuffer(query, "    AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 00aef85..9ad71c8 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -84,7 +84,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -157,7 +157,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 7e524ea..371168f 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -441,22 +443,46 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 	 * output, so we have to copy that system table.  It's easiest to do that
 	 * by treating it as a user table.
 	 */
-	snprintf(query + strlen(query), sizeof(query) - strlen(query),
-			 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
-			 "  SELECT c.oid, 0::oid, 0::oid "
-			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
-			 "         ON c.relnamespace = n.oid "
-			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
-	/* exclude possible orphaned temp tables */
-			 "    ((n.nspname !~ '^pg_temp_' AND "
-			 "      n.nspname !~ '^pg_toast_temp_' AND "
-			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
-			 "                        'binary_upgrade', 'pg_toast') AND "
-			 "      c.oid >= %u::pg_catalog.oid) OR "
-			 "     (n.nspname = 'pg_catalog' AND "
-			 "      relname IN ('pg_largeobject') ))), ",
-			 FirstNormalObjectId);
+	if (skip_gtt)
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude global temp tables */
+				 "    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
+	else
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
 
 	/*
 	 * Add a CTE that collects OIDs of toast tables belonging to the tables
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 70194eb..8dd9e66 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -411,7 +411,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -642,7 +642,9 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -653,7 +655,9 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 8b90cef..d155205 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -389,7 +389,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 8dca6d8..6b3f571 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3738,7 +3738,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index f6fd623..ea87bc4 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1023,6 +1023,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2394,6 +2396,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2602,6 +2607,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index cbfdfe2..be39472 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -59,7 +59,8 @@ extern Relation heap_create(const char *relname,
 							bool mapped_relation,
 							bool allow_system_table_mods,
 							TransactionId *relfrozenxid,
-							MultiXactId *relminmxid);
+							MultiXactId *relminmxid,
+							bool skip_create_storage);
 
 extern Oid	heap_create_with_catalog(const char *relname,
 									 Oid relnamespace,
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 78b33b2..4be89f6 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -168,6 +168,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad..c8236a1 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5558,6 +5558,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4388',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4389',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4390',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4391',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 30c38e0..7ff2408 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,7 +22,7 @@
 /* GUC variables */
 extern int	wal_skip_threshold;
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationPreTruncate(Relation rel);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..902375d
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern int		vacuum_gtt_defer_check_age;
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Relation relation,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void gtt_force_enable_index(Relation index);
+extern void gtt_fix_index_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+extern void gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index e2638ab..89a5ce4 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 3f88683..f870e9a 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 8fda8e4..172eae9 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_TBM,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_SXACT,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index ae4f573..69cce92 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -117,6 +117,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId session_gtt_frozenxid;	/* session level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 2819282..363fc33 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -282,6 +282,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 0b5957b..0720a4c 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	struct SMgrRelationData *rd_smgr;	/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -305,6 +305,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -570,11 +571,13 @@ typedef struct ViewOptions
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -582,6 +585,7 @@ typedef struct ViewOptions
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -594,6 +598,14 @@ typedef struct ViewOptions
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is either local temp relation of this session
+ * 		or global temp relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -637,6 +649,17 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..ca2d135
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,14 @@
+reset search_path;
+select pg_sleep(5);
+ pg_sleep 
+----------
+ 
+(1 row)
+
+drop schema gtt cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
+drop cascades to table gtt.gtt_with_seq
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..d6a675a
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,383 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+ n 
+---
+ 9
+(1 row)
+
+commit;
+-- 0 row
+select * from gtt6;
+ n 
+---
+(0 rows)
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temp table
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  could not create global temporary table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR:  insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count 
+-------
+     1
+(1 row)
+
+select count(*) from orders;
+ count 
+-------
+     0
+(1 row)
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  2 |  3
+(2 rows)
+
+commit;
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+select * from gtt_s_2 order by c1;
+ c1 | c2 
+----+----
+  1 |  2
+  2 |  4
+(2 rows)
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 1)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 300)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 33 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test_new
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.gtt6
+drop cascades to table gtt_function.foo
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to sequence gtt_function.seq_1
+drop cascades to table gtt_function.gtt_s_1
+drop cascades to table gtt_function.gtt_s_2
+drop cascades to table gtt_function.gt1
+drop cascades to table gtt_function.gtt_test1
+drop cascades to table gtt_function.gtt_test2
+drop cascades to table gtt_function.gtt_test3
+drop cascades to table gtt_function.gtt_test4
+drop cascades to table gtt_function.gtt_test5
+drop cascades to table gtt_function.gtt_test6
+drop cascades to table gtt_function.gtt_test7
+drop cascades to table gtt_function.gtt_test8
+drop cascades to table gtt_function.gtt_test9
+drop cascades to table gtt_function.gtt_test10
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..0646aae
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,90 @@
+set search_path=gtt,sys;
+select nextval('gtt_with_seq_c2_seq');
+ nextval 
+---------
+       1
+(1 row)
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..2f084be
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,343 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+rollback;
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+commit;
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          114688 |                 614400
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |            49152 |         49152 |                  49152
+(2 rows)
+
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2000
+(1 row)
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+ c1 | c2 
+----+----
+  1 |  1
+(1 row)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..8c0c376
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,10 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..4420fdb
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,80 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+ gtt_stats  | gtt_pkey  |        1 |         0 |             0
+(2 rows)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+reindex table gtt;
+reindex index gtt_pkey;
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+ gtt_stats  | gtt_pkey  |       30 |     10000 |             0
+(2 rows)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index ac31840..55b221e 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1349,6 +1349,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 95f1925..76b2374 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..2c8e586
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,8 @@
+
+
+reset search_path;
+
+select pg_sleep(5);
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..81f0bfc
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,253 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+commit;
+-- 0 row
+select * from gtt6;
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+commit;
+select * from gtt_s_1 order by c1;
+select * from gtt_s_2 order by c1;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d05745e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,44 @@
+
+
+set search_path=gtt,sys;
+
+select nextval('gtt_with_seq_c2_seq');
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..39cca5e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,154 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+rollback;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+commit;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+select count(*) from gtt_t_kenyon;
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..dbe84d1
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,19 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..d61b0ff
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,46 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+reindex table gtt;
+
+reindex index gtt_pkey;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#266曾文旌
wenjing.zwj@alibaba-inc.com
In reply to: tushar (#261)
1 attachment(s)
Re: [Proposal] Global temporary tables

2020年4月24日 下午9:03,tushar <tushar.ahuja@enterprisedb.com> 写道:

On 4/22/20 2:49 PM, 曾文旌 wrote:

I provide the TRUNCATE tablename DROP to clear the data in the GTT and delete the storage files.
This feature requires the current transaction to commit immediately after it finishes truncate.

Thanks Wenjing , Please refer this scenario

postgres=# create global temp table testing (a int);
CREATE TABLE
postgres=# begin;
BEGIN
postgres=*# truncate testing; -- working [1]
TRUNCATE TABLE
postgres=*# truncate testing drop;
ERROR: Truncate global temporary table cannot run inside a transaction block --that is throwing an error claiming something which i did successfully [1]

The truncate GTT drop was removed.
So the problem goes away.

Wenjing

Show quoted text

postgres=!#

--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company

Attachments:

smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#267曾文旌
wenjing.zwj@alibaba-inc.com
In reply to: Prabhat Sahu (#263)
2 attachment(s)
Re: [Proposal] Global temporary tables

2020年4月27日 下午5:26,Prabhat Sahu <prabhat.sahu@enterprisedb.com> 写道:

Hi Wenjing,

Please check the below scenario shows different error message with "DROP TABLE gtt;" for gtt with and without index.
-- Session1:
postgres=# create global temporary table gtt1 (c1 int);
CREATE TABLE
postgres=# create global temporary table gtt2 (c1 int);
CREATE TABLE
postgres=# create index idx2 on gtt2(c1);
CREATE INDEX

-- Session2:
postgres=# drop table gtt1;
ERROR: can not drop relation gtt1 when other backend attached this global temp table
postgres=# drop table gtt2;
ERROR: can not drop index gtt2 when other backend attached this global temp table.

For DROP GTT, we need to drop the index on the table first.
So the indexes on the GTT are checked first.
But the error message needs to be fixed.
Fixed in v32

wenjing

Show quoted text

--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

Attachments:

global_temporary_table_v32-pg13.patchapplication/octet-stream; name=global_temporary_table_v32-pg13.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 8ccc228..b061de9 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -168,6 +168,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * For global temp table only
+	 * use ShareUpdateExclusiveLock for ensure safety
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temp table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},	
 	/* list terminator */
 	{{NULL}}
 };
@@ -1817,6 +1830,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1961,13 +1976,18 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	/*
 	 * There are no options for partitioned tables yet, but this is able to do
 	 * some validation.
 	 */
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index 765329b..4761fdc 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1011,7 +1011,9 @@ gistproperty(Oid index_oid, int attno,
 XLogRecPtr
 gistGetFakeLSN(Relation rel)
 {
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	/* global temp is same as local temp table */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 3ec6d52..1222594 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -150,7 +150,9 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	/* global temp table is same as local temp table */
+	if (!(index->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		index->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 56b3562..d3443ec 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -587,7 +587,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -640,7 +640,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 3c18db2..b08a1fc 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -62,6 +62,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -431,9 +432,9 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
 	Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
-	/* not every AM requires these to be valid, but heap does */
-	Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
-	Assert(MultiXactIdIsValid(onerel->rd_rel->relminmxid));
+	/* not every AM requires these to be valid, but regular heap does */
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ MultiXactIdIsValid(onerel->rd_rel->relminmxid));
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index f5962f6..045c0ce 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -601,6 +602,10 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 065eb27..8427894 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6433,6 +6433,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/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 5eaca27..e5002aa 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -231,7 +231,8 @@ Boot_CreateStmt:
 												   mapped_relation,
 												   true,
 												   &relfrozenxid,
-												   &relminmxid);
+												   &relminmxid,
+												   false);
 						elog(DEBUG4, "bootstrap relation created");
 					}
 					else
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 9499bb3..ae47364 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -43,6 +43,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 7d6acae..3be8d63 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -393,7 +393,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/* global temp table is same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 632c058..d3a9bfb 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -304,7 +306,8 @@ heap_create(const char *relname,
 			bool mapped_relation,
 			bool allow_system_table_mods,
 			TransactionId *relfrozenxid,
-			MultiXactId *relminmxid)
+			MultiXactId *relminmxid,
+			bool skip_create_storage)
 {
 	bool		create_storage;
 	Relation	rel;
@@ -404,6 +407,9 @@ heap_create(const char *relname,
 									 relpersistence,
 									 relkind);
 
+	if (skip_create_storage)
+		create_storage = false;
+
 	/*
 	 * Have the storage manager create the relation's disk file, if needed.
 	 *
@@ -427,7 +433,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -959,6 +965,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -997,8 +1004,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;
@@ -1261,7 +1278,8 @@ heap_create_with_catalog(const char *relname,
 							   mapped_relation,
 							   allow_system_table_mods,
 							   &relfrozenxid,
-							   &relminmxid);
+							   &relminmxid,
+							   false);
 
 	Assert(relid == RelationGetRelid(new_rel_desc));
 
@@ -1360,6 +1378,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1940,6 +1959,14 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/* We allow to drop global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			elog(ERROR, "cannot drop global temp table %s when other backend attached it.",
+						RelationGetRelationName(rel));
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3167,7 +3194,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3179,7 +3206,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3225,8 +3252,13 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
+
+		/* truncate global temp table only need RowExclusiveLock */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3259,6 +3291,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3267,23 +3300,37 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (!gtt_storage_attached(RelationGetRelid(rel)))
+			return;
+
+		/*
+		 * Truncate global temp table only need RowExclusiveLock
+		 */
+		lockmode = RowExclusiveLock;
+	}
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(rel, 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 7cfbdd5..543deef 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -53,6 +53,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -727,6 +728,11 @@ index_create(Relation heapRelation,
 	char		relkind;
 	TransactionId relfrozenxid;
 	MultiXactId relminmxid;
+	bool		skip_create_storage = false;
+
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation) &&
+		!gtt_storage_attached(RelationGetRelid(heapRelation)))
+		skip_create_storage = true;
 
 	/* constraint flags can only be set when a constraint is requested */
 	Assert((constr_flags == 0) ||
@@ -880,6 +886,19 @@ index_create(Relation heapRelation,
 						indexRelationName, RelationGetRelationName(heapRelation))));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temp table use concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot reindex global temporary tables concurrently")));
+
+		/* if global temp table not init storage, then skip build index */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -934,7 +953,8 @@ index_create(Relation heapRelation,
 								mapped_relation,
 								allow_system_table_mods,
 								&relfrozenxid,
-								&relminmxid);
+								&relminmxid,
+								skip_create_storage);
 
 	Assert(relfrozenxid == InvalidTransactionId);
 	Assert(relminmxid == InvalidMultiXactId);
@@ -2040,7 +2060,8 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 * lock (see comments in RemoveRelations), and a non-concurrent DROP is
 	 * more efficient.
 	 */
-	Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+	Assert(!(get_rel_persistence(indexId) == RELPERSISTENCE_TEMP ||
+			 get_rel_persistence(indexId) == RELPERSISTENCE_GLOBAL_TEMP) ||
 		   (!concurrent && !concurrent_lock_mode));
 
 	/*
@@ -2072,6 +2093,14 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/* We allow to drop index on global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+			elog(ERROR, "cannot drop index %s on global temp table %s when other backend attached it.",
+						RelationGetRelationName(userIndexRelation), RelationGetRelationName(userHeapRelation));
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2680,6 +2709,11 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	/* update index stats into localhash and rel_rd_rel for global temp table */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		is_gtt = true;
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2765,21 +2799,35 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
+		if (is_gtt)
+			rel->rd_rel->relpages = (int32) relpages;
+		else if (rd_rel->relpages != (int32) relpages)
 		{
 			rd_rel->relpages = (int32) relpages;
 			dirty = true;
 		}
-		if (rd_rel->reltuples != (float4) reltuples)
+
+		if (is_gtt)
+			rel->rd_rel->reltuples = (float4) reltuples;
+		else if (rd_rel->reltuples != (float4) reltuples)
 		{
 			rd_rel->reltuples = (float4) reltuples;
 			dirty = true;
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+
+		if (is_gtt)
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+		else if (rd_rel->relallvisible != (int32) relallvisible)
 		{
 			rd_rel->relallvisible = (int32) relallvisible;
 			dirty = true;
 		}
+
+		if (is_gtt)
+		{
+			up_gtt_relstats(rel, relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
+		}
 	}
 
 	/*
@@ -2893,6 +2941,15 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, index, val);
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			gtt_force_enable_index(indexRelation);
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3448,6 +3505,16 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	 * we only need to be sure no schema or data changes are going on.
 	 */
 	heapId = IndexGetRelation(indexId, false);
+
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		!gtt_storage_attached(indexId))
+	{
+		/* Correction status flag, and return */
+		SetReindexProcessing(heapId, indexId);
+		ResetReindexProcessing();
+		return;
+	}
+
 	heapRelation = table_open(heapId, ShareLock);
 
 	if (progress)
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 2ec2301..1b6061d 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -655,6 +655,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		/* global temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp relations in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index d713d5c..afcf004 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,6 +27,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -61,6 +62,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			relOid;			/* InvalidOid if not a global temp rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -115,7 +117,7 @@ AddPendingSync(const RelFileNode *rnode)
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -127,6 +129,8 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		/* global temp table use same storage strategy as local temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -154,6 +158,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->relOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
@@ -165,6 +170,13 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		AddPendingSync(&rnode);
 	}
 
+	/* remember global temp table storage info to localhash */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel)
+	{
+		pending->relOid = RelationGetRelid(rel);
+		remember_gtt_storage_info(rnode, rel);
+	}
+
 	return srel;
 }
 
@@ -201,11 +213,15 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->relOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->relOid = RelationGetRelid(rel);
+
 	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
@@ -602,6 +618,7 @@ smgrDoPendingDeletes(bool isCommit)
 				i = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -631,14 +648,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->relOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -651,9 +672,18 @@ smgrDoPendingDeletes(bool isCommit)
 		smgrdounlinkall(srels, nrels, false);
 
 		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* clean global temp table flags when transaction commit or rollback */
+			if (SmgrIsTemp(srels[i]) &&
+				reloids[i] != InvalidOid &&
+				gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..451cf82
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1491 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  code to create and destroy physical storage for global temparary table
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_relstats_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+int		vacuum_gtt_defer_check_age = 0;
+
+typedef struct gtt_ctl_data
+{
+	LWLock			lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+	Oid			relfilenode;
+	Oid			spcnode;
+
+	/* pg_class stat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+
+	/* pg_statistic */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+
+	Oid			oldrelid;			/* remember the source of relid, before the switch relfilenode. */
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_reset_statistics(gtt_local_hash_entry *entry);
+static void gtt_free_statistics(gtt_local_hash_entry *entry);
+static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok);
+static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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();
+	}
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(gtt_fnode);
+	info.entrysize = action_gtt_shared_hash_entry_size();
+	active_gtt_shared_hash =
+		ShmemInitHash("active gtt shared hash",
+						gtt_shared_ctl->max_entry,
+						gtt_shared_ctl->max_entry,
+						&info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			found;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *)&(fnode), 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_global_temporary_table.")));
+	}
+
+	if (found == false)
+	{
+		int			wordnum;
+
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode				fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	if (!skiplock)
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		if (!skiplock)
+			LWLockRelease(&gtt_shared_ctl->lock);
+
+		if (isCommit)
+			elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+	bms_del_member(entry->map, MyBackendId);
+
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+
+	if (!skiplock)
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+	return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	Bitmapset	*map_copy = NULL;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool		in_use = false;
+	int			num_use = 0;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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;
+	gtt_relfilenode			*new_node = NULL;
+	Oid						relid = RelationGetRelid(rel);
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		/* First time through: initialize the hash table */
+		HASHCTL		ctl;
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_relstats_context =
+			AllocSetContextCreate(CacheMemoryContext,
+							"gtt relstats context",
+							ALLOCSET_DEFAULT_SIZES);
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+	entry = gtt_search_by_relid(relid, true);
+	if (!entry)
+	{
+		bool		found = false;
+		int 		natts = 0;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash,
+						(void *) &relid, HASH_ENTER, &found);
+
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+		entry->natts = 0;
+		entry->attnum = NULL;
+		entry->att_stat_tups = NULL;
+		entry->oldrelid = InvalidOid;
+
+		natts = RelationGetNumberOfAttributes(rel);
+		entry->attnum = palloc0(sizeof(int) * natts);
+		entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+		entry->natts = natts;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			gtt_storage_checkin(relid);
+		}
+	}
+
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* only heap contain transaction information */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	gtt_reset_statistics(entry);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	gtt_relfilenode *d_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+		return;
+	}
+
+	d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+		else if (entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+			gtt_relfilenode *gttnode2 = NULL;
+
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+			Assert(gttnode2->relfilenode == rnode.relNode);
+			Assert(list_length(entry->relfilenode_list) == 1);
+			/* rollback switch relfilenode */
+			gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+									   entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+									   false);
+			/* clean up footprint */
+			entry2->oldrelid = InvalidOid;
+			d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+			Assert(d_rnode);
+		}
+		else
+		{
+			if (entry->relfilenode_list == NIL)
+			{
+				if (entry->relkind == RELKIND_RELATION ||
+					entry->relkind == RELKIND_SEQUENCE)
+					gtt_storage_checkout(relid, false, isCommit);
+
+				gtt_free_statistics(entry);
+				hash_search(gtt_storage_local_hash,
+						(void *) &(relid), HASH_REMOVE, NULL);
+			}
+
+			return;
+		}
+	}
+
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	pfree(d_rnode);
+	if (entry->relfilenode_list == NIL)
+	{
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(relid, false, isCommit);
+
+		if (isCommit && entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			/* clean up footprint */
+			entry2->oldrelid = InvalidOid;
+		}
+
+		gtt_free_statistics(entry);
+		hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_REMOVE, NULL);
+	}
+	else
+		gtt_reset_statistics(entry);
+
+	return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool found = false;
+	gtt_local_hash_entry *entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry)
+		found = true;
+
+	return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS				status;
+	gtt_local_hash_entry		*entry;
+	SMgrRelation	*srels = NULL;
+	Oid				*relids = NULL;
+	char			*relkinds = NULL;
+	int			nrels = 0,
+				nfiles = 0,
+				maxrels = 0,
+				maxfiles = 0,
+				i = 0;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel;
+			RelFileNode 	rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel = smgropen(rnode, MyBackendId);
+
+			if (maxfiles == 0)
+			{
+				maxfiles = 32;
+				srels = palloc(sizeof(SMgrRelation) * maxfiles);
+			}
+			else if (maxfiles <= nfiles)
+			{
+				maxfiles *= 2;
+				srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+			}
+
+			srels[nfiles++] = srel;
+		}
+
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			relids  = palloc(sizeof(Oid) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			relids  = repalloc(relids , sizeof(Oid) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		relkinds[nrels] = entry->relkind;
+		relids[nrels] = entry->relid;
+		nrels++;
+	}
+
+	if (nfiles > 0)
+	{
+		smgrdounlinkall(srels, nfiles, false);
+		for (i = 0; i < nfiles; i++)
+			smgrclose(srels[i]);
+
+		pfree(srels);
+	}
+
+	if (nrels)
+	{
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+		for (i = 0; i < nrels; i++)
+		{
+			if (relkinds[i] == RELKIND_RELATION ||
+				relkinds[i] == RELKIND_SEQUENCE)
+				gtt_storage_checkout(relids[i], true, false);
+		}
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+		pfree(relids);
+		pfree(relkinds);
+	}
+
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+	return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible) 
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+					BlockNumber num_pages,
+					double num_tuples,
+					BlockNumber num_all_visible_pages,
+					TransactionId relfrozenxid,
+					TransactionId relminmxid)
+{
+	Oid		relid = RelationGetRelid(relation);
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (num_pages >= 0 &&
+		gtt_rnode->relpages != (int32)num_pages)
+		gtt_rnode->relpages = (int32)num_pages;
+
+	if (num_tuples >= 0 &&
+		gtt_rnode->reltuples != (float4)num_tuples)
+		gtt_rnode->reltuples = (float4)num_tuples;
+
+	/* only heap contain transaction information and relallvisible */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (gtt_rnode->relallvisible >= 0 &&
+			gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+		{
+			gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+		}
+
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNewTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible) 
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	MemoryContext			oldcontext;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return;
+
+	if (entry->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	Assert(entry->relid == reloid);
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == 0)
+		{
+			entry->attnum[i] = attnum;
+			break;
+		}
+		else if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < entry->natts);
+	Assert(entry->att_stat_tups[i] == NULL);
+	entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return NULL;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			return entry->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int			i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	HeapTuple	tuple;
+	Relation	rel = NULL;
+	int		attnum = PG_GETARG_INT32(1);
+	Oid		reloid = PG_GETARG_OID(0);
+	char		rel_persistence;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	  tupdesc;
+	MemoryContext oldcontext;
+	Tuplestorestate *tupstore;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+
+	relation_close(rel, NoLock);
+	relation_close(pg_tatistic, AccessShareLock);
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate	*tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Oid			relnode = 0;
+	Relation	rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[6];
+		bool	isnull[6];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(reloid);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(RelationGetRelid(index)));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid relOid = index->rd_index->indrelid;
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	if (gtt_storage_attached(relOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int			i;
+	Oid			toastrelid;
+
+	if (!(operation == CMD_UTILITY || operation == CMD_INSERT))
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+
+		index_build(relation, index, info, true, false);
+	}
+
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid 		indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo 	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+static void
+gtt_reset_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+
+		entry->attnum[i] = 0;
+	}
+
+	return;
+}
+
+static void
+gtt_free_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (entry->attnum)
+		pfree(entry->attnum);
+
+	if (entry->att_stat_tups)
+		pfree(entry->att_stat_tups);
+
+	return;
+}
+
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
+void
+gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint)
+{
+	gtt_local_hash_entry	*entry1;
+	gtt_local_hash_entry	*entry2;
+	gtt_relfilenode			*gtt_rnode1 = NULL;
+	gtt_relfilenode			*gtt_rnode2 = NULL;
+	MemoryContext			oldcontext;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry1 = gtt_search_by_relid(rel1, false);
+	gtt_rnode1 = gtt_search_relfilenode(entry1, relfilenode1, false);
+
+	entry2 = gtt_search_by_relid(rel2, false);
+	gtt_rnode2 = gtt_search_relfilenode(entry2, relfilenode2, false);
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	entry1->relfilenode_list = list_delete_ptr(entry1->relfilenode_list, gtt_rnode1);
+	entry2->relfilenode_list = lappend(entry2->relfilenode_list, gtt_rnode1);
+
+	entry2->relfilenode_list = list_delete_ptr(entry2->relfilenode_list, gtt_rnode2);
+	entry1->relfilenode_list = lappend(entry1->relfilenode_list, gtt_rnode2);
+	MemoryContextSwitchTo(oldcontext);
+
+	if (footprint)
+	{
+		entry1->oldrelid = rel2;
+		entry2->oldrelid = rel1;
+	}
+
+	return;
+}
+
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok)
+{
+	gtt_relfilenode			*rnode = NULL;
+	ListCell				*lc;
+
+	Assert(entry);
+
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == relfilenode)
+		{
+			rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (!missing_ok && rnode == NULL)
+		elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+	return rnode;
+}
+
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry == NULL && !missing_ok)
+		elog(ERROR, "relid %u not found in local hash", relid);
+
+	return entry;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index d406ea8..88be041 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM 
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 924ef37..db12eef 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -184,6 +185,13 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -586,14 +594,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 		 * pg_statistic for columns we didn't process, we leave them alone.)
 		 */
 		update_attstats(RelationGetRelid(onerel), inh,
-						attr_cnt, vacattrstats);
+						attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
 
 		for (ind = 0; ind < nindexes; ind++)
 		{
 			AnlIndexData *thisdata = &indexdata[ind];
 
 			update_attstats(RelationGetRelid(Irel[ind]), false,
-							thisdata->attr_cnt, thisdata->vacattrstats);
+							thisdata->attr_cnt, thisdata->vacattrstats,
+							RelationGetRelPersistence(Irel[ind]));
 		}
 
 		/*
@@ -1456,7 +1465,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
  *		by taking a self-exclusive lock on the relation in analyze_rel().
  */
 static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
 {
 	Relation	sd;
 	int			attno;
@@ -1558,31 +1567,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/* Update column statistic to localhash, not catalog */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 04d12a7..42c82c8 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/toasting.h"
 #include "commands/cluster.h"
 #include "commands/progress.h"
@@ -72,6 +73,12 @@ static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
 							bool verbose, bool *pSwapToastByContent,
 							TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
 static List *get_tables_to_cluster(MemoryContext cluster_context);
+static void gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables);
 
 
 /*---------------------------------------------------------------------------
@@ -366,6 +373,14 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap) &&
+		!gtt_storage_attached(RelationGetRelid(OldHeap)))
+	{
+		relation_close(OldHeap, AccessExclusiveLock);
+		pgstat_progress_end_command();
+		return;
+	}
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
@@ -750,6 +765,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	BlockNumber num_pages;
 	int			elevel = verbose ? INFO : DEBUG2;
 	PGRUsage	ru0;
+	bool		is_gtt = false;
+	uint32		gtt_relfrozenxid = 0;
+	uint32		gtt_relminmxid = 0;
 
 	pg_rusage_init(&ru0);
 
@@ -763,6 +781,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	else
 		OldIndex = NULL;
 
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		is_gtt = true;
+
 	/*
 	 * Their tuple descriptors should be exactly alike, but here we only need
 	 * assume that they have the same number of columns.
@@ -830,20 +851,37 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 						  &OldestXmin, &FreezeXid, NULL, &MultiXactCutoff,
 						  NULL);
 
-	/*
-	 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
-	 * backwards, so take the max.
-	 */
-	if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
-		TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
-		FreezeXid = OldHeap->rd_rel->relfrozenxid;
+	if (is_gtt)
+	{
+		get_gtt_relstats(OIDOldHeap,
+					NULL, NULL, NULL,
+					&gtt_relfrozenxid, &gtt_relminmxid);
 
-	/*
-	 * MultiXactCutoff, similarly, shouldn't go backwards either.
-	 */
-	if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
-		MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
-		MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+		if (TransactionIdIsValid(gtt_relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, gtt_relfrozenxid))
+			FreezeXid = gtt_relfrozenxid;
+
+		if (MultiXactIdIsValid(gtt_relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, gtt_relminmxid))
+			MultiXactCutoff = gtt_relminmxid;
+	}
+	else
+	{
+		/*
+		 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
+		 * backwards, so take the max.
+		 */
+		if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
+			FreezeXid = OldHeap->rd_rel->relfrozenxid;
+
+		/*
+		 * MultiXactCutoff, similarly, shouldn't go backwards either.
+		 */
+		if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
+			MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+	}
 
 	/*
 	 * Decide whether to use an indexscan or seqscan-and-optional-sort to scan
@@ -911,6 +949,12 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	table_close(OldHeap, NoLock);
 	table_close(NewHeap, NoLock);
 
+	if (is_gtt)
+	{
+		CommandCounterIncrement();
+		return;
+	}
+
 	/* Update pg_class to reflect the correct values of pages and tuples. */
 	relRelation = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1346,10 +1390,21 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 	 * Swap the contents of the heap relations (including any toast tables).
 	 * Also set old heap's relfrozenxid to frozenXid.
 	 */
-	swap_relation_files(OIDOldHeap, OIDNewHeap,
+	if (newrelpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		Assert(!is_system_catalog);
+		gtt_swap_relation_files(OIDOldHeap, OIDNewHeap,
+								(OIDOldHeap == RelationRelationId),
+								swap_toast_by_content, is_internal,
+								frozenXid, cutoffMulti, mapped_tables);
+	}
+	else
+	{
+		swap_relation_files(OIDOldHeap, OIDNewHeap,
 						(OIDOldHeap == RelationRelationId),
 						swap_toast_by_content, is_internal,
 						frozenXid, cutoffMulti, mapped_tables);
+	}
 
 	/*
 	 * If it's a system catalog, queue a sinval message to flush all catcaches
@@ -1557,3 +1612,141 @@ get_tables_to_cluster(MemoryContext cluster_context)
 
 	return rvs;
 }
+
+static void
+gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables)
+{
+	Relation	relRelation;
+	Oid			relfilenode1,
+				relfilenode2;
+	Relation	rel1;
+	Relation	rel2;
+
+	relRelation = table_open(RelationRelationId, RowExclusiveLock);
+
+	rel1 = relation_open(r1, AccessExclusiveLock);
+	rel2 = relation_open(r2, AccessExclusiveLock);
+
+	relfilenode1 = gtt_fetch_current_relfilenode(r1);
+	relfilenode2 = gtt_fetch_current_relfilenode(r2);
+
+	Assert(OidIsValid(relfilenode1) && OidIsValid(relfilenode2));
+	gtt_switch_rel_relfilenode(r1, relfilenode1, r2, relfilenode2, true);
+
+	CacheInvalidateRelcache(rel1);
+	CacheInvalidateRelcache(rel2);
+
+	InvokeObjectPostAlterHookArg(RelationRelationId, r1, 0,
+								 InvalidOid, is_internal);
+	InvokeObjectPostAlterHookArg(RelationRelationId, r2, 0,
+								 InvalidOid, true);
+
+	if (rel1->rd_rel->reltoastrelid || rel2->rd_rel->reltoastrelid)
+	{
+		if (swap_toast_by_content)
+		{
+			if (rel1->rd_rel->reltoastrelid && rel2->rd_rel->reltoastrelid)
+			{
+				gtt_swap_relation_files(rel1->rd_rel->reltoastrelid,
+									rel2->rd_rel->reltoastrelid,
+									target_is_pg_class,
+									swap_toast_by_content,
+									is_internal,
+									frozenXid,
+									cutoffMulti,
+									mapped_tables);
+			}
+			else
+				elog(ERROR, "cannot swap toast files by content when there's only one");
+		}
+		else
+		{
+			ObjectAddress baseobject,
+						toastobject;
+			long		count;
+
+			if (IsSystemRelation(rel1))
+				elog(ERROR, "cannot swap toast files by links for system catalogs");
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel1->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel2->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+
+			/* Register new dependencies */
+			baseobject.classId = RelationRelationId;
+			baseobject.objectSubId = 0;
+			toastobject.classId = RelationRelationId;
+			toastobject.objectSubId = 0;
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r1;
+				toastobject.objectId = rel1->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r2;
+				toastobject.objectId = rel2->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+		}
+	}
+
+	if (swap_toast_by_content &&
+		rel1->rd_rel->relkind == RELKIND_TOASTVALUE &&
+		rel2->rd_rel->relkind == RELKIND_TOASTVALUE)
+	{
+		Oid			toastIndex1,
+					toastIndex2;
+
+		/* Get valid index for each relation */
+		toastIndex1 = toast_get_valid_index(r1,
+											AccessExclusiveLock);
+		toastIndex2 = toast_get_valid_index(r2,
+											AccessExclusiveLock);
+
+		gtt_swap_relation_files(toastIndex1,
+							toastIndex2,
+							target_is_pg_class,
+							swap_toast_by_content,
+							is_internal,
+							InvalidTransactionId,
+							InvalidMultiXactId,
+							mapped_tables);
+	}
+
+	relation_close(rel1, NoLock);
+	relation_close(rel2, NoLock);
+
+	table_close(relRelation, RowExclusiveLock);
+
+	RelationCloseSmgrByOid(r1);
+	RelationCloseSmgrByOid(r2);
+
+	CommandCounterIncrement();
+}
+
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index ac07f75..655184d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -27,6 +27,7 @@
 #include "catalog/dependency.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -1063,7 +1064,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, stmt->filename, stmt->is_program,
@@ -2794,6 +2795,8 @@ CopyFrom(CopyState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	estate->es_result_relations = resultRelInfo;
 	estate->es_num_result_relations = 1;
 	estate->es_result_relation_info = resultRelInfo;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 2baca12..5338db9 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -521,6 +521,7 @@ DefineIndex(Oid relationId,
 	Snapshot	snapshot;
 	int			save_nestlevel = -1;
 	int			i;
+	char		rel_persistence;
 
 	/*
 	 * Some callers need us to run with an empty default_tablespace; this is a
@@ -542,7 +543,9 @@ DefineIndex(Oid relationId,
 	 * is more efficient.  Do this before any use of the concurrent option is
 	 * done.
 	 */
-	if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+	rel_persistence = get_rel_persistence(relationId);
+	if (stmt->concurrent &&
+		!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		concurrent = true;
 	else
 		concurrent = false;
@@ -2460,7 +2463,8 @@ ReindexIndex(RangeVar *indexRelation, int options, bool concurrent)
 	persistence = irel->rd_rel->relpersistence;
 	index_close(irel, NoLock);
 
-	if (concurrent && persistence != RELPERSISTENCE_TEMP)
+	if (concurrent &&
+		!(persistence == RELPERSISTENCE_TEMP || persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		ReindexRelationConcurrently(indOid, options);
 	else
 		reindex_index(indOid, false, persistence,
@@ -2546,6 +2550,7 @@ ReindexTable(RangeVar *relation, int options, bool concurrent)
 {
 	Oid			heapOid;
 	bool		result;
+	char		rel_persistence;
 
 	/*
 	 * The lock level used here should match reindex_relation().
@@ -2560,7 +2565,9 @@ ReindexTable(RangeVar *relation, int options, bool concurrent)
 									   0,
 									   RangeVarCallbackOwnsTable, NULL);
 
-	if (concurrent && get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+	rel_persistence = get_rel_persistence(heapOid);
+	if (concurrent &&
+		!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 	{
 		result = ReindexRelationConcurrently(heapOid, options);
 
@@ -2761,12 +2768,15 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 	foreach(l, relids)
 	{
 		Oid			relid = lfirst_oid(l);
+		char		rel_persistence;
 
 		StartTransactionCommand();
 		/* functions in indexes may want a snapshot set */
 		PushActiveSnapshot(GetTransactionSnapshot());
 
-		if (concurrent && get_rel_persistence(relid) != RELPERSISTENCE_TEMP)
+		rel_persistence = get_rel_persistence(relid);
+		if (concurrent &&
+			!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		{
 			(void) ReindexRelationConcurrently(relid, options);
 			/* ReindexRelationConcurrently() does the verbose output */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..a45863a 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -94,7 +96,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -222,7 +225,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* now initialize the sequence's data */
 	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -327,7 +330,7 @@ ResetSequence(Oid seq_relid)
 	/*
 	 * Insert the modified tuple into the new storage file.
 	 */
-	fill_seq_with_data(seq_rel, tuple);
+	fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -340,17 +343,21 @@ ResetSequence(Oid seq_relid)
  * Initialize a sequence's relation with the specified tuple as content
  */
 static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 	sequence_magic *sm;
 	OffsetNumber offnum;
+	bool lockBuffer = false;
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ReadBuffer(rel, P_NEW);
-	Assert(BufferGetBlockNumber(buf) == 0);
+	if (buf == InvalidBuffer)
+	{
+		buf = ReadBuffer(rel, P_NEW);
+		Assert(BufferGetBlockNumber(buf) == 0);
+		lockBuffer = true;
+	}
 
 	page = BufferGetPage(buf);
 
@@ -360,7 +367,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* Now insert sequence tuple */
 
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+	if (lockBuffer)
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +418,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	END_CRIT_SECTION();
 
-	UnlockReleaseBuffer(buf);
+	if (lockBuffer)
+		UnlockReleaseBuffer(buf);
 }
 
 /*
@@ -451,6 +460,13 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 
 	init_sequence(relid, &elm, &seqrel);
 
+	if (RELATION_IS_GLOBAL_TEMP(seqrel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(seqrel)))
+			elog(ERROR, "cannot alter global temp sequence %s when other backend attached it",
+						RelationGetRelationName(seqrel));
+	}
+
 	rel = table_open(SequenceRelationId, RowExclusiveLock);
 	seqtuple = SearchSysCacheCopy1(SEQRELID,
 								   ObjectIdGetDatum(relid));
@@ -502,7 +518,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * Insert the modified tuple into the new storage file.
 		 */
-		fill_seq_with_data(seqrel, newdatatuple);
+		fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
 	}
 
 	/* process OWNED BY if given */
@@ -611,7 +627,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +952,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1154,6 +1170,13 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1954,3 +1977,46 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+void
+gtt_init_seq(Relation rel)
+{
+	/* Initialize sequence for global temporary tables */
+	Datum		value[SEQ_COL_LASTCOL] = {0};
+	bool		null[SEQ_COL_LASTCOL] = {false};
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5745cd6..ad6bbc1 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -556,6 +557,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 								  Relation partitionTbl);
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static OnCommitAction gtt_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -601,6 +603,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -611,8 +614,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")));
@@ -642,7 +647,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	/* global temp table same as local temp table */
+	if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+		 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -743,6 +750,55 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* global temp table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* check parent table*/
+		if (inheritOids)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			table_close(relation, NoLock);
+		}
+
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temp table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1330,6 +1386,7 @@ RemoveRelations(DropStmt *drop)
 		Oid			relOid;
 		ObjectAddress obj;
 		struct DropRelationCallbackState state;
+		char		rel_persistence;
 
 		/*
 		 * These next few steps are a great deal like relation_openrv, but we
@@ -1363,8 +1420,9 @@ RemoveRelations(DropStmt *drop)
 		 * Decide if concurrent mode needs to be used here or not.  The
 		 * relation persistence cannot be known without its OID.
 		 */
+		rel_persistence = get_rel_persistence(relOid);
 		if (drop->concurrent &&
-			get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+			!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		{
 			Assert(list_length(drop->objects) == 1 &&
 				   drop->removeType == OBJECT_INDEX);
@@ -1819,6 +1877,10 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
 		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 			continue;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			continue;
+
 		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
@@ -3570,6 +3632,14 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			elog(ERROR, "cannot alter global temp table %s when other backend attached it.",
+						RelationGetRelationName(rel));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -4847,6 +4917,38 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot rewrite temporary tables of other sessions")));
 
+			if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+			{
+				if (tab->chgPersistence)
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot change global temporary table persistence setting")));
+
+				/* gtt may not attached, create it */
+				if(!gtt_storage_attached(tab->relid))
+				{
+					ResultRelInfo *resultRelInfo;
+					MemoryContext oldcontext;
+					MemoryContext ctx_alter_gtt;
+
+					ctx_alter_gtt = AllocSetContextCreate(CurrentMemoryContext,
+											"gtt alter table", ALLOCSET_DEFAULT_SIZES);
+
+					oldcontext = MemoryContextSwitchTo(ctx_alter_gtt);
+					resultRelInfo = makeNode(ResultRelInfo);
+					InitResultRelInfo(resultRelInfo, OldHeap,
+									1, NULL, 0);
+					if (resultRelInfo->ri_RelationDesc->rd_rel->relhasindex &&
+						resultRelInfo->ri_IndexRelationDescs == NULL)
+						ExecOpenIndices(resultRelInfo, false);
+
+					init_gtt_storage(CMD_UTILITY, resultRelInfo);
+					ExecCloseIndices(resultRelInfo);
+					MemoryContextSwitchTo(oldcontext);
+					MemoryContextDelete(ctx_alter_gtt);
+				}
+			}
+
 			/*
 			 * Select destination tablespace (same as original unless user
 			 * requested a change)
@@ -8184,6 +8286,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -12740,6 +12848,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -12942,6 +13053,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temp table");
+
 	/*
 	 * No work if no change in tablespace.
 	 */
@@ -13316,7 +13430,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,
@@ -14723,7 +14837,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",
@@ -17379,3 +17495,36 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
 		table_close(rel, NoLock);
 	}
 }
+
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			bool	res = false;
+			char	*sval = defGetString(def);
+
+			if (!parse_bool(sval, &res))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
+
+			if (res)
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 5a110ed..02e6f1e 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"
@@ -1215,6 +1216,17 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	 /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		is_gtt = true;
+		up_gtt_relstats(relation,
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1228,17 +1240,26 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relpages = (int32) num_pages;
+	else if (pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (is_gtt)
+		relation->rd_rel->reltuples = (float4) num_tuples;
+	else if (pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	else if (pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1283,7 +1304,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1294,7 +1316,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1392,6 +1415,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
@@ -1449,6 +1476,42 @@ 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	safe_age;
+		TransactionId	oldest_gtt_frozenxid =
+			list_all_session_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid))
+		{
+			safe_age = oldest_gtt_frozenxid + vacuum_gtt_defer_check_age;
+			if (safe_age < FirstNormalTransactionId)
+				safe_age += FirstNormalTransactionId;
+
+			/*
+			 * We tolerate that the minimum age of gtt is less than
+			 * the minimum age of conventional tables, otherwise it will
+			 * throw warning message.
+			 */
+			if (TransactionIdIsNormal(safe_age) &&
+				TransactionIdPrecedes(safe_age, newFrozenXid))
+			{
+				ereport(WARNING,
+					(errmsg("global temp table oldest relfrozenxid %u is the oldest in the entire db",
+							oldest_gtt_frozenxid),
+					 errdetail("The oldest relfrozenxid in pg_class is %u", newFrozenXid),
+					 errhint("If they differ greatly, please consider cleaning up the data in global temp table.")));
+			}
+
+			if (TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+				newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1776,6 +1839,15 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6e65103..04706ee 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -530,6 +530,11 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 4fdffad..0aa7559 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index fb6ce49..06d9237 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
@@ -545,6 +546,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	init_gtt_storage(mtstate->operation, leaf_part_rri);
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 20a4c47..e9f6dcd 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,6 +42,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -2379,6 +2380,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			ExecOpenIndices(resultRelInfo,
 							node->onConflictAction != ONCONFLICT_NONE);
 
+		init_gtt_storage(operation, resultRelInfo);
+
 		/*
 		 * If this is an UPDATE and a BEFORE UPDATE trigger is present, the
 		 * trigger itself might modify the partition-key values. So arrange
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 255f56b..d94d6f0 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 e664eb1..e03bfa0 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6419,7 +6419,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
+	/* global temp table is same as local temp table */
 	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 2554502..04fb5d4 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -28,6 +28,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temp table */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index eee9c33..1891c53 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2594,6 +2594,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 3c78f2d..edb8d9f 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3302,17 +3302,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; }
@@ -11648,19 +11642,13 @@ OptTempTableName:
 				}
 			| GLOBAL TEMPORARY opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED opt_table qualified_name
 				{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b875a50..123945b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 75c122f..97213ed 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
@@ -3108,6 +3111,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		cxt.isforeign = false;
 	}
 	cxt.relation = stmt->relation;
+	cxt.relation->relpersistence = RelationGetRelPersistence(rel);
 	cxt.rel = rel;
 	cxt.inhRelations = NIL;
 	cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 7e97ffa..1f2ac58 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2089,6 +2089,11 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* autovacuum skip vacuum global temp table */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2155,7 +2160,9 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		/* autovacuum skip vacuum global temp table */
+		if (classForm->relpersistence == RELPERSISTENCE_TEMP || 
+			classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index f9980cf..406a25c 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -53,6 +54,7 @@
 #include "utils/rel.h"
 #include "utils/resowner_private.h"
 #include "utils/timestamp.h"
+#include "utils/guc.h"
 
 
 /* Note: these two macros only work on shared buffers, not local ones! */
@@ -2819,6 +2821,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d5..7adb969 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -21,6 +21,7 @@
 #include "access/nbtree.h"
 #include "access/subtrans.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, active_gtt_shared_hash_size());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* global temporary table */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 3630006..0c8ef80 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -63,6 +63,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -4086,3 +4087,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 5aa19d3..b3bc455 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 8406644..0d99321 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -1008,6 +1008,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index cfb0568..5913efc 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -112,6 +112,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -4821,12 +4822,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))
 							{
@@ -4951,15 +4965,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))
 		{
@@ -6341,6 +6367,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;
@@ -6358,6 +6385,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,
@@ -6369,6 +6403,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;
@@ -6384,6 +6420,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,
@@ -7302,6 +7345,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))
@@ -7314,6 +7359,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 =
@@ -7326,6 +7379,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.
@@ -7345,6 +7400,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index a7d63f1..d8a52cb 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -2939,6 +2940,18 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 9f1f11d..aeb2a78 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -65,6 +65,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1133,6 +1134,24 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1187,6 +1206,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			gtt_fix_index_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -1317,7 +1337,17 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+			if (newrelnode != InvalidOid &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2246,6 +2276,8 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		gtt_fix_index_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3472,6 +3504,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3579,28 +3615,34 @@ void
 RelationSetNewRelfilenode(Relation relation, char persistence)
 {
 	Oid			newrelfilenode;
-	Relation	pg_class;
-	HeapTuple	tuple;
+	Relation	pg_class = NULL;
+	HeapTuple	tuple = NULL;
 	Form_pg_class classform;
 	MultiXactId minmulti = InvalidMultiXactId;
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
+	bool		modify_pg_class = !RELATION_IS_GLOBAL_TEMP(relation);
 
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
 
-	/*
-	 * Get a writable copy of the pg_class tuple for the given relation.
-	 */
-	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+	if (modify_pg_class)
+	{
+		/*
+		 * Get a writable copy of the pg_class tuple for the given relation.
+		 */
+		pg_class = table_open(RelationRelationId, RowExclusiveLock);
 
-	tuple = SearchSysCacheCopy1(RELOID,
-								ObjectIdGetDatum(RelationGetRelid(relation)));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "could not find tuple for relation %u",
-			 RelationGetRelid(relation));
-	classform = (Form_pg_class) GETSTRUCT(tuple);
+		tuple = SearchSysCacheCopy1(RELOID,
+									ObjectIdGetDatum(RelationGetRelid(relation)));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u",
+				 RelationGetRelid(relation));
+		classform = (Form_pg_class) GETSTRUCT(tuple);
+	}
+	else
+		memset(&classform, 0, sizeof(classform));
 
 	/*
 	 * Schedule unlinking of the old storage at transaction commit.
@@ -3626,7 +3668,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;
@@ -3646,6 +3688,15 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 			break;
 	}
 
+	if (!modify_pg_class)
+	{
+		Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+		Assert(RELATION_IS_GLOBAL_TEMP(relation));
+		Assert(!RelationIsMapped(relation));
+		relation->rd_node.relNode = relnode;
+		CacheInvalidateRelcache(relation);
+	}
 	/*
 	 * If we're dealing with a mapped index, pg_class.relfilenode doesn't
 	 * change; instead we have to send the update to the relation mapper.
@@ -3655,7 +3706,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	 * possibly-inaccurate values of relpages etc, but those will be fixed up
 	 * later.
 	 */
-	if (RelationIsMapped(relation))
+	else if (RelationIsMapped(relation))
 	{
 		/* This case is only supported for indexes */
 		Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3701,9 +3752,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 		CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 	}
 
-	heap_freetuple(tuple);
+	if (modify_pg_class)
+	{
+		heap_freetuple(tuple);
 
-	table_close(pg_class, RowExclusiveLock);
+		table_close(pg_class, RowExclusiveLock);
+	}
 
 	/*
 	 * Make the pg_class row change or relation map change visible.  This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 5bdc02f..b52b51d 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -37,6 +37,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "commands/prepare.h"
 #include "commands/trigger.h"
@@ -142,6 +143,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temp table feature.
+ * global temp table define can still storage in catalog
+ * just can not use.
+ * num > 0 means database can management num active global temp table.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2070,6 +2083,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."),
@@ -2577,6 +2599,16 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"vacuum_gtt_defer_check_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("The defer check age of GTT, used to check expired data after vacuum."),
+			NULL
+		},
+		&vacuum_gtt_defer_check_age,
+		10000, 0, 1000000,
+		NULL, NULL, NULL
+	},
+
 	/*
 	 * See also CheckRequiredParameterValues() if this parameter changes
 	 */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 5db4f57..018a9bf 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2424,6 +2424,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
 		dopt->no_unlogged_table_data)
 		return;
 
+	/* Don't dump data in global temp table/sequence */
+	if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		return;
+
 	/* Check that the data is not explicitly excluded */
 	if (simple_oid_list_member(&tabledata_exclude_oids,
 							   tbinfo->dobj.catId.oid))
@@ -15777,6 +15781,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
 		char	   *foreign = "";
+		char	   *table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15830,9 +15835,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);
 
@@ -16226,7 +16237,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
-			 tbinfo->relkind == RELKIND_MATVIEW))
+			 tbinfo->relkind == RELKIND_MATVIEW) &&
+			 tbinfo->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
 		{
 			appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
 			appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
@@ -17131,6 +17143,7 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	PQExpBuffer query = createPQExpBuffer();
 	PQExpBuffer delqry = createPQExpBuffer();
 	char	   *qseqname;
+	bool	   global_temp_seq = false;
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
@@ -17140,9 +17153,11 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
-						  "FROM pg_catalog.pg_sequence "
-						  "WHERE seqrelid = '%u'::oid",
+						  "seqcache, seqcycle, c.relpersistence "
+						  "FROM pg_catalog.pg_sequence s, "
+						  "pg_catalog.pg_class c "
+						  "WHERE seqrelid = '%u'::oid "
+						  "and s.seqrelid = c.oid",
 						  tbinfo->dobj.catId.oid);
 	}
 	else if (fout->remoteVersion >= 80400)
@@ -17187,6 +17202,9 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
 
+	if (fout->remoteVersion >= 130000)
+		global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
 	if (strcmp(seqtype, "smallint") == 0)
@@ -17261,9 +17279,13 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	}
 	else
 	{
-		appendPQExpBuffer(query,
-						  "CREATE SEQUENCE %s\n",
-						  fmtQualifiedDumpable(tbinfo));
+		appendPQExpBuffer(query, "CREATE ");
+
+		if (global_temp_seq)
+			appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+		appendPQExpBuffer(query, "SEQUENCE %s\n",
+								  fmtQualifiedDumpable(tbinfo));
 
 		if (strcmp(seqtype, "bigint") != 0)
 			appendPQExpBuffer(query, "    AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 00aef85..9ad71c8 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -84,7 +84,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -157,7 +157,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 7e524ea..371168f 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -441,22 +443,46 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 	 * output, so we have to copy that system table.  It's easiest to do that
 	 * by treating it as a user table.
 	 */
-	snprintf(query + strlen(query), sizeof(query) - strlen(query),
-			 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
-			 "  SELECT c.oid, 0::oid, 0::oid "
-			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
-			 "         ON c.relnamespace = n.oid "
-			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
-	/* exclude possible orphaned temp tables */
-			 "    ((n.nspname !~ '^pg_temp_' AND "
-			 "      n.nspname !~ '^pg_toast_temp_' AND "
-			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
-			 "                        'binary_upgrade', 'pg_toast') AND "
-			 "      c.oid >= %u::pg_catalog.oid) OR "
-			 "     (n.nspname = 'pg_catalog' AND "
-			 "      relname IN ('pg_largeobject') ))), ",
-			 FirstNormalObjectId);
+	if (skip_gtt)
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude global temp tables */
+				 "    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
+	else
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
 
 	/*
 	 * Add a CTE that collects OIDs of toast tables belonging to the tables
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 70194eb..8dd9e66 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -411,7 +411,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -642,7 +642,9 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -653,7 +655,9 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 8b90cef..d155205 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -389,7 +389,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 8dca6d8..6b3f571 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3738,7 +3738,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index f6fd623..ea87bc4 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1023,6 +1023,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2394,6 +2396,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2602,6 +2607,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index cbfdfe2..be39472 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -59,7 +59,8 @@ extern Relation heap_create(const char *relname,
 							bool mapped_relation,
 							bool allow_system_table_mods,
 							TransactionId *relfrozenxid,
-							MultiXactId *relminmxid);
+							MultiXactId *relminmxid,
+							bool skip_create_storage);
 
 extern Oid	heap_create_with_catalog(const char *relname,
 									 Oid relnamespace,
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 78b33b2..4be89f6 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -168,6 +168,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad..c8236a1 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5558,6 +5558,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4388',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4389',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4390',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4391',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 30c38e0..7ff2408 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,7 +22,7 @@
 /* GUC variables */
 extern int	wal_skip_threshold;
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationPreTruncate(Relation rel);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..902375d
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern int		vacuum_gtt_defer_check_age;
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Relation relation,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void gtt_force_enable_index(Relation index);
+extern void gtt_fix_index_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+extern void gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index e2638ab..89a5ce4 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 3f88683..f870e9a 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 8fda8e4..172eae9 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_TBM,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_SXACT,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index ae4f573..69cce92 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -117,6 +117,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId session_gtt_frozenxid;	/* session level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 2819282..363fc33 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -282,6 +282,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 0b5957b..0720a4c 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	struct SMgrRelationData *rd_smgr;	/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -305,6 +305,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -570,11 +571,13 @@ typedef struct ViewOptions
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -582,6 +585,7 @@ typedef struct ViewOptions
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -594,6 +598,14 @@ typedef struct ViewOptions
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is either local temp relation of this session
+ * 		or global temp relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -637,6 +649,17 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..ca2d135
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,14 @@
+reset search_path;
+select pg_sleep(5);
+ pg_sleep 
+----------
+ 
+(1 row)
+
+drop schema gtt cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
+drop cascades to table gtt.gtt_with_seq
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..d6a675a
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,383 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+ n 
+---
+ 9
+(1 row)
+
+commit;
+-- 0 row
+select * from gtt6;
+ n 
+---
+(0 rows)
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temp table
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  could not create global temporary table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR:  insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count 
+-------
+     1
+(1 row)
+
+select count(*) from orders;
+ count 
+-------
+     0
+(1 row)
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  2 |  3
+(2 rows)
+
+commit;
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+select * from gtt_s_2 order by c1;
+ c1 | c2 
+----+----
+  1 |  2
+  2 |  4
+(2 rows)
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 1)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 300)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 33 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test_new
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.gtt6
+drop cascades to table gtt_function.foo
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to sequence gtt_function.seq_1
+drop cascades to table gtt_function.gtt_s_1
+drop cascades to table gtt_function.gtt_s_2
+drop cascades to table gtt_function.gt1
+drop cascades to table gtt_function.gtt_test1
+drop cascades to table gtt_function.gtt_test2
+drop cascades to table gtt_function.gtt_test3
+drop cascades to table gtt_function.gtt_test4
+drop cascades to table gtt_function.gtt_test5
+drop cascades to table gtt_function.gtt_test6
+drop cascades to table gtt_function.gtt_test7
+drop cascades to table gtt_function.gtt_test8
+drop cascades to table gtt_function.gtt_test9
+drop cascades to table gtt_function.gtt_test10
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..0646aae
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,90 @@
+set search_path=gtt,sys;
+select nextval('gtt_with_seq_c2_seq');
+ nextval 
+---------
+       1
+(1 row)
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..2f084be
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,343 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+rollback;
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+commit;
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          114688 |                 614400
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |            49152 |         49152 |                  49152
+(2 rows)
+
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2000
+(1 row)
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+ c1 | c2 
+----+----
+  1 |  1
+(1 row)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..8c0c376
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,10 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..4420fdb
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,80 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+ gtt_stats  | gtt_pkey  |        1 |         0 |             0
+(2 rows)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+reindex table gtt;
+reindex index gtt_pkey;
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+ gtt_stats  | gtt_pkey  |       30 |     10000 |             0
+(2 rows)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index ac31840..55b221e 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1349,6 +1349,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 95f1925..76b2374 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..2c8e586
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,8 @@
+
+
+reset search_path;
+
+select pg_sleep(5);
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..81f0bfc
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,253 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+commit;
+-- 0 row
+select * from gtt6;
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+commit;
+select * from gtt_s_1 order by c1;
+select * from gtt_s_2 order by c1;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d05745e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,44 @@
+
+
+set search_path=gtt,sys;
+
+select nextval('gtt_with_seq_c2_seq');
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..39cca5e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,154 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+rollback;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+commit;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+select count(*) from gtt_t_kenyon;
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..dbe84d1
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,19 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..d61b0ff
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,46 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+reindex table gtt;
+
+reindex index gtt_pkey;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#268Prabhat Sahu
prabhat.sahu@enterprisedb.com
In reply to: 曾文旌 (#267)
Re: [Proposal] Global temporary tables

Thanks Wenjing, for the fix patch for previous issues.
I have verified the issues, now those fix look good to me.
But the below error message is confusing(for gtt2).

postgres=# drop table gtt1;
ERROR: cannot drop global temp table gtt1 when other backend attached it.

postgres=# drop table gtt2;
ERROR: cannot drop index idx2 on global temp table gtt2 when other backend
attached it.

I feel the above error message shown for "DROP TABLE gtt2;" is a bit
confusing(looks similar to DROP INDEX gtt2;).
If possible, can we keep the error message simple as "ERROR: cannot drop
global temp table gtt2 when other backend attached it."?
I mean, without giving extra information for the index attached to that GTT.

On Mon, Apr 27, 2020 at 5:34 PM 曾文旌 <wenjing.zwj@alibaba-inc.com> wrote:

2020年4月27日 下午5:26,Prabhat Sahu <prabhat.sahu@enterprisedb.com> 写道:

Hi Wenjing,

Please check the below scenario shows different error message with "DROP
TABLE gtt;" for gtt with and without index.

*-- Session1:*postgres=# create global temporary table gtt1 (c1 int);
CREATE TABLE
postgres=# create global temporary table gtt2 (c1 int);
CREATE TABLE
postgres=# create index idx2 on gtt2(c1);
CREATE INDEX

*-- Session2:*postgres=# drop table gtt1;
ERROR: can not drop relation gtt1 when other backend attached this global
temp table
postgres=# drop table gtt2;
ERROR: can not drop index gtt2 when other backend attached this global
temp table.

For DROP GTT, we need to drop the index on the table first.
So the indexes on the GTT are checked first.
But the error message needs to be fixed.
Fixed in v32

wenjing

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

#269曾文旌
wenjing.zwj@alibaba-inc.com
In reply to: Prabhat Sahu (#268)
2 attachment(s)
Re: [Proposal] Global temporary tables

2020年4月27日 下午9:48,Prabhat Sahu <prabhat.sahu@enterprisedb.com> 写道:

Thanks Wenjing, for the fix patch for previous issues.
I have verified the issues, now those fix look good to me.
But the below error message is confusing(for gtt2).

postgres=# drop table gtt1;
ERROR: cannot drop global temp table gtt1 when other backend attached it.

postgres=# drop table gtt2;
ERROR: cannot drop index idx2 on global temp table gtt2 when other backend attached it.

I feel the above error message shown for "DROP TABLE gtt2;" is a bit confusing(looks similar to DROP INDEX gtt2;).
If possible, can we keep the error message simple as "ERROR: cannot drop global temp table gtt2 when other backend attached it."?
I mean, without giving extra information for the index attached to that GTT.

Fixed the error message to make the expression more accurate. In v33.

Wenjing

Show quoted text

On Mon, Apr 27, 2020 at 5:34 PM 曾文旌 <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> wrote:

2020年4月27日 下午5:26,Prabhat Sahu <prabhat.sahu@enterprisedb.com <mailto:prabhat.sahu@enterprisedb.com>> 写道:

Hi Wenjing,

Please check the below scenario shows different error message with "DROP TABLE gtt;" for gtt with and without index.
-- Session1:
postgres=# create global temporary table gtt1 (c1 int);
CREATE TABLE
postgres=# create global temporary table gtt2 (c1 int);
CREATE TABLE
postgres=# create index idx2 on gtt2(c1);
CREATE INDEX

-- Session2:
postgres=# drop table gtt1;
ERROR: can not drop relation gtt1 when other backend attached this global temp table
postgres=# drop table gtt2;
ERROR: can not drop index gtt2 when other backend attached this global temp table.

For DROP GTT, we need to drop the index on the table first.
So the indexes on the GTT are checked first.
But the error message needs to be fixed.
Fixed in v32

wenjing

--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

--
With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

Attachments:

global_temporary_table_v33-pg13.patchapplication/octet-stream; name=global_temporary_table_v33-pg13.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 8ccc228..b061de9 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -168,6 +168,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * For global temp table only
+	 * use ShareUpdateExclusiveLock for ensure safety
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temp table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},	
 	/* list terminator */
 	{{NULL}}
 };
@@ -1817,6 +1830,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1961,13 +1976,18 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	/*
 	 * There are no options for partitioned tables yet, but this is able to do
 	 * some validation.
 	 */
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index 765329b..4761fdc 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1011,7 +1011,9 @@ gistproperty(Oid index_oid, int attno,
 XLogRecPtr
 gistGetFakeLSN(Relation rel)
 {
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	/* global temp is same as local temp table */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 3ec6d52..1222594 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -150,7 +150,9 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	/* global temp table is same as local temp table */
+	if (!(index->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		index->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 56b3562..d3443ec 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -587,7 +587,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -640,7 +640,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 3c18db2..b08a1fc 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -62,6 +62,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -431,9 +432,9 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
 	Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
-	/* not every AM requires these to be valid, but heap does */
-	Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
-	Assert(MultiXactIdIsValid(onerel->rd_rel->relminmxid));
+	/* not every AM requires these to be valid, but regular heap does */
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ MultiXactIdIsValid(onerel->rd_rel->relminmxid));
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index f5962f6..045c0ce 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -601,6 +602,10 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 065eb27..8427894 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6433,6 +6433,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/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 5eaca27..e5002aa 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -231,7 +231,8 @@ Boot_CreateStmt:
 												   mapped_relation,
 												   true,
 												   &relfrozenxid,
-												   &relminmxid);
+												   &relminmxid,
+												   false);
 						elog(DEBUG4, "bootstrap relation created");
 					}
 					else
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 9499bb3..ae47364 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -43,6 +43,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 7d6acae..3be8d63 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -393,7 +393,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/* global temp table is same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 632c058..9b43493 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -304,7 +306,8 @@ heap_create(const char *relname,
 			bool mapped_relation,
 			bool allow_system_table_mods,
 			TransactionId *relfrozenxid,
-			MultiXactId *relminmxid)
+			MultiXactId *relminmxid,
+			bool skip_create_storage)
 {
 	bool		create_storage;
 	Relation	rel;
@@ -404,6 +407,9 @@ heap_create(const char *relname,
 									 relpersistence,
 									 relkind);
 
+	if (skip_create_storage)
+		create_storage = false;
+
 	/*
 	 * Have the storage manager create the relation's disk file, if needed.
 	 *
@@ -427,7 +433,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -959,6 +965,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -997,8 +1004,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;
@@ -1261,7 +1278,8 @@ heap_create_with_catalog(const char *relname,
 							   mapped_relation,
 							   allow_system_table_mods,
 							   &relfrozenxid,
-							   &relminmxid);
+							   &relminmxid,
+							   false);
 
 	Assert(relid == RelationGetRelid(new_rel_desc));
 
@@ -1360,6 +1378,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1940,6 +1959,16 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/* We allow to drop global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot drop global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3167,7 +3196,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3179,7 +3208,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3225,8 +3254,13 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
+
+		/* truncate global temp table only need RowExclusiveLock */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3259,6 +3293,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3267,23 +3302,37 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (!gtt_storage_attached(RelationGetRelid(rel)))
+			return;
+
+		/*
+		 * Truncate global temp table only need RowExclusiveLock
+		 */
+		lockmode = RowExclusiveLock;
+	}
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(rel, 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 7cfbdd5..2c09b94 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -53,6 +53,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -727,6 +728,11 @@ index_create(Relation heapRelation,
 	char		relkind;
 	TransactionId relfrozenxid;
 	MultiXactId relminmxid;
+	bool		skip_create_storage = false;
+
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation) &&
+		!gtt_storage_attached(RelationGetRelid(heapRelation)))
+		skip_create_storage = true;
 
 	/* constraint flags can only be set when a constraint is requested */
 	Assert((constr_flags == 0) ||
@@ -880,6 +886,19 @@ index_create(Relation heapRelation,
 						indexRelationName, RelationGetRelationName(heapRelation))));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temp table use concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot reindex global temporary tables concurrently")));
+
+		/* if global temp table not init storage, then skip build index */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -934,7 +953,8 @@ index_create(Relation heapRelation,
 								mapped_relation,
 								allow_system_table_mods,
 								&relfrozenxid,
-								&relminmxid);
+								&relminmxid,
+								skip_create_storage);
 
 	Assert(relfrozenxid == InvalidTransactionId);
 	Assert(relminmxid == InvalidMultiXactId);
@@ -2040,7 +2060,8 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 * lock (see comments in RemoveRelations), and a non-concurrent DROP is
 	 * more efficient.
 	 */
-	Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+	Assert(!(get_rel_persistence(indexId) == RELPERSISTENCE_TEMP ||
+			 get_rel_persistence(indexId) == RELPERSISTENCE_GLOBAL_TEMP) ||
 		   (!concurrent && !concurrent_lock_mode));
 
 	/*
@@ -2072,6 +2093,17 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/* We allow to drop index on global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot drop index %s or global temporary table %s",
+						RelationGetRelationName(userIndexRelation), RelationGetRelationName(userHeapRelation)),
+				 errhint("Because the index is created on the global temporary table and other backend attached it.")));
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2680,6 +2712,11 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	/* update index stats into localhash and rel_rd_rel for global temp table */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		is_gtt = true;
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2765,21 +2802,35 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
+		if (is_gtt)
+			rel->rd_rel->relpages = (int32) relpages;
+		else if (rd_rel->relpages != (int32) relpages)
 		{
 			rd_rel->relpages = (int32) relpages;
 			dirty = true;
 		}
-		if (rd_rel->reltuples != (float4) reltuples)
+
+		if (is_gtt)
+			rel->rd_rel->reltuples = (float4) reltuples;
+		else if (rd_rel->reltuples != (float4) reltuples)
 		{
 			rd_rel->reltuples = (float4) reltuples;
 			dirty = true;
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+
+		if (is_gtt)
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+		else if (rd_rel->relallvisible != (int32) relallvisible)
 		{
 			rd_rel->relallvisible = (int32) relallvisible;
 			dirty = true;
 		}
+
+		if (is_gtt)
+		{
+			up_gtt_relstats(rel, relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
+		}
 	}
 
 	/*
@@ -2893,6 +2944,15 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, index, val);
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			gtt_force_enable_index(indexRelation);
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3448,6 +3508,16 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	 * we only need to be sure no schema or data changes are going on.
 	 */
 	heapId = IndexGetRelation(indexId, false);
+
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		!gtt_storage_attached(indexId))
+	{
+		/* Correction status flag, and return */
+		SetReindexProcessing(heapId, indexId);
+		ResetReindexProcessing();
+		return;
+	}
+
 	heapRelation = table_open(heapId, ShareLock);
 
 	if (progress)
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 2ec2301..1b6061d 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -655,6 +655,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		/* global temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp relations in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index d713d5c..afcf004 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,6 +27,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -61,6 +62,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			relOid;			/* InvalidOid if not a global temp rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -115,7 +117,7 @@ AddPendingSync(const RelFileNode *rnode)
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -127,6 +129,8 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		/* global temp table use same storage strategy as local temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -154,6 +158,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->relOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
@@ -165,6 +170,13 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		AddPendingSync(&rnode);
 	}
 
+	/* remember global temp table storage info to localhash */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel)
+	{
+		pending->relOid = RelationGetRelid(rel);
+		remember_gtt_storage_info(rnode, rel);
+	}
+
 	return srel;
 }
 
@@ -201,11 +213,15 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->relOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->relOid = RelationGetRelid(rel);
+
 	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
@@ -602,6 +618,7 @@ smgrDoPendingDeletes(bool isCommit)
 				i = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -631,14 +648,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->relOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -651,9 +672,18 @@ smgrDoPendingDeletes(bool isCommit)
 		smgrdounlinkall(srels, nrels, false);
 
 		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* clean global temp table flags when transaction commit or rollback */
+			if (SmgrIsTemp(srels[i]) &&
+				reloids[i] != InvalidOid &&
+				gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..451cf82
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1491 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  code to create and destroy physical storage for global temparary table
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_relstats_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+int		vacuum_gtt_defer_check_age = 0;
+
+typedef struct gtt_ctl_data
+{
+	LWLock			lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+	Oid			relfilenode;
+	Oid			spcnode;
+
+	/* pg_class stat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+
+	/* pg_statistic */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+
+	Oid			oldrelid;			/* remember the source of relid, before the switch relfilenode. */
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_reset_statistics(gtt_local_hash_entry *entry);
+static void gtt_free_statistics(gtt_local_hash_entry *entry);
+static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok);
+static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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();
+	}
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(gtt_fnode);
+	info.entrysize = action_gtt_shared_hash_entry_size();
+	active_gtt_shared_hash =
+		ShmemInitHash("active gtt shared hash",
+						gtt_shared_ctl->max_entry,
+						gtt_shared_ctl->max_entry,
+						&info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			found;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *)&(fnode), 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_global_temporary_table.")));
+	}
+
+	if (found == false)
+	{
+		int			wordnum;
+
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode				fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	if (!skiplock)
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		if (!skiplock)
+			LWLockRelease(&gtt_shared_ctl->lock);
+
+		if (isCommit)
+			elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+	bms_del_member(entry->map, MyBackendId);
+
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+
+	if (!skiplock)
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+	return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	Bitmapset	*map_copy = NULL;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool		in_use = false;
+	int			num_use = 0;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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;
+	gtt_relfilenode			*new_node = NULL;
+	Oid						relid = RelationGetRelid(rel);
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		/* First time through: initialize the hash table */
+		HASHCTL		ctl;
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_relstats_context =
+			AllocSetContextCreate(CacheMemoryContext,
+							"gtt relstats context",
+							ALLOCSET_DEFAULT_SIZES);
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+	entry = gtt_search_by_relid(relid, true);
+	if (!entry)
+	{
+		bool		found = false;
+		int 		natts = 0;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash,
+						(void *) &relid, HASH_ENTER, &found);
+
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+		entry->natts = 0;
+		entry->attnum = NULL;
+		entry->att_stat_tups = NULL;
+		entry->oldrelid = InvalidOid;
+
+		natts = RelationGetNumberOfAttributes(rel);
+		entry->attnum = palloc0(sizeof(int) * natts);
+		entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+		entry->natts = natts;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			gtt_storage_checkin(relid);
+		}
+	}
+
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* only heap contain transaction information */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	gtt_reset_statistics(entry);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	gtt_relfilenode *d_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+		return;
+	}
+
+	d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+		else if (entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+			gtt_relfilenode *gttnode2 = NULL;
+
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+			Assert(gttnode2->relfilenode == rnode.relNode);
+			Assert(list_length(entry->relfilenode_list) == 1);
+			/* rollback switch relfilenode */
+			gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+									   entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+									   false);
+			/* clean up footprint */
+			entry2->oldrelid = InvalidOid;
+			d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+			Assert(d_rnode);
+		}
+		else
+		{
+			if (entry->relfilenode_list == NIL)
+			{
+				if (entry->relkind == RELKIND_RELATION ||
+					entry->relkind == RELKIND_SEQUENCE)
+					gtt_storage_checkout(relid, false, isCommit);
+
+				gtt_free_statistics(entry);
+				hash_search(gtt_storage_local_hash,
+						(void *) &(relid), HASH_REMOVE, NULL);
+			}
+
+			return;
+		}
+	}
+
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	pfree(d_rnode);
+	if (entry->relfilenode_list == NIL)
+	{
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(relid, false, isCommit);
+
+		if (isCommit && entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			/* clean up footprint */
+			entry2->oldrelid = InvalidOid;
+		}
+
+		gtt_free_statistics(entry);
+		hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_REMOVE, NULL);
+	}
+	else
+		gtt_reset_statistics(entry);
+
+	return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool found = false;
+	gtt_local_hash_entry *entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry)
+		found = true;
+
+	return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS				status;
+	gtt_local_hash_entry		*entry;
+	SMgrRelation	*srels = NULL;
+	Oid				*relids = NULL;
+	char			*relkinds = NULL;
+	int			nrels = 0,
+				nfiles = 0,
+				maxrels = 0,
+				maxfiles = 0,
+				i = 0;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel;
+			RelFileNode 	rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel = smgropen(rnode, MyBackendId);
+
+			if (maxfiles == 0)
+			{
+				maxfiles = 32;
+				srels = palloc(sizeof(SMgrRelation) * maxfiles);
+			}
+			else if (maxfiles <= nfiles)
+			{
+				maxfiles *= 2;
+				srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+			}
+
+			srels[nfiles++] = srel;
+		}
+
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			relids  = palloc(sizeof(Oid) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			relids  = repalloc(relids , sizeof(Oid) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		relkinds[nrels] = entry->relkind;
+		relids[nrels] = entry->relid;
+		nrels++;
+	}
+
+	if (nfiles > 0)
+	{
+		smgrdounlinkall(srels, nfiles, false);
+		for (i = 0; i < nfiles; i++)
+			smgrclose(srels[i]);
+
+		pfree(srels);
+	}
+
+	if (nrels)
+	{
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+		for (i = 0; i < nrels; i++)
+		{
+			if (relkinds[i] == RELKIND_RELATION ||
+				relkinds[i] == RELKIND_SEQUENCE)
+				gtt_storage_checkout(relids[i], true, false);
+		}
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+		pfree(relids);
+		pfree(relkinds);
+	}
+
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+	return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible) 
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+					BlockNumber num_pages,
+					double num_tuples,
+					BlockNumber num_all_visible_pages,
+					TransactionId relfrozenxid,
+					TransactionId relminmxid)
+{
+	Oid		relid = RelationGetRelid(relation);
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (num_pages >= 0 &&
+		gtt_rnode->relpages != (int32)num_pages)
+		gtt_rnode->relpages = (int32)num_pages;
+
+	if (num_tuples >= 0 &&
+		gtt_rnode->reltuples != (float4)num_tuples)
+		gtt_rnode->reltuples = (float4)num_tuples;
+
+	/* only heap contain transaction information and relallvisible */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (gtt_rnode->relallvisible >= 0 &&
+			gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+		{
+			gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+		}
+
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNewTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible) 
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	MemoryContext			oldcontext;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return;
+
+	if (entry->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	Assert(entry->relid == reloid);
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == 0)
+		{
+			entry->attnum[i] = attnum;
+			break;
+		}
+		else if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < entry->natts);
+	Assert(entry->att_stat_tups[i] == NULL);
+	entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return NULL;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			return entry->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int			i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	HeapTuple	tuple;
+	Relation	rel = NULL;
+	int		attnum = PG_GETARG_INT32(1);
+	Oid		reloid = PG_GETARG_OID(0);
+	char		rel_persistence;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	  tupdesc;
+	MemoryContext oldcontext;
+	Tuplestorestate *tupstore;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+
+	relation_close(rel, NoLock);
+	relation_close(pg_tatistic, AccessShareLock);
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate	*tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Oid			relnode = 0;
+	Relation	rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[6];
+		bool	isnull[6];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(reloid);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(RelationGetRelid(index)));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid relOid = index->rd_index->indrelid;
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	if (gtt_storage_attached(relOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int			i;
+	Oid			toastrelid;
+
+	if (!(operation == CMD_UTILITY || operation == CMD_INSERT))
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+
+		index_build(relation, index, info, true, false);
+	}
+
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid 		indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo 	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+static void
+gtt_reset_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+
+		entry->attnum[i] = 0;
+	}
+
+	return;
+}
+
+static void
+gtt_free_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (entry->attnum)
+		pfree(entry->attnum);
+
+	if (entry->att_stat_tups)
+		pfree(entry->att_stat_tups);
+
+	return;
+}
+
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
+void
+gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint)
+{
+	gtt_local_hash_entry	*entry1;
+	gtt_local_hash_entry	*entry2;
+	gtt_relfilenode			*gtt_rnode1 = NULL;
+	gtt_relfilenode			*gtt_rnode2 = NULL;
+	MemoryContext			oldcontext;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry1 = gtt_search_by_relid(rel1, false);
+	gtt_rnode1 = gtt_search_relfilenode(entry1, relfilenode1, false);
+
+	entry2 = gtt_search_by_relid(rel2, false);
+	gtt_rnode2 = gtt_search_relfilenode(entry2, relfilenode2, false);
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	entry1->relfilenode_list = list_delete_ptr(entry1->relfilenode_list, gtt_rnode1);
+	entry2->relfilenode_list = lappend(entry2->relfilenode_list, gtt_rnode1);
+
+	entry2->relfilenode_list = list_delete_ptr(entry2->relfilenode_list, gtt_rnode2);
+	entry1->relfilenode_list = lappend(entry1->relfilenode_list, gtt_rnode2);
+	MemoryContextSwitchTo(oldcontext);
+
+	if (footprint)
+	{
+		entry1->oldrelid = rel2;
+		entry2->oldrelid = rel1;
+	}
+
+	return;
+}
+
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok)
+{
+	gtt_relfilenode			*rnode = NULL;
+	ListCell				*lc;
+
+	Assert(entry);
+
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == relfilenode)
+		{
+			rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (!missing_ok && rnode == NULL)
+		elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+	return rnode;
+}
+
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry == NULL && !missing_ok)
+		elog(ERROR, "relid %u not found in local hash", relid);
+
+	return entry;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2bd5f5e..1a94c28 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM 
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 924ef37..db12eef 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -184,6 +185,13 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -586,14 +594,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 		 * pg_statistic for columns we didn't process, we leave them alone.)
 		 */
 		update_attstats(RelationGetRelid(onerel), inh,
-						attr_cnt, vacattrstats);
+						attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
 
 		for (ind = 0; ind < nindexes; ind++)
 		{
 			AnlIndexData *thisdata = &indexdata[ind];
 
 			update_attstats(RelationGetRelid(Irel[ind]), false,
-							thisdata->attr_cnt, thisdata->vacattrstats);
+							thisdata->attr_cnt, thisdata->vacattrstats,
+							RelationGetRelPersistence(Irel[ind]));
 		}
 
 		/*
@@ -1456,7 +1465,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
  *		by taking a self-exclusive lock on the relation in analyze_rel().
  */
 static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
 {
 	Relation	sd;
 	int			attno;
@@ -1558,31 +1567,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/* Update column statistic to localhash, not catalog */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 04d12a7..42c82c8 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/toasting.h"
 #include "commands/cluster.h"
 #include "commands/progress.h"
@@ -72,6 +73,12 @@ static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
 							bool verbose, bool *pSwapToastByContent,
 							TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
 static List *get_tables_to_cluster(MemoryContext cluster_context);
+static void gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables);
 
 
 /*---------------------------------------------------------------------------
@@ -366,6 +373,14 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap) &&
+		!gtt_storage_attached(RelationGetRelid(OldHeap)))
+	{
+		relation_close(OldHeap, AccessExclusiveLock);
+		pgstat_progress_end_command();
+		return;
+	}
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
@@ -750,6 +765,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	BlockNumber num_pages;
 	int			elevel = verbose ? INFO : DEBUG2;
 	PGRUsage	ru0;
+	bool		is_gtt = false;
+	uint32		gtt_relfrozenxid = 0;
+	uint32		gtt_relminmxid = 0;
 
 	pg_rusage_init(&ru0);
 
@@ -763,6 +781,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	else
 		OldIndex = NULL;
 
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		is_gtt = true;
+
 	/*
 	 * Their tuple descriptors should be exactly alike, but here we only need
 	 * assume that they have the same number of columns.
@@ -830,20 +851,37 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 						  &OldestXmin, &FreezeXid, NULL, &MultiXactCutoff,
 						  NULL);
 
-	/*
-	 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
-	 * backwards, so take the max.
-	 */
-	if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
-		TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
-		FreezeXid = OldHeap->rd_rel->relfrozenxid;
+	if (is_gtt)
+	{
+		get_gtt_relstats(OIDOldHeap,
+					NULL, NULL, NULL,
+					&gtt_relfrozenxid, &gtt_relminmxid);
 
-	/*
-	 * MultiXactCutoff, similarly, shouldn't go backwards either.
-	 */
-	if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
-		MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
-		MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+		if (TransactionIdIsValid(gtt_relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, gtt_relfrozenxid))
+			FreezeXid = gtt_relfrozenxid;
+
+		if (MultiXactIdIsValid(gtt_relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, gtt_relminmxid))
+			MultiXactCutoff = gtt_relminmxid;
+	}
+	else
+	{
+		/*
+		 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
+		 * backwards, so take the max.
+		 */
+		if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
+			FreezeXid = OldHeap->rd_rel->relfrozenxid;
+
+		/*
+		 * MultiXactCutoff, similarly, shouldn't go backwards either.
+		 */
+		if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
+			MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+	}
 
 	/*
 	 * Decide whether to use an indexscan or seqscan-and-optional-sort to scan
@@ -911,6 +949,12 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	table_close(OldHeap, NoLock);
 	table_close(NewHeap, NoLock);
 
+	if (is_gtt)
+	{
+		CommandCounterIncrement();
+		return;
+	}
+
 	/* Update pg_class to reflect the correct values of pages and tuples. */
 	relRelation = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1346,10 +1390,21 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 	 * Swap the contents of the heap relations (including any toast tables).
 	 * Also set old heap's relfrozenxid to frozenXid.
 	 */
-	swap_relation_files(OIDOldHeap, OIDNewHeap,
+	if (newrelpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		Assert(!is_system_catalog);
+		gtt_swap_relation_files(OIDOldHeap, OIDNewHeap,
+								(OIDOldHeap == RelationRelationId),
+								swap_toast_by_content, is_internal,
+								frozenXid, cutoffMulti, mapped_tables);
+	}
+	else
+	{
+		swap_relation_files(OIDOldHeap, OIDNewHeap,
 						(OIDOldHeap == RelationRelationId),
 						swap_toast_by_content, is_internal,
 						frozenXid, cutoffMulti, mapped_tables);
+	}
 
 	/*
 	 * If it's a system catalog, queue a sinval message to flush all catcaches
@@ -1557,3 +1612,141 @@ get_tables_to_cluster(MemoryContext cluster_context)
 
 	return rvs;
 }
+
+static void
+gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables)
+{
+	Relation	relRelation;
+	Oid			relfilenode1,
+				relfilenode2;
+	Relation	rel1;
+	Relation	rel2;
+
+	relRelation = table_open(RelationRelationId, RowExclusiveLock);
+
+	rel1 = relation_open(r1, AccessExclusiveLock);
+	rel2 = relation_open(r2, AccessExclusiveLock);
+
+	relfilenode1 = gtt_fetch_current_relfilenode(r1);
+	relfilenode2 = gtt_fetch_current_relfilenode(r2);
+
+	Assert(OidIsValid(relfilenode1) && OidIsValid(relfilenode2));
+	gtt_switch_rel_relfilenode(r1, relfilenode1, r2, relfilenode2, true);
+
+	CacheInvalidateRelcache(rel1);
+	CacheInvalidateRelcache(rel2);
+
+	InvokeObjectPostAlterHookArg(RelationRelationId, r1, 0,
+								 InvalidOid, is_internal);
+	InvokeObjectPostAlterHookArg(RelationRelationId, r2, 0,
+								 InvalidOid, true);
+
+	if (rel1->rd_rel->reltoastrelid || rel2->rd_rel->reltoastrelid)
+	{
+		if (swap_toast_by_content)
+		{
+			if (rel1->rd_rel->reltoastrelid && rel2->rd_rel->reltoastrelid)
+			{
+				gtt_swap_relation_files(rel1->rd_rel->reltoastrelid,
+									rel2->rd_rel->reltoastrelid,
+									target_is_pg_class,
+									swap_toast_by_content,
+									is_internal,
+									frozenXid,
+									cutoffMulti,
+									mapped_tables);
+			}
+			else
+				elog(ERROR, "cannot swap toast files by content when there's only one");
+		}
+		else
+		{
+			ObjectAddress baseobject,
+						toastobject;
+			long		count;
+
+			if (IsSystemRelation(rel1))
+				elog(ERROR, "cannot swap toast files by links for system catalogs");
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel1->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel2->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+
+			/* Register new dependencies */
+			baseobject.classId = RelationRelationId;
+			baseobject.objectSubId = 0;
+			toastobject.classId = RelationRelationId;
+			toastobject.objectSubId = 0;
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r1;
+				toastobject.objectId = rel1->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r2;
+				toastobject.objectId = rel2->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+		}
+	}
+
+	if (swap_toast_by_content &&
+		rel1->rd_rel->relkind == RELKIND_TOASTVALUE &&
+		rel2->rd_rel->relkind == RELKIND_TOASTVALUE)
+	{
+		Oid			toastIndex1,
+					toastIndex2;
+
+		/* Get valid index for each relation */
+		toastIndex1 = toast_get_valid_index(r1,
+											AccessExclusiveLock);
+		toastIndex2 = toast_get_valid_index(r2,
+											AccessExclusiveLock);
+
+		gtt_swap_relation_files(toastIndex1,
+							toastIndex2,
+							target_is_pg_class,
+							swap_toast_by_content,
+							is_internal,
+							InvalidTransactionId,
+							InvalidMultiXactId,
+							mapped_tables);
+	}
+
+	relation_close(rel1, NoLock);
+	relation_close(rel2, NoLock);
+
+	table_close(relRelation, RowExclusiveLock);
+
+	RelationCloseSmgrByOid(r1);
+	RelationCloseSmgrByOid(r2);
+
+	CommandCounterIncrement();
+}
+
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index ac07f75..655184d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -27,6 +27,7 @@
 #include "catalog/dependency.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -1063,7 +1064,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, stmt->filename, stmt->is_program,
@@ -2794,6 +2795,8 @@ CopyFrom(CopyState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	estate->es_result_relations = resultRelInfo;
 	estate->es_num_result_relations = 1;
 	estate->es_result_relation_info = resultRelInfo;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 2baca12..5338db9 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -521,6 +521,7 @@ DefineIndex(Oid relationId,
 	Snapshot	snapshot;
 	int			save_nestlevel = -1;
 	int			i;
+	char		rel_persistence;
 
 	/*
 	 * Some callers need us to run with an empty default_tablespace; this is a
@@ -542,7 +543,9 @@ DefineIndex(Oid relationId,
 	 * is more efficient.  Do this before any use of the concurrent option is
 	 * done.
 	 */
-	if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+	rel_persistence = get_rel_persistence(relationId);
+	if (stmt->concurrent &&
+		!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		concurrent = true;
 	else
 		concurrent = false;
@@ -2460,7 +2463,8 @@ ReindexIndex(RangeVar *indexRelation, int options, bool concurrent)
 	persistence = irel->rd_rel->relpersistence;
 	index_close(irel, NoLock);
 
-	if (concurrent && persistence != RELPERSISTENCE_TEMP)
+	if (concurrent &&
+		!(persistence == RELPERSISTENCE_TEMP || persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		ReindexRelationConcurrently(indOid, options);
 	else
 		reindex_index(indOid, false, persistence,
@@ -2546,6 +2550,7 @@ ReindexTable(RangeVar *relation, int options, bool concurrent)
 {
 	Oid			heapOid;
 	bool		result;
+	char		rel_persistence;
 
 	/*
 	 * The lock level used here should match reindex_relation().
@@ -2560,7 +2565,9 @@ ReindexTable(RangeVar *relation, int options, bool concurrent)
 									   0,
 									   RangeVarCallbackOwnsTable, NULL);
 
-	if (concurrent && get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+	rel_persistence = get_rel_persistence(heapOid);
+	if (concurrent &&
+		!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 	{
 		result = ReindexRelationConcurrently(heapOid, options);
 
@@ -2761,12 +2768,15 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 	foreach(l, relids)
 	{
 		Oid			relid = lfirst_oid(l);
+		char		rel_persistence;
 
 		StartTransactionCommand();
 		/* functions in indexes may want a snapshot set */
 		PushActiveSnapshot(GetTransactionSnapshot());
 
-		if (concurrent && get_rel_persistence(relid) != RELPERSISTENCE_TEMP)
+		rel_persistence = get_rel_persistence(relid);
+		if (concurrent &&
+			!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		{
 			(void) ReindexRelationConcurrently(relid, options);
 			/* ReindexRelationConcurrently() does the verbose output */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..234ffa6 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -94,7 +96,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -222,7 +225,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* now initialize the sequence's data */
 	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -327,7 +330,7 @@ ResetSequence(Oid seq_relid)
 	/*
 	 * Insert the modified tuple into the new storage file.
 	 */
-	fill_seq_with_data(seq_rel, tuple);
+	fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -340,17 +343,21 @@ ResetSequence(Oid seq_relid)
  * Initialize a sequence's relation with the specified tuple as content
  */
 static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 	sequence_magic *sm;
 	OffsetNumber offnum;
+	bool lockBuffer = false;
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ReadBuffer(rel, P_NEW);
-	Assert(BufferGetBlockNumber(buf) == 0);
+	if (buf == InvalidBuffer)
+	{
+		buf = ReadBuffer(rel, P_NEW);
+		Assert(BufferGetBlockNumber(buf) == 0);
+		lockBuffer = true;
+	}
 
 	page = BufferGetPage(buf);
 
@@ -360,7 +367,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* Now insert sequence tuple */
 
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+	if (lockBuffer)
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +418,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	END_CRIT_SECTION();
 
-	UnlockReleaseBuffer(buf);
+	if (lockBuffer)
+		UnlockReleaseBuffer(buf);
 }
 
 /*
@@ -451,6 +460,15 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 
 	init_sequence(relid, &elm, &seqrel);
 
+	if (RELATION_IS_GLOBAL_TEMP(seqrel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(seqrel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary sequence %s when other backend attached it.",
+						RelationGetRelationName(seqrel))));
+	}
+
 	rel = table_open(SequenceRelationId, RowExclusiveLock);
 	seqtuple = SearchSysCacheCopy1(SEQRELID,
 								   ObjectIdGetDatum(relid));
@@ -502,7 +520,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * Insert the modified tuple into the new storage file.
 		 */
-		fill_seq_with_data(seqrel, newdatatuple);
+		fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
 	}
 
 	/* process OWNED BY if given */
@@ -611,7 +629,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +954,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1154,6 +1172,13 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1954,3 +1979,46 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+void
+gtt_init_seq(Relation rel)
+{
+	/* Initialize sequence for global temporary tables */
+	Datum		value[SEQ_COL_LASTCOL] = {0};
+	bool		null[SEQ_COL_LASTCOL] = {false};
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5745cd6..c0b0dc8 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -556,6 +557,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 								  Relation partitionTbl);
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static OnCommitAction gtt_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -601,6 +603,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -611,8 +614,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")));
@@ -642,7 +647,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	/* global temp table same as local temp table */
+	if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+		 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -743,6 +750,55 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* global temp table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* check parent table*/
+		if (inheritOids)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			table_close(relation, NoLock);
+		}
+
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temp table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1330,6 +1386,7 @@ RemoveRelations(DropStmt *drop)
 		Oid			relOid;
 		ObjectAddress obj;
 		struct DropRelationCallbackState state;
+		char		rel_persistence;
 
 		/*
 		 * These next few steps are a great deal like relation_openrv, but we
@@ -1363,8 +1420,9 @@ RemoveRelations(DropStmt *drop)
 		 * Decide if concurrent mode needs to be used here or not.  The
 		 * relation persistence cannot be known without its OID.
 		 */
+		rel_persistence = get_rel_persistence(relOid);
 		if (drop->concurrent &&
-			get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+			!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		{
 			Assert(list_length(drop->objects) == 1 &&
 				   drop->removeType == OBJECT_INDEX);
@@ -1819,6 +1877,10 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
 		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 			continue;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			continue;
+
 		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
@@ -3570,6 +3632,16 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -4847,6 +4919,38 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot rewrite temporary tables of other sessions")));
 
+			if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+			{
+				if (tab->chgPersistence)
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot change global temporary table persistence setting")));
+
+				/* gtt may not attached, create it */
+				if(!gtt_storage_attached(tab->relid))
+				{
+					ResultRelInfo *resultRelInfo;
+					MemoryContext oldcontext;
+					MemoryContext ctx_alter_gtt;
+
+					ctx_alter_gtt = AllocSetContextCreate(CurrentMemoryContext,
+											"gtt alter table", ALLOCSET_DEFAULT_SIZES);
+
+					oldcontext = MemoryContextSwitchTo(ctx_alter_gtt);
+					resultRelInfo = makeNode(ResultRelInfo);
+					InitResultRelInfo(resultRelInfo, OldHeap,
+									1, NULL, 0);
+					if (resultRelInfo->ri_RelationDesc->rd_rel->relhasindex &&
+						resultRelInfo->ri_IndexRelationDescs == NULL)
+						ExecOpenIndices(resultRelInfo, false);
+
+					init_gtt_storage(CMD_UTILITY, resultRelInfo);
+					ExecCloseIndices(resultRelInfo);
+					MemoryContextSwitchTo(oldcontext);
+					MemoryContextDelete(ctx_alter_gtt);
+				}
+			}
+
 			/*
 			 * Select destination tablespace (same as original unless user
 			 * requested a change)
@@ -8184,6 +8288,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -12740,6 +12850,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -12942,6 +13055,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temp table");
+
 	/*
 	 * No work if no change in tablespace.
 	 */
@@ -13316,7 +13432,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,
@@ -14723,7 +14839,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",
@@ -17379,3 +17497,36 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
 		table_close(rel, NoLock);
 	}
 }
+
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			bool	res = false;
+			char	*sval = defGetString(def);
+
+			if (!parse_bool(sval, &res))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
+
+			if (res)
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 5a110ed..02e6f1e 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"
@@ -1215,6 +1216,17 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	 /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		is_gtt = true;
+		up_gtt_relstats(relation,
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1228,17 +1240,26 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relpages = (int32) num_pages;
+	else if (pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (is_gtt)
+		relation->rd_rel->reltuples = (float4) num_tuples;
+	else if (pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	else if (pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1283,7 +1304,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1294,7 +1316,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1392,6 +1415,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
@@ -1449,6 +1476,42 @@ 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	safe_age;
+		TransactionId	oldest_gtt_frozenxid =
+			list_all_session_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid))
+		{
+			safe_age = oldest_gtt_frozenxid + vacuum_gtt_defer_check_age;
+			if (safe_age < FirstNormalTransactionId)
+				safe_age += FirstNormalTransactionId;
+
+			/*
+			 * We tolerate that the minimum age of gtt is less than
+			 * the minimum age of conventional tables, otherwise it will
+			 * throw warning message.
+			 */
+			if (TransactionIdIsNormal(safe_age) &&
+				TransactionIdPrecedes(safe_age, newFrozenXid))
+			{
+				ereport(WARNING,
+					(errmsg("global temp table oldest relfrozenxid %u is the oldest in the entire db",
+							oldest_gtt_frozenxid),
+					 errdetail("The oldest relfrozenxid in pg_class is %u", newFrozenXid),
+					 errhint("If they differ greatly, please consider cleaning up the data in global temp table.")));
+			}
+
+			if (TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+				newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1776,6 +1839,15 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6e65103..04706ee 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -530,6 +530,11 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 4fdffad..0aa7559 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index fb6ce49..06d9237 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
@@ -545,6 +546,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	init_gtt_storage(mtstate->operation, leaf_part_rri);
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 20a4c47..e9f6dcd 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,6 +42,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -2379,6 +2380,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			ExecOpenIndices(resultRelInfo,
 							node->onConflictAction != ONCONFLICT_NONE);
 
+		init_gtt_storage(operation, resultRelInfo);
+
 		/*
 		 * If this is an UPDATE and a BEFORE UPDATE trigger is present, the
 		 * trigger itself might modify the partition-key values. So arrange
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 255f56b..d94d6f0 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 e664eb1..e03bfa0 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6419,7 +6419,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
+	/* global temp table is same as local temp table */
 	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 2554502..04fb5d4 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -28,6 +28,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temp table */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index eee9c33..1891c53 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2594,6 +2594,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 3c78f2d..edb8d9f 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3302,17 +3302,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; }
@@ -11648,19 +11642,13 @@ OptTempTableName:
 				}
 			| GLOBAL TEMPORARY opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED opt_table qualified_name
 				{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b875a50..123945b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 75c122f..97213ed 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
@@ -3108,6 +3111,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		cxt.isforeign = false;
 	}
 	cxt.relation = stmt->relation;
+	cxt.relation->relpersistence = RelationGetRelPersistence(rel);
 	cxt.rel = rel;
 	cxt.inhRelations = NIL;
 	cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 7e97ffa..1f2ac58 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2089,6 +2089,11 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* autovacuum skip vacuum global temp table */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2155,7 +2160,9 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		/* autovacuum skip vacuum global temp table */
+		if (classForm->relpersistence == RELPERSISTENCE_TEMP || 
+			classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index f9980cf..406a25c 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -53,6 +54,7 @@
 #include "utils/rel.h"
 #include "utils/resowner_private.h"
 #include "utils/timestamp.h"
+#include "utils/guc.h"
 
 
 /* Note: these two macros only work on shared buffers, not local ones! */
@@ -2819,6 +2821,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d5..7adb969 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -21,6 +21,7 @@
 #include "access/nbtree.h"
 #include "access/subtrans.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, active_gtt_shared_hash_size());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* global temporary table */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 3630006..0c8ef80 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -63,6 +63,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -4086,3 +4087,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 5aa19d3..b3bc455 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 8406644..0d99321 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -1008,6 +1008,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index cfb0568..5913efc 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -112,6 +112,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -4821,12 +4822,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))
 							{
@@ -4951,15 +4965,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))
 		{
@@ -6341,6 +6367,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;
@@ -6358,6 +6385,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,
@@ -6369,6 +6403,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;
@@ -6384,6 +6420,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,
@@ -7302,6 +7345,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))
@@ -7314,6 +7359,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 =
@@ -7326,6 +7379,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.
@@ -7345,6 +7400,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index a7d63f1..d8a52cb 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -2939,6 +2940,18 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 9f1f11d..aeb2a78 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -65,6 +65,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1133,6 +1134,24 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1187,6 +1206,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			gtt_fix_index_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -1317,7 +1337,17 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+			if (newrelnode != InvalidOid &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2246,6 +2276,8 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		gtt_fix_index_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3472,6 +3504,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3579,28 +3615,34 @@ void
 RelationSetNewRelfilenode(Relation relation, char persistence)
 {
 	Oid			newrelfilenode;
-	Relation	pg_class;
-	HeapTuple	tuple;
+	Relation	pg_class = NULL;
+	HeapTuple	tuple = NULL;
 	Form_pg_class classform;
 	MultiXactId minmulti = InvalidMultiXactId;
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
+	bool		modify_pg_class = !RELATION_IS_GLOBAL_TEMP(relation);
 
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
 
-	/*
-	 * Get a writable copy of the pg_class tuple for the given relation.
-	 */
-	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+	if (modify_pg_class)
+	{
+		/*
+		 * Get a writable copy of the pg_class tuple for the given relation.
+		 */
+		pg_class = table_open(RelationRelationId, RowExclusiveLock);
 
-	tuple = SearchSysCacheCopy1(RELOID,
-								ObjectIdGetDatum(RelationGetRelid(relation)));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "could not find tuple for relation %u",
-			 RelationGetRelid(relation));
-	classform = (Form_pg_class) GETSTRUCT(tuple);
+		tuple = SearchSysCacheCopy1(RELOID,
+									ObjectIdGetDatum(RelationGetRelid(relation)));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u",
+				 RelationGetRelid(relation));
+		classform = (Form_pg_class) GETSTRUCT(tuple);
+	}
+	else
+		memset(&classform, 0, sizeof(classform));
 
 	/*
 	 * Schedule unlinking of the old storage at transaction commit.
@@ -3626,7 +3668,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;
@@ -3646,6 +3688,15 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 			break;
 	}
 
+	if (!modify_pg_class)
+	{
+		Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+		Assert(RELATION_IS_GLOBAL_TEMP(relation));
+		Assert(!RelationIsMapped(relation));
+		relation->rd_node.relNode = relnode;
+		CacheInvalidateRelcache(relation);
+	}
 	/*
 	 * If we're dealing with a mapped index, pg_class.relfilenode doesn't
 	 * change; instead we have to send the update to the relation mapper.
@@ -3655,7 +3706,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	 * possibly-inaccurate values of relpages etc, but those will be fixed up
 	 * later.
 	 */
-	if (RelationIsMapped(relation))
+	else if (RelationIsMapped(relation))
 	{
 		/* This case is only supported for indexes */
 		Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3701,9 +3752,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 		CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 	}
 
-	heap_freetuple(tuple);
+	if (modify_pg_class)
+	{
+		heap_freetuple(tuple);
 
-	table_close(pg_class, RowExclusiveLock);
+		table_close(pg_class, RowExclusiveLock);
+	}
 
 	/*
 	 * Make the pg_class row change or relation map change visible.  This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 5bdc02f..b52b51d 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -37,6 +37,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "commands/prepare.h"
 #include "commands/trigger.h"
@@ -142,6 +143,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temp table feature.
+ * global temp table define can still storage in catalog
+ * just can not use.
+ * num > 0 means database can management num active global temp table.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2070,6 +2083,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."),
@@ -2577,6 +2599,16 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"vacuum_gtt_defer_check_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("The defer check age of GTT, used to check expired data after vacuum."),
+			NULL
+		},
+		&vacuum_gtt_defer_check_age,
+		10000, 0, 1000000,
+		NULL, NULL, NULL
+	},
+
 	/*
 	 * See also CheckRequiredParameterValues() if this parameter changes
 	 */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 5db4f57..018a9bf 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2424,6 +2424,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
 		dopt->no_unlogged_table_data)
 		return;
 
+	/* Don't dump data in global temp table/sequence */
+	if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		return;
+
 	/* Check that the data is not explicitly excluded */
 	if (simple_oid_list_member(&tabledata_exclude_oids,
 							   tbinfo->dobj.catId.oid))
@@ -15777,6 +15781,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
 		char	   *foreign = "";
+		char	   *table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15830,9 +15835,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);
 
@@ -16226,7 +16237,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
-			 tbinfo->relkind == RELKIND_MATVIEW))
+			 tbinfo->relkind == RELKIND_MATVIEW) &&
+			 tbinfo->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
 		{
 			appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
 			appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
@@ -17131,6 +17143,7 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	PQExpBuffer query = createPQExpBuffer();
 	PQExpBuffer delqry = createPQExpBuffer();
 	char	   *qseqname;
+	bool	   global_temp_seq = false;
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
@@ -17140,9 +17153,11 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
-						  "FROM pg_catalog.pg_sequence "
-						  "WHERE seqrelid = '%u'::oid",
+						  "seqcache, seqcycle, c.relpersistence "
+						  "FROM pg_catalog.pg_sequence s, "
+						  "pg_catalog.pg_class c "
+						  "WHERE seqrelid = '%u'::oid "
+						  "and s.seqrelid = c.oid",
 						  tbinfo->dobj.catId.oid);
 	}
 	else if (fout->remoteVersion >= 80400)
@@ -17187,6 +17202,9 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
 
+	if (fout->remoteVersion >= 130000)
+		global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
 	if (strcmp(seqtype, "smallint") == 0)
@@ -17261,9 +17279,13 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	}
 	else
 	{
-		appendPQExpBuffer(query,
-						  "CREATE SEQUENCE %s\n",
-						  fmtQualifiedDumpable(tbinfo));
+		appendPQExpBuffer(query, "CREATE ");
+
+		if (global_temp_seq)
+			appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+		appendPQExpBuffer(query, "SEQUENCE %s\n",
+								  fmtQualifiedDumpable(tbinfo));
 
 		if (strcmp(seqtype, "bigint") != 0)
 			appendPQExpBuffer(query, "    AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 00aef85..9ad71c8 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -84,7 +84,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -157,7 +157,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 7e524ea..371168f 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -441,22 +443,46 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 	 * output, so we have to copy that system table.  It's easiest to do that
 	 * by treating it as a user table.
 	 */
-	snprintf(query + strlen(query), sizeof(query) - strlen(query),
-			 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
-			 "  SELECT c.oid, 0::oid, 0::oid "
-			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
-			 "         ON c.relnamespace = n.oid "
-			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
-	/* exclude possible orphaned temp tables */
-			 "    ((n.nspname !~ '^pg_temp_' AND "
-			 "      n.nspname !~ '^pg_toast_temp_' AND "
-			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
-			 "                        'binary_upgrade', 'pg_toast') AND "
-			 "      c.oid >= %u::pg_catalog.oid) OR "
-			 "     (n.nspname = 'pg_catalog' AND "
-			 "      relname IN ('pg_largeobject') ))), ",
-			 FirstNormalObjectId);
+	if (skip_gtt)
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude global temp tables */
+				 "    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
+	else
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
 
 	/*
 	 * Add a CTE that collects OIDs of toast tables belonging to the tables
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 70194eb..8dd9e66 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -411,7 +411,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -642,7 +642,9 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -653,7 +655,9 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 8b90cef..d155205 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -389,7 +389,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 8dca6d8..6b3f571 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3738,7 +3738,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index f6fd623..ea87bc4 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1023,6 +1023,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2394,6 +2396,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2602,6 +2607,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index cbfdfe2..be39472 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -59,7 +59,8 @@ extern Relation heap_create(const char *relname,
 							bool mapped_relation,
 							bool allow_system_table_mods,
 							TransactionId *relfrozenxid,
-							MultiXactId *relminmxid);
+							MultiXactId *relminmxid,
+							bool skip_create_storage);
 
 extern Oid	heap_create_with_catalog(const char *relname,
 									 Oid relnamespace,
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 78b33b2..4be89f6 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -168,6 +168,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad..c8236a1 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5558,6 +5558,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4388',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4389',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4390',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4391',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 30c38e0..7ff2408 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,7 +22,7 @@
 /* GUC variables */
 extern int	wal_skip_threshold;
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationPreTruncate(Relation rel);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..902375d
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern int		vacuum_gtt_defer_check_age;
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Relation relation,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void gtt_force_enable_index(Relation index);
+extern void gtt_fix_index_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+extern void gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index e2638ab..89a5ce4 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 3f88683..f870e9a 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 8fda8e4..172eae9 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_TBM,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_SXACT,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index ae4f573..69cce92 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -117,6 +117,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId session_gtt_frozenxid;	/* session level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 2819282..363fc33 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -282,6 +282,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 0b5957b..0720a4c 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	struct SMgrRelationData *rd_smgr;	/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -305,6 +305,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -570,11 +571,13 @@ typedef struct ViewOptions
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -582,6 +585,7 @@ typedef struct ViewOptions
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -594,6 +598,14 @@ typedef struct ViewOptions
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is either local temp relation of this session
+ * 		or global temp relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -637,6 +649,17 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..ca2d135
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,14 @@
+reset search_path;
+select pg_sleep(5);
+ pg_sleep 
+----------
+ 
+(1 row)
+
+drop schema gtt cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
+drop cascades to table gtt.gtt_with_seq
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..d6a675a
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,383 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+ n 
+---
+ 9
+(1 row)
+
+commit;
+-- 0 row
+select * from gtt6;
+ n 
+---
+(0 rows)
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temp table
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  could not create global temporary table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR:  insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count 
+-------
+     1
+(1 row)
+
+select count(*) from orders;
+ count 
+-------
+     0
+(1 row)
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  2 |  3
+(2 rows)
+
+commit;
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+select * from gtt_s_2 order by c1;
+ c1 | c2 
+----+----
+  1 |  2
+  2 |  4
+(2 rows)
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 1)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 300)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 33 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test_new
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.gtt6
+drop cascades to table gtt_function.foo
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to sequence gtt_function.seq_1
+drop cascades to table gtt_function.gtt_s_1
+drop cascades to table gtt_function.gtt_s_2
+drop cascades to table gtt_function.gt1
+drop cascades to table gtt_function.gtt_test1
+drop cascades to table gtt_function.gtt_test2
+drop cascades to table gtt_function.gtt_test3
+drop cascades to table gtt_function.gtt_test4
+drop cascades to table gtt_function.gtt_test5
+drop cascades to table gtt_function.gtt_test6
+drop cascades to table gtt_function.gtt_test7
+drop cascades to table gtt_function.gtt_test8
+drop cascades to table gtt_function.gtt_test9
+drop cascades to table gtt_function.gtt_test10
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..0646aae
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,90 @@
+set search_path=gtt,sys;
+select nextval('gtt_with_seq_c2_seq');
+ nextval 
+---------
+       1
+(1 row)
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..2f084be
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,343 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+rollback;
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+commit;
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          114688 |                 614400
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |            49152 |         49152 |                  49152
+(2 rows)
+
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2000
+(1 row)
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+ c1 | c2 
+----+----
+  1 |  1
+(1 row)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..8c0c376
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,10 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..4420fdb
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,80 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+ gtt_stats  | gtt_pkey  |        1 |         0 |             0
+(2 rows)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+reindex table gtt;
+reindex index gtt_pkey;
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+ gtt_stats  | gtt_pkey  |       30 |     10000 |             0
+(2 rows)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 8876025..f9c34d7 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1349,6 +1349,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 95f1925..76b2374 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..2c8e586
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,8 @@
+
+
+reset search_path;
+
+select pg_sleep(5);
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..81f0bfc
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,253 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+commit;
+-- 0 row
+select * from gtt6;
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+commit;
+select * from gtt_s_1 order by c1;
+select * from gtt_s_2 order by c1;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d05745e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,44 @@
+
+
+set search_path=gtt,sys;
+
+select nextval('gtt_with_seq_c2_seq');
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..39cca5e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,154 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+rollback;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+commit;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+select count(*) from gtt_t_kenyon;
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..dbe84d1
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,19 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..d61b0ff
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,46 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+reindex table gtt;
+
+reindex index gtt_pkey;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#270tushar
tushar.ahuja@enterprisedb.com
In reply to: 曾文旌 (#269)
Re: [Proposal] Global temporary tables

On 4/29/20 8:52 AM, 曾文旌 wrote:

Fixed the error message to make the expression more accurate. In v33.

Thanks wenjing

Please refer this scenario  , where getting an error while performing
cluster o/p

1)

X terminal -

postgres=# create global temp table f(n int);
CREATE TABLE

Y Terminal -

postgres=# create index index12 on f(n);
CREATE INDEX
postgres=# \q

X terminal -

postgres=# reindex index  index12;
REINDEX
postgres=#  cluster f using index12;
ERROR:  cannot cluster on invalid index "index12"
postgres=# drop index index12;
DROP INDEX

if this is an expected  , could we try  to make the error message more
simpler, if possible.

Another issue  -

X terminal -

postgres=# create global temp table f11(n int);
CREATE TABLE
postgres=# create index ind1 on f11(n);
CREATE INDEX
postgres=# create index ind2 on f11(n);
CREATE INDEX
postgres=#

Y terminal -

postgres=# drop table f11;
ERROR:  cannot drop index ind2 or global temporary table f11
HINT:  Because the index is created on the global temporary table and
other backend attached it.
postgres=#

it is only mentioning about ind2 index but what about ind1 and what if 
- they have lots of indexes ?
i  think - we should not mix index information while dropping the table
and vice versa.

--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company

#271曾文旌
wenjing.zwj@alibaba-inc.com
In reply to: tushar (#270)
1 attachment(s)
Re: [Proposal] Global temporary tables

2020年4月29日 下午7:46,tushar <tushar.ahuja@enterprisedb.com> 写道:

On 4/29/20 8:52 AM, 曾文旌 wrote:

Fixed the error message to make the expression more accurate. In v33.

Thanks wenjing

Please refer this scenario , where getting an error while performing cluster o/p

1)

X terminal -

postgres=# create global temp table f(n int);
CREATE TABLE

Y Terminal -

postgres=# create index index12 on f(n);
CREATE INDEX
postgres=# \q

X terminal -

postgres=# reindex index index12;
REINDEX
postgres=# cluster f using index12;
ERROR: cannot cluster on invalid index "index12"
postgres=# drop index index12;
DROP INDEX

if this is an expected , could we try to make the error message more simpler, if possible.

Another issue -

X terminal -

postgres=# create global temp table f11(n int);
CREATE TABLE
postgres=# create index ind1 on f11(n);
CREATE INDEX
postgres=# create index ind2 on f11(n);
CREATE INDEX
postgres=#

Y terminal -

postgres=# drop table f11;
ERROR: cannot drop index ind2 or global temporary table f11
HINT: Because the index is created on the global temporary table and other backend attached it.
postgres=#

it is only mentioning about ind2 index but what about ind1 and what if - they have lots of indexes ?
i think - we should not mix index information while dropping the table and vice versa.

postgres=# drop index index12;
ERROR: cannot drop index index12 or global temporary table f
HINT: Because the index is created on the global temporary table and other backend attached it.

postgres=# drop table f;
ERROR: cannot drop index index12 or global temporary table f
HINT: Because the index is created on the global temporary table and other backend attached it.
postgres=#

Dropping an index on a GTT and dropping a GTT with an index can both trigger this message, so the message looks like this, and it feels like there's no better way to do it.

Wenjing

Show quoted text

--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company

Attachments:

smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#272Prabhat Sahu
prabhat.sahu@enterprisedb.com
In reply to: 曾文旌 (#269)
Re: [Proposal] Global temporary tables

On Wed, Apr 29, 2020 at 8:52 AM 曾文旌 <wenjing.zwj@alibaba-inc.com> wrote:

2020年4月27日 下午9:48,Prabhat Sahu <prabhat.sahu@enterprisedb.com> 写道:

Thanks Wenjing, for the fix patch for previous issues.
I have verified the issues, now those fix look good to me.
But the below error message is confusing(for gtt2).

postgres=# drop table gtt1;
ERROR: cannot drop global temp table gtt1 when other backend attached it.

postgres=# drop table gtt2;
ERROR: cannot drop index idx2 on global temp table gtt2 when other
backend attached it.

I feel the above error message shown for "DROP TABLE gtt2;" is a bit
confusing(looks similar to DROP INDEX gtt2;).
If possible, can we keep the error message simple as "ERROR: cannot drop
global temp table gtt2 when other backend attached it."?
I mean, without giving extra information for the index attached to that
GTT.

Fixed the error message to make the expression more accurate. In v33.

Thanks Wenjing. We verified your latest patch(gtt_v33) focusing on all
reported issues and they work fine.
Thanks.
--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

#273曾文旌
wenjing.zwj@alibaba-inc.com
In reply to: Prabhat Sahu (#272)
1 attachment(s)
Re: [Proposal] Global temporary tables

2020年6月9日 下午8:15,Prabhat Sahu <prabhat.sahu@enterprisedb.com> 写道:

On Wed, Apr 29, 2020 at 8:52 AM 曾文旌 <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> wrote:

2020年4月27日 下午9:48,Prabhat Sahu <prabhat.sahu@enterprisedb.com <mailto:prabhat.sahu@enterprisedb.com>> 写道:

Thanks Wenjing, for the fix patch for previous issues.
I have verified the issues, now those fix look good to me.
But the below error message is confusing(for gtt2).

postgres=# drop table gtt1;
ERROR: cannot drop global temp table gtt1 when other backend attached it.

postgres=# drop table gtt2;
ERROR: cannot drop index idx2 on global temp table gtt2 when other backend attached it.

I feel the above error message shown for "DROP TABLE gtt2;" is a bit confusing(looks similar to DROP INDEX gtt2;).
If possible, can we keep the error message simple as "ERROR: cannot drop global temp table gtt2 when other backend attached it."?
I mean, without giving extra information for the index attached to that GTT.

Fixed the error message to make the expression more accurate. In v33.

Thanks Wenjing. We verified your latest patch(gtt_v33) focusing on all reported issues and they work fine.
Thanks.
--

I'm very glad to hear such good news.
I am especially grateful for your professional work on GTT.
Please feel free to let me know if there is anything you think could be improved.

Thanks.

Wenjing

Show quoted text

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

Attachments:

smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#274Pavel Stehule
pavel.stehule@gmail.com
In reply to: 曾文旌 (#273)
Re: [Proposal] Global temporary tables

Hi

čt 11. 6. 2020 v 4:13 odesílatel 曾文旌 <wenjing.zwj@alibaba-inc.com> napsal:

2020年6月9日 下午8:15,Prabhat Sahu <prabhat.sahu@enterprisedb.com> 写道:

On Wed, Apr 29, 2020 at 8:52 AM 曾文旌 <wenjing.zwj@alibaba-inc.com> wrote:

2020年4月27日 下午9:48,Prabhat Sahu <prabhat.sahu@enterprisedb.com> 写道:

Thanks Wenjing, for the fix patch for previous issues.
I have verified the issues, now those fix look good to me.
But the below error message is confusing(for gtt2).

postgres=# drop table gtt1;
ERROR: cannot drop global temp table gtt1 when other backend attached it.

postgres=# drop table gtt2;
ERROR: cannot drop index idx2 on global temp table gtt2 when other
backend attached it.

I feel the above error message shown for "DROP TABLE gtt2;" is a bit
confusing(looks similar to DROP INDEX gtt2;).
If possible, can we keep the error message simple as "ERROR: cannot
drop global temp table gtt2 when other backend attached it."?
I mean, without giving extra information for the index attached to that
GTT.

Fixed the error message to make the expression more accurate. In v33.

Thanks Wenjing. We verified your latest patch(gtt_v33) focusing on all
reported issues and they work fine.
Thanks.
--

I'm very glad to hear such good news.
I am especially grateful for your professional work on GTT.
Please feel free to let me know if there is anything you think could be
improved.

Thanks.

Wenjing

this patch needs rebase

Regards

Pavel

Show quoted text

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

#275曾文旌
wenjing.zwj@alibaba-inc.com
In reply to: Pavel Stehule (#274)
2 attachment(s)
Re: [Proposal] Global temporary tables

2020年7月6日 下午11:31,Pavel Stehule <pavel.stehule@gmail.com> 写道:

Hi

čt 11. 6. 2020 v 4:13 odesílatel 曾文旌 <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:

2020年6月9日 下午8:15,Prabhat Sahu <prabhat.sahu@enterprisedb.com <mailto:prabhat.sahu@enterprisedb.com>> 写道:

On Wed, Apr 29, 2020 at 8:52 AM 曾文旌 <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> wrote:

2020年4月27日 下午9:48,Prabhat Sahu <prabhat.sahu@enterprisedb.com <mailto:prabhat.sahu@enterprisedb.com>> 写道:

Thanks Wenjing, for the fix patch for previous issues.
I have verified the issues, now those fix look good to me.
But the below error message is confusing(for gtt2).

postgres=# drop table gtt1;
ERROR: cannot drop global temp table gtt1 when other backend attached it.

postgres=# drop table gtt2;
ERROR: cannot drop index idx2 on global temp table gtt2 when other backend attached it.

I feel the above error message shown for "DROP TABLE gtt2;" is a bit confusing(looks similar to DROP INDEX gtt2;).
If possible, can we keep the error message simple as "ERROR: cannot drop global temp table gtt2 when other backend attached it."?
I mean, without giving extra information for the index attached to that GTT.

Fixed the error message to make the expression more accurate. In v33.

Thanks Wenjing. We verified your latest patch(gtt_v33) focusing on all reported issues and they work fine.
Thanks.
--

I'm very glad to hear such good news.
I am especially grateful for your professional work on GTT.
Please feel free to let me know if there is anything you think could be improved.

Thanks.

Wenjing

this patch needs rebase

GTT Merge the latest PGMaster and resolves conflicts.

Wenjing

Show quoted text

Regards

Pavel

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

Attachments:

global_temporary_table_v34-pg13.patchapplication/octet-stream; name=global_temporary_table_v34-pg13.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 8ccc228..b061de9 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -168,6 +168,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * For global temp table only
+	 * use ShareUpdateExclusiveLock for ensure safety
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temp table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},	
 	/* list terminator */
 	{{NULL}}
 };
@@ -1817,6 +1830,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1961,13 +1976,18 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	/*
 	 * There are no options for partitioned tables yet, but this is able to do
 	 * some validation.
 	 */
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index 765329b..4761fdc 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1011,7 +1011,9 @@ gistproperty(Oid index_oid, int attno,
 XLogRecPtr
 gistGetFakeLSN(Relation rel)
 {
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	/* global temp is same as local temp table */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 3ec6d52..1222594 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -150,7 +150,9 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	/* global temp table is same as local temp table */
+	if (!(index->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		index->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 56b3562..d3443ec 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -587,7 +587,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -640,7 +640,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 1bbc459..eeac2c3 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -62,6 +62,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -439,9 +440,9 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
 	Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
-	/* not every AM requires these to be valid, but heap does */
-	Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
-	Assert(MultiXactIdIsValid(onerel->rd_rel->relminmxid));
+	/* not every AM requires these to be valid, but regular heap does */
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ MultiXactIdIsValid(onerel->rd_rel->relminmxid));
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 75628e0..6022d28 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -608,6 +609,10 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index fd93bcf..ba69f66 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6437,6 +6437,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/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 5eaca27..e5002aa 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -231,7 +231,8 @@ Boot_CreateStmt:
 												   mapped_relation,
 												   true,
 												   &relfrozenxid,
-												   &relminmxid);
+												   &relminmxid,
+												   false);
 						elog(DEBUG4, "bootstrap relation created");
 					}
 					else
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 9499bb3..ae47364 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -43,6 +43,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 7d6acae..3be8d63 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -393,7 +393,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/* global temp table is same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index fd04e82..bc4d103 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -304,7 +306,8 @@ heap_create(const char *relname,
 			bool mapped_relation,
 			bool allow_system_table_mods,
 			TransactionId *relfrozenxid,
-			MultiXactId *relminmxid)
+			MultiXactId *relminmxid,
+			bool skip_create_storage)
 {
 	bool		create_storage;
 	Relation	rel;
@@ -404,6 +407,9 @@ heap_create(const char *relname,
 									 relpersistence,
 									 relkind);
 
+	if (skip_create_storage)
+		create_storage = false;
+
 	/*
 	 * Have the storage manager create the relation's disk file, if needed.
 	 *
@@ -427,7 +433,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -953,6 +959,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -991,8 +998,18 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/* global temp table not remember transaction info in catalog */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1255,7 +1272,8 @@ heap_create_with_catalog(const char *relname,
 							   mapped_relation,
 							   allow_system_table_mods,
 							   &relfrozenxid,
-							   &relminmxid);
+							   &relminmxid,
+							   false);
 
 	Assert(relid == RelationGetRelid(new_rel_desc));
 
@@ -1351,6 +1369,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1931,6 +1950,16 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/* We allow to drop global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot drop global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3153,7 +3182,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3165,7 +3194,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3211,8 +3240,13 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
+
+		/* truncate global temp table only need RowExclusiveLock */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3245,6 +3279,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3253,23 +3288,37 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (!gtt_storage_attached(RelationGetRelid(rel)))
+			return;
+
+		/*
+		 * Truncate global temp table only need RowExclusiveLock
+		 */
+		lockmode = RowExclusiveLock;
+	}
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(rel, 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index fc088d3..b801f14 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -53,6 +53,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -727,6 +728,11 @@ index_create(Relation heapRelation,
 	char		relkind;
 	TransactionId relfrozenxid;
 	MultiXactId relminmxid;
+	bool		skip_create_storage = false;
+
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation) &&
+		!gtt_storage_attached(RelationGetRelid(heapRelation)))
+		skip_create_storage = true;
 
 	/* constraint flags can only be set when a constraint is requested */
 	Assert((constr_flags == 0) ||
@@ -880,6 +886,19 @@ index_create(Relation heapRelation,
 						indexRelationName, RelationGetRelationName(heapRelation))));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temp table use concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot reindex global temporary tables concurrently")));
+
+		/* if global temp table not init storage, then skip build index */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -934,7 +953,8 @@ index_create(Relation heapRelation,
 								mapped_relation,
 								allow_system_table_mods,
 								&relfrozenxid,
-								&relminmxid);
+								&relminmxid,
+								skip_create_storage);
 
 	Assert(relfrozenxid == InvalidTransactionId);
 	Assert(relminmxid == InvalidMultiXactId);
@@ -2027,7 +2047,8 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 * lock (see comments in RemoveRelations), and a non-concurrent DROP is
 	 * more efficient.
 	 */
-	Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+	Assert(!(get_rel_persistence(indexId) == RELPERSISTENCE_TEMP ||
+			 get_rel_persistence(indexId) == RELPERSISTENCE_GLOBAL_TEMP) ||
 		   (!concurrent && !concurrent_lock_mode));
 
 	/*
@@ -2059,6 +2080,17 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/* We allow to drop index on global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot drop index %s or global temporary table %s",
+						RelationGetRelationName(userIndexRelation), RelationGetRelationName(userHeapRelation)),
+				 errhint("Because the index is created on the global temporary table and other backend attached it.")));
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2667,6 +2699,11 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	/* update index stats into localhash and rel_rd_rel for global temp table */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		is_gtt = true;
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2752,21 +2789,35 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
+		if (is_gtt)
+			rel->rd_rel->relpages = (int32) relpages;
+		else if (rd_rel->relpages != (int32) relpages)
 		{
 			rd_rel->relpages = (int32) relpages;
 			dirty = true;
 		}
-		if (rd_rel->reltuples != (float4) reltuples)
+
+		if (is_gtt)
+			rel->rd_rel->reltuples = (float4) reltuples;
+		else if (rd_rel->reltuples != (float4) reltuples)
 		{
 			rd_rel->reltuples = (float4) reltuples;
 			dirty = true;
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+
+		if (is_gtt)
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+		else if (rd_rel->relallvisible != (int32) relallvisible)
 		{
 			rd_rel->relallvisible = (int32) relallvisible;
 			dirty = true;
 		}
+
+		if (is_gtt)
+		{
+			up_gtt_relstats(rel, relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
+		}
 	}
 
 	/*
@@ -2880,6 +2931,15 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, index, val);
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			gtt_force_enable_index(indexRelation);
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3435,6 +3495,16 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	 * we only need to be sure no schema or data changes are going on.
 	 */
 	heapId = IndexGetRelation(indexId, false);
+
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		!gtt_storage_attached(indexId))
+	{
+		/* Correction status flag, and return */
+		SetReindexProcessing(heapId, indexId);
+		ResetReindexProcessing();
+		return;
+	}
+
 	heapRelation = table_open(heapId, ShareLock);
 
 	if (progress)
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 2ec2301..1b6061d 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -655,6 +655,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		/* global temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp relations in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index ec143b6..204a0e0 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,6 +27,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -61,6 +62,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			relOid;			/* InvalidOid if not a global temp rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -115,7 +117,7 @@ AddPendingSync(const RelFileNode *rnode)
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -127,6 +129,8 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		/* global temp table use same storage strategy as local temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -154,6 +158,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->relOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
@@ -165,6 +170,13 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		AddPendingSync(&rnode);
 	}
 
+	/* remember global temp table storage info to localhash */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel)
+	{
+		pending->relOid = RelationGetRelid(rel);
+		remember_gtt_storage_info(rnode, rel);
+	}
+
 	return srel;
 }
 
@@ -201,11 +213,15 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->relOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->relOid = RelationGetRelid(rel);
+
 	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
@@ -602,6 +618,7 @@ smgrDoPendingDeletes(bool isCommit)
 				i = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -631,14 +648,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->relOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -651,9 +672,18 @@ smgrDoPendingDeletes(bool isCommit)
 		smgrdounlinkall(srels, nrels, false);
 
 		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* clean global temp table flags when transaction commit or rollback */
+			if (SmgrIsTemp(srels[i]) &&
+				reloids[i] != InvalidOid &&
+				gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..451cf82
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1491 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  code to create and destroy physical storage for global temparary table
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_relstats_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+int		vacuum_gtt_defer_check_age = 0;
+
+typedef struct gtt_ctl_data
+{
+	LWLock			lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+	Oid			relfilenode;
+	Oid			spcnode;
+
+	/* pg_class stat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+
+	/* pg_statistic */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+
+	Oid			oldrelid;			/* remember the source of relid, before the switch relfilenode. */
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_reset_statistics(gtt_local_hash_entry *entry);
+static void gtt_free_statistics(gtt_local_hash_entry *entry);
+static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok);
+static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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();
+	}
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(gtt_fnode);
+	info.entrysize = action_gtt_shared_hash_entry_size();
+	active_gtt_shared_hash =
+		ShmemInitHash("active gtt shared hash",
+						gtt_shared_ctl->max_entry,
+						gtt_shared_ctl->max_entry,
+						&info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			found;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *)&(fnode), 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_global_temporary_table.")));
+	}
+
+	if (found == false)
+	{
+		int			wordnum;
+
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode				fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	if (!skiplock)
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		if (!skiplock)
+			LWLockRelease(&gtt_shared_ctl->lock);
+
+		if (isCommit)
+			elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+	bms_del_member(entry->map, MyBackendId);
+
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+
+	if (!skiplock)
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+	return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	Bitmapset	*map_copy = NULL;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool		in_use = false;
+	int			num_use = 0;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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;
+	gtt_relfilenode			*new_node = NULL;
+	Oid						relid = RelationGetRelid(rel);
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		/* First time through: initialize the hash table */
+		HASHCTL		ctl;
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_relstats_context =
+			AllocSetContextCreate(CacheMemoryContext,
+							"gtt relstats context",
+							ALLOCSET_DEFAULT_SIZES);
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+	entry = gtt_search_by_relid(relid, true);
+	if (!entry)
+	{
+		bool		found = false;
+		int 		natts = 0;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash,
+						(void *) &relid, HASH_ENTER, &found);
+
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+		entry->natts = 0;
+		entry->attnum = NULL;
+		entry->att_stat_tups = NULL;
+		entry->oldrelid = InvalidOid;
+
+		natts = RelationGetNumberOfAttributes(rel);
+		entry->attnum = palloc0(sizeof(int) * natts);
+		entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+		entry->natts = natts;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			gtt_storage_checkin(relid);
+		}
+	}
+
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* only heap contain transaction information */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	gtt_reset_statistics(entry);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	gtt_relfilenode *d_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+		return;
+	}
+
+	d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+		else if (entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+			gtt_relfilenode *gttnode2 = NULL;
+
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+			Assert(gttnode2->relfilenode == rnode.relNode);
+			Assert(list_length(entry->relfilenode_list) == 1);
+			/* rollback switch relfilenode */
+			gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+									   entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+									   false);
+			/* clean up footprint */
+			entry2->oldrelid = InvalidOid;
+			d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+			Assert(d_rnode);
+		}
+		else
+		{
+			if (entry->relfilenode_list == NIL)
+			{
+				if (entry->relkind == RELKIND_RELATION ||
+					entry->relkind == RELKIND_SEQUENCE)
+					gtt_storage_checkout(relid, false, isCommit);
+
+				gtt_free_statistics(entry);
+				hash_search(gtt_storage_local_hash,
+						(void *) &(relid), HASH_REMOVE, NULL);
+			}
+
+			return;
+		}
+	}
+
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	pfree(d_rnode);
+	if (entry->relfilenode_list == NIL)
+	{
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(relid, false, isCommit);
+
+		if (isCommit && entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			/* clean up footprint */
+			entry2->oldrelid = InvalidOid;
+		}
+
+		gtt_free_statistics(entry);
+		hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_REMOVE, NULL);
+	}
+	else
+		gtt_reset_statistics(entry);
+
+	return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool found = false;
+	gtt_local_hash_entry *entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry)
+		found = true;
+
+	return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS				status;
+	gtt_local_hash_entry		*entry;
+	SMgrRelation	*srels = NULL;
+	Oid				*relids = NULL;
+	char			*relkinds = NULL;
+	int			nrels = 0,
+				nfiles = 0,
+				maxrels = 0,
+				maxfiles = 0,
+				i = 0;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel;
+			RelFileNode 	rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel = smgropen(rnode, MyBackendId);
+
+			if (maxfiles == 0)
+			{
+				maxfiles = 32;
+				srels = palloc(sizeof(SMgrRelation) * maxfiles);
+			}
+			else if (maxfiles <= nfiles)
+			{
+				maxfiles *= 2;
+				srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+			}
+
+			srels[nfiles++] = srel;
+		}
+
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			relids  = palloc(sizeof(Oid) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			relids  = repalloc(relids , sizeof(Oid) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		relkinds[nrels] = entry->relkind;
+		relids[nrels] = entry->relid;
+		nrels++;
+	}
+
+	if (nfiles > 0)
+	{
+		smgrdounlinkall(srels, nfiles, false);
+		for (i = 0; i < nfiles; i++)
+			smgrclose(srels[i]);
+
+		pfree(srels);
+	}
+
+	if (nrels)
+	{
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+		for (i = 0; i < nrels; i++)
+		{
+			if (relkinds[i] == RELKIND_RELATION ||
+				relkinds[i] == RELKIND_SEQUENCE)
+				gtt_storage_checkout(relids[i], true, false);
+		}
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+		pfree(relids);
+		pfree(relkinds);
+	}
+
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+	return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible) 
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+					BlockNumber num_pages,
+					double num_tuples,
+					BlockNumber num_all_visible_pages,
+					TransactionId relfrozenxid,
+					TransactionId relminmxid)
+{
+	Oid		relid = RelationGetRelid(relation);
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (num_pages >= 0 &&
+		gtt_rnode->relpages != (int32)num_pages)
+		gtt_rnode->relpages = (int32)num_pages;
+
+	if (num_tuples >= 0 &&
+		gtt_rnode->reltuples != (float4)num_tuples)
+		gtt_rnode->reltuples = (float4)num_tuples;
+
+	/* only heap contain transaction information and relallvisible */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (gtt_rnode->relallvisible >= 0 &&
+			gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+		{
+			gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+		}
+
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNewTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible) 
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	MemoryContext			oldcontext;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return;
+
+	if (entry->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	Assert(entry->relid == reloid);
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == 0)
+		{
+			entry->attnum[i] = attnum;
+			break;
+		}
+		else if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < entry->natts);
+	Assert(entry->att_stat_tups[i] == NULL);
+	entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return NULL;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			return entry->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int			i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	HeapTuple	tuple;
+	Relation	rel = NULL;
+	int		attnum = PG_GETARG_INT32(1);
+	Oid		reloid = PG_GETARG_OID(0);
+	char		rel_persistence;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	  tupdesc;
+	MemoryContext oldcontext;
+	Tuplestorestate *tupstore;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+
+	relation_close(rel, NoLock);
+	relation_close(pg_tatistic, AccessShareLock);
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate	*tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Oid			relnode = 0;
+	Relation	rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[6];
+		bool	isnull[6];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(reloid);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(RelationGetRelid(index)));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid relOid = index->rd_index->indrelid;
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	if (gtt_storage_attached(relOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int			i;
+	Oid			toastrelid;
+
+	if (!(operation == CMD_UTILITY || operation == CMD_INSERT))
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+
+		index_build(relation, index, info, true, false);
+	}
+
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid 		indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo 	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+static void
+gtt_reset_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+
+		entry->attnum[i] = 0;
+	}
+
+	return;
+}
+
+static void
+gtt_free_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (entry->attnum)
+		pfree(entry->attnum);
+
+	if (entry->att_stat_tups)
+		pfree(entry->att_stat_tups);
+
+	return;
+}
+
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
+void
+gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint)
+{
+	gtt_local_hash_entry	*entry1;
+	gtt_local_hash_entry	*entry2;
+	gtt_relfilenode			*gtt_rnode1 = NULL;
+	gtt_relfilenode			*gtt_rnode2 = NULL;
+	MemoryContext			oldcontext;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry1 = gtt_search_by_relid(rel1, false);
+	gtt_rnode1 = gtt_search_relfilenode(entry1, relfilenode1, false);
+
+	entry2 = gtt_search_by_relid(rel2, false);
+	gtt_rnode2 = gtt_search_relfilenode(entry2, relfilenode2, false);
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	entry1->relfilenode_list = list_delete_ptr(entry1->relfilenode_list, gtt_rnode1);
+	entry2->relfilenode_list = lappend(entry2->relfilenode_list, gtt_rnode1);
+
+	entry2->relfilenode_list = list_delete_ptr(entry2->relfilenode_list, gtt_rnode2);
+	entry1->relfilenode_list = lappend(entry1->relfilenode_list, gtt_rnode2);
+	MemoryContextSwitchTo(oldcontext);
+
+	if (footprint)
+	{
+		entry1->oldrelid = rel2;
+		entry2->oldrelid = rel1;
+	}
+
+	return;
+}
+
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok)
+{
+	gtt_relfilenode			*rnode = NULL;
+	ListCell				*lc;
+
+	Assert(entry);
+
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == relfilenode)
+		{
+			rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (!missing_ok && rnode == NULL)
+		elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+	return rnode;
+}
+
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry == NULL && !missing_ok)
+		elog(ERROR, "relid %u not found in local hash", relid);
+
+	return entry;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 5314e93..0fe8492 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM 
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 924ef37..db12eef 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -184,6 +185,13 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -586,14 +594,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 		 * pg_statistic for columns we didn't process, we leave them alone.)
 		 */
 		update_attstats(RelationGetRelid(onerel), inh,
-						attr_cnt, vacattrstats);
+						attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
 
 		for (ind = 0; ind < nindexes; ind++)
 		{
 			AnlIndexData *thisdata = &indexdata[ind];
 
 			update_attstats(RelationGetRelid(Irel[ind]), false,
-							thisdata->attr_cnt, thisdata->vacattrstats);
+							thisdata->attr_cnt, thisdata->vacattrstats,
+							RelationGetRelPersistence(Irel[ind]));
 		}
 
 		/*
@@ -1456,7 +1465,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
  *		by taking a self-exclusive lock on the relation in analyze_rel().
  */
 static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
 {
 	Relation	sd;
 	int			attno;
@@ -1558,31 +1567,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/* Update column statistic to localhash, not catalog */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 04d12a7..42c82c8 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/toasting.h"
 #include "commands/cluster.h"
 #include "commands/progress.h"
@@ -72,6 +73,12 @@ static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
 							bool verbose, bool *pSwapToastByContent,
 							TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
 static List *get_tables_to_cluster(MemoryContext cluster_context);
+static void gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables);
 
 
 /*---------------------------------------------------------------------------
@@ -366,6 +373,14 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap) &&
+		!gtt_storage_attached(RelationGetRelid(OldHeap)))
+	{
+		relation_close(OldHeap, AccessExclusiveLock);
+		pgstat_progress_end_command();
+		return;
+	}
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
@@ -750,6 +765,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	BlockNumber num_pages;
 	int			elevel = verbose ? INFO : DEBUG2;
 	PGRUsage	ru0;
+	bool		is_gtt = false;
+	uint32		gtt_relfrozenxid = 0;
+	uint32		gtt_relminmxid = 0;
 
 	pg_rusage_init(&ru0);
 
@@ -763,6 +781,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	else
 		OldIndex = NULL;
 
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		is_gtt = true;
+
 	/*
 	 * Their tuple descriptors should be exactly alike, but here we only need
 	 * assume that they have the same number of columns.
@@ -830,20 +851,37 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 						  &OldestXmin, &FreezeXid, NULL, &MultiXactCutoff,
 						  NULL);
 
-	/*
-	 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
-	 * backwards, so take the max.
-	 */
-	if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
-		TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
-		FreezeXid = OldHeap->rd_rel->relfrozenxid;
+	if (is_gtt)
+	{
+		get_gtt_relstats(OIDOldHeap,
+					NULL, NULL, NULL,
+					&gtt_relfrozenxid, &gtt_relminmxid);
 
-	/*
-	 * MultiXactCutoff, similarly, shouldn't go backwards either.
-	 */
-	if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
-		MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
-		MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+		if (TransactionIdIsValid(gtt_relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, gtt_relfrozenxid))
+			FreezeXid = gtt_relfrozenxid;
+
+		if (MultiXactIdIsValid(gtt_relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, gtt_relminmxid))
+			MultiXactCutoff = gtt_relminmxid;
+	}
+	else
+	{
+		/*
+		 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
+		 * backwards, so take the max.
+		 */
+		if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
+			FreezeXid = OldHeap->rd_rel->relfrozenxid;
+
+		/*
+		 * MultiXactCutoff, similarly, shouldn't go backwards either.
+		 */
+		if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
+			MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+	}
 
 	/*
 	 * Decide whether to use an indexscan or seqscan-and-optional-sort to scan
@@ -911,6 +949,12 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	table_close(OldHeap, NoLock);
 	table_close(NewHeap, NoLock);
 
+	if (is_gtt)
+	{
+		CommandCounterIncrement();
+		return;
+	}
+
 	/* Update pg_class to reflect the correct values of pages and tuples. */
 	relRelation = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1346,10 +1390,21 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 	 * Swap the contents of the heap relations (including any toast tables).
 	 * Also set old heap's relfrozenxid to frozenXid.
 	 */
-	swap_relation_files(OIDOldHeap, OIDNewHeap,
+	if (newrelpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		Assert(!is_system_catalog);
+		gtt_swap_relation_files(OIDOldHeap, OIDNewHeap,
+								(OIDOldHeap == RelationRelationId),
+								swap_toast_by_content, is_internal,
+								frozenXid, cutoffMulti, mapped_tables);
+	}
+	else
+	{
+		swap_relation_files(OIDOldHeap, OIDNewHeap,
 						(OIDOldHeap == RelationRelationId),
 						swap_toast_by_content, is_internal,
 						frozenXid, cutoffMulti, mapped_tables);
+	}
 
 	/*
 	 * If it's a system catalog, queue a sinval message to flush all catcaches
@@ -1557,3 +1612,141 @@ get_tables_to_cluster(MemoryContext cluster_context)
 
 	return rvs;
 }
+
+static void
+gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables)
+{
+	Relation	relRelation;
+	Oid			relfilenode1,
+				relfilenode2;
+	Relation	rel1;
+	Relation	rel2;
+
+	relRelation = table_open(RelationRelationId, RowExclusiveLock);
+
+	rel1 = relation_open(r1, AccessExclusiveLock);
+	rel2 = relation_open(r2, AccessExclusiveLock);
+
+	relfilenode1 = gtt_fetch_current_relfilenode(r1);
+	relfilenode2 = gtt_fetch_current_relfilenode(r2);
+
+	Assert(OidIsValid(relfilenode1) && OidIsValid(relfilenode2));
+	gtt_switch_rel_relfilenode(r1, relfilenode1, r2, relfilenode2, true);
+
+	CacheInvalidateRelcache(rel1);
+	CacheInvalidateRelcache(rel2);
+
+	InvokeObjectPostAlterHookArg(RelationRelationId, r1, 0,
+								 InvalidOid, is_internal);
+	InvokeObjectPostAlterHookArg(RelationRelationId, r2, 0,
+								 InvalidOid, true);
+
+	if (rel1->rd_rel->reltoastrelid || rel2->rd_rel->reltoastrelid)
+	{
+		if (swap_toast_by_content)
+		{
+			if (rel1->rd_rel->reltoastrelid && rel2->rd_rel->reltoastrelid)
+			{
+				gtt_swap_relation_files(rel1->rd_rel->reltoastrelid,
+									rel2->rd_rel->reltoastrelid,
+									target_is_pg_class,
+									swap_toast_by_content,
+									is_internal,
+									frozenXid,
+									cutoffMulti,
+									mapped_tables);
+			}
+			else
+				elog(ERROR, "cannot swap toast files by content when there's only one");
+		}
+		else
+		{
+			ObjectAddress baseobject,
+						toastobject;
+			long		count;
+
+			if (IsSystemRelation(rel1))
+				elog(ERROR, "cannot swap toast files by links for system catalogs");
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel1->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel2->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+
+			/* Register new dependencies */
+			baseobject.classId = RelationRelationId;
+			baseobject.objectSubId = 0;
+			toastobject.classId = RelationRelationId;
+			toastobject.objectSubId = 0;
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r1;
+				toastobject.objectId = rel1->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r2;
+				toastobject.objectId = rel2->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+		}
+	}
+
+	if (swap_toast_by_content &&
+		rel1->rd_rel->relkind == RELKIND_TOASTVALUE &&
+		rel2->rd_rel->relkind == RELKIND_TOASTVALUE)
+	{
+		Oid			toastIndex1,
+					toastIndex2;
+
+		/* Get valid index for each relation */
+		toastIndex1 = toast_get_valid_index(r1,
+											AccessExclusiveLock);
+		toastIndex2 = toast_get_valid_index(r2,
+											AccessExclusiveLock);
+
+		gtt_swap_relation_files(toastIndex1,
+							toastIndex2,
+							target_is_pg_class,
+							swap_toast_by_content,
+							is_internal,
+							InvalidTransactionId,
+							InvalidMultiXactId,
+							mapped_tables);
+	}
+
+	relation_close(rel1, NoLock);
+	relation_close(rel2, NoLock);
+
+	table_close(relRelation, RowExclusiveLock);
+
+	RelationCloseSmgrByOid(r1);
+	RelationCloseSmgrByOid(r2);
+
+	CommandCounterIncrement();
+}
+
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 3e199bd..2155034 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -27,6 +27,7 @@
 #include "catalog/dependency.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -1062,7 +1063,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, stmt->filename, stmt->is_program,
@@ -2784,6 +2785,8 @@ CopyFrom(CopyState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	estate->es_result_relations = resultRelInfo;
 	estate->es_num_result_relations = 1;
 	estate->es_result_relation_info = resultRelInfo;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 2baca12..5338db9 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -521,6 +521,7 @@ DefineIndex(Oid relationId,
 	Snapshot	snapshot;
 	int			save_nestlevel = -1;
 	int			i;
+	char		rel_persistence;
 
 	/*
 	 * Some callers need us to run with an empty default_tablespace; this is a
@@ -542,7 +543,9 @@ DefineIndex(Oid relationId,
 	 * is more efficient.  Do this before any use of the concurrent option is
 	 * done.
 	 */
-	if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+	rel_persistence = get_rel_persistence(relationId);
+	if (stmt->concurrent &&
+		!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		concurrent = true;
 	else
 		concurrent = false;
@@ -2460,7 +2463,8 @@ ReindexIndex(RangeVar *indexRelation, int options, bool concurrent)
 	persistence = irel->rd_rel->relpersistence;
 	index_close(irel, NoLock);
 
-	if (concurrent && persistence != RELPERSISTENCE_TEMP)
+	if (concurrent &&
+		!(persistence == RELPERSISTENCE_TEMP || persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		ReindexRelationConcurrently(indOid, options);
 	else
 		reindex_index(indOid, false, persistence,
@@ -2546,6 +2550,7 @@ ReindexTable(RangeVar *relation, int options, bool concurrent)
 {
 	Oid			heapOid;
 	bool		result;
+	char		rel_persistence;
 
 	/*
 	 * The lock level used here should match reindex_relation().
@@ -2560,7 +2565,9 @@ ReindexTable(RangeVar *relation, int options, bool concurrent)
 									   0,
 									   RangeVarCallbackOwnsTable, NULL);
 
-	if (concurrent && get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+	rel_persistence = get_rel_persistence(heapOid);
+	if (concurrent &&
+		!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 	{
 		result = ReindexRelationConcurrently(heapOid, options);
 
@@ -2761,12 +2768,15 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 	foreach(l, relids)
 	{
 		Oid			relid = lfirst_oid(l);
+		char		rel_persistence;
 
 		StartTransactionCommand();
 		/* functions in indexes may want a snapshot set */
 		PushActiveSnapshot(GetTransactionSnapshot());
 
-		if (concurrent && get_rel_persistence(relid) != RELPERSISTENCE_TEMP)
+		rel_persistence = get_rel_persistence(relid);
+		if (concurrent &&
+			!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		{
 			(void) ReindexRelationConcurrently(relid, options);
 			/* ReindexRelationConcurrently() does the verbose output */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..234ffa6 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -94,7 +96,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -222,7 +225,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* now initialize the sequence's data */
 	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -327,7 +330,7 @@ ResetSequence(Oid seq_relid)
 	/*
 	 * Insert the modified tuple into the new storage file.
 	 */
-	fill_seq_with_data(seq_rel, tuple);
+	fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -340,17 +343,21 @@ ResetSequence(Oid seq_relid)
  * Initialize a sequence's relation with the specified tuple as content
  */
 static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 	sequence_magic *sm;
 	OffsetNumber offnum;
+	bool lockBuffer = false;
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ReadBuffer(rel, P_NEW);
-	Assert(BufferGetBlockNumber(buf) == 0);
+	if (buf == InvalidBuffer)
+	{
+		buf = ReadBuffer(rel, P_NEW);
+		Assert(BufferGetBlockNumber(buf) == 0);
+		lockBuffer = true;
+	}
 
 	page = BufferGetPage(buf);
 
@@ -360,7 +367,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* Now insert sequence tuple */
 
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+	if (lockBuffer)
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +418,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	END_CRIT_SECTION();
 
-	UnlockReleaseBuffer(buf);
+	if (lockBuffer)
+		UnlockReleaseBuffer(buf);
 }
 
 /*
@@ -451,6 +460,15 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 
 	init_sequence(relid, &elm, &seqrel);
 
+	if (RELATION_IS_GLOBAL_TEMP(seqrel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(seqrel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary sequence %s when other backend attached it.",
+						RelationGetRelationName(seqrel))));
+	}
+
 	rel = table_open(SequenceRelationId, RowExclusiveLock);
 	seqtuple = SearchSysCacheCopy1(SEQRELID,
 								   ObjectIdGetDatum(relid));
@@ -502,7 +520,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * Insert the modified tuple into the new storage file.
 		 */
-		fill_seq_with_data(seqrel, newdatatuple);
+		fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
 	}
 
 	/* process OWNED BY if given */
@@ -611,7 +629,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +954,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1154,6 +1172,13 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1954,3 +1979,46 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+void
+gtt_init_seq(Relation rel)
+{
+	/* Initialize sequence for global temporary tables */
+	Datum		value[SEQ_COL_LASTCOL] = {0};
+	bool		null[SEQ_COL_LASTCOL] = {false};
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f79044f..09e2dda 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -556,6 +557,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 								  Relation partitionTbl);
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static OnCommitAction gtt_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -601,6 +603,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -611,8 +614,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")));
@@ -642,7 +647,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	/* global temp table same as local temp table */
+	if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+		 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -743,6 +750,55 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* global temp table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* check parent table*/
+		if (inheritOids)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			table_close(relation, NoLock);
+		}
+
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temp table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1330,6 +1386,7 @@ RemoveRelations(DropStmt *drop)
 		Oid			relOid;
 		ObjectAddress obj;
 		struct DropRelationCallbackState state;
+		char		rel_persistence;
 
 		/*
 		 * These next few steps are a great deal like relation_openrv, but we
@@ -1363,8 +1420,9 @@ RemoveRelations(DropStmt *drop)
 		 * Decide if concurrent mode needs to be used here or not.  The
 		 * relation persistence cannot be known without its OID.
 		 */
+		rel_persistence = get_rel_persistence(relOid);
 		if (drop->concurrent &&
-			get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+			!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		{
 			Assert(list_length(drop->objects) == 1 &&
 				   drop->removeType == OBJECT_INDEX);
@@ -1559,7 +1617,12 @@ ExecuteTruncate(TruncateStmt *stmt)
 		Relation	rel;
 		bool		recurse = rv->inh;
 		Oid			myrelid;
-		LOCKMODE	lockmode = AccessExclusiveLock;
+		LOCKMODE	lockmode;
+
+		if (rv->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+		else
+			lockmode = AccessExclusiveLock;
 
 		myrelid = RangeVarGetRelidExtended(rv, lockmode,
 										   0, RangeVarCallbackForTruncate,
@@ -1819,6 +1882,10 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
 		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 			continue;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			continue;
+
 		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
@@ -3622,6 +3689,16 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -4897,6 +4974,38 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot rewrite temporary tables of other sessions")));
 
+			if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+			{
+				if (tab->chgPersistence)
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot change global temporary table persistence setting")));
+
+				/* gtt may not attached, create it */
+				if(!gtt_storage_attached(tab->relid))
+				{
+					ResultRelInfo *resultRelInfo;
+					MemoryContext oldcontext;
+					MemoryContext ctx_alter_gtt;
+
+					ctx_alter_gtt = AllocSetContextCreate(CurrentMemoryContext,
+											"gtt alter table", ALLOCSET_DEFAULT_SIZES);
+
+					oldcontext = MemoryContextSwitchTo(ctx_alter_gtt);
+					resultRelInfo = makeNode(ResultRelInfo);
+					InitResultRelInfo(resultRelInfo, OldHeap,
+									1, NULL, 0);
+					if (resultRelInfo->ri_RelationDesc->rd_rel->relhasindex &&
+						resultRelInfo->ri_IndexRelationDescs == NULL)
+						ExecOpenIndices(resultRelInfo, false);
+
+					init_gtt_storage(CMD_UTILITY, resultRelInfo);
+					ExecCloseIndices(resultRelInfo);
+					MemoryContextSwitchTo(oldcontext);
+					MemoryContextDelete(ctx_alter_gtt);
+				}
+			}
+
 			/*
 			 * Select destination tablespace (same as original unless user
 			 * requested a change)
@@ -8281,6 +8390,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -12837,6 +12952,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -13039,6 +13157,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temp table");
+
 	/*
 	 * No work if no change in tablespace.
 	 */
@@ -13413,7 +13534,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,
@@ -14820,7 +14941,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",
@@ -17477,3 +17600,36 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
 		table_close(rel, NoLock);
 	}
 }
+
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			bool	res = false;
+			char	*sval = defGetString(def);
+
+			if (!parse_bool(sval, &res))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
+
+			if (res)
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index d32de23..a448737 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"
@@ -1215,6 +1216,17 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	 /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		is_gtt = true;
+		up_gtt_relstats(relation,
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1228,17 +1240,26 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relpages = (int32) num_pages;
+	else if (pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (is_gtt)
+		relation->rd_rel->reltuples = (float4) num_tuples;
+	else if (pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	else if (pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1283,7 +1304,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1294,7 +1316,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1392,6 +1415,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
@@ -1449,6 +1476,42 @@ 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	safe_age;
+		TransactionId	oldest_gtt_frozenxid =
+			list_all_session_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid))
+		{
+			safe_age = oldest_gtt_frozenxid + vacuum_gtt_defer_check_age;
+			if (safe_age < FirstNormalTransactionId)
+				safe_age += FirstNormalTransactionId;
+
+			/*
+			 * We tolerate that the minimum age of gtt is less than
+			 * the minimum age of conventional tables, otherwise it will
+			 * throw warning message.
+			 */
+			if (TransactionIdIsNormal(safe_age) &&
+				TransactionIdPrecedes(safe_age, newFrozenXid))
+			{
+				ereport(WARNING,
+					(errmsg("global temp table oldest relfrozenxid %u is the oldest in the entire db",
+							oldest_gtt_frozenxid),
+					 errdetail("The oldest relfrozenxid in pg_class is %u", newFrozenXid),
+					 errhint("If they differ greatly, please consider cleaning up the data in global temp table.")));
+			}
+
+			if (TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+				newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1776,6 +1839,15 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6e65103..04706ee 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -530,6 +530,11 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 4fdffad..0aa7559 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index fb6ce49..06d9237 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
@@ -545,6 +546,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	init_gtt_storage(mtstate->operation, leaf_part_rri);
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 20a4c47..e9f6dcd 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,6 +42,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -2379,6 +2380,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			ExecOpenIndices(resultRelInfo,
 							node->onConflictAction != ONCONFLICT_NONE);
 
+		init_gtt_storage(operation, resultRelInfo);
+
 		/*
 		 * If this is an UPDATE and a BEFORE UPDATE trigger is present, the
 		 * trigger itself might modify the partition-key values. So arrange
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index c4e1967..b4b05fa 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 14f3fd4..be75579 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6417,7 +6417,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
+	/* global temp table is same as local temp table */
 	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 2554502..04fb5d4 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -28,6 +28,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temp table */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 401da5d..47fb239 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2594,6 +2594,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 4ff3509..c57aa8d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3315,17 +3315,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; }
@@ -11407,19 +11401,13 @@ OptTempTableName:
 				}
 			| GLOBAL TEMPORARY opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED opt_table qualified_name
 				{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b875a50..123945b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 0e4caa6..1b21fa2 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
@@ -3108,6 +3111,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		cxt.isforeign = false;
 	}
 	cxt.relation = stmt->relation;
+	cxt.relation->relpersistence = RelationGetRelPersistence(rel);
 	cxt.rel = rel;
 	cxt.inhRelations = NIL;
 	cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 9c7d4b0..03173a6 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2088,6 +2088,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);
@@ -2154,7 +2159,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 29c9208..a74c207 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -53,6 +54,7 @@
 #include "utils/rel.h"
 #include "utils/resowner_private.h"
 #include "utils/timestamp.h"
+#include "utils/guc.h"
 
 
 /* Note: these two macros only work on shared buffers, not local ones! */
@@ -2819,6 +2821,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d5..7adb969 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -21,6 +21,7 @@
 #include "access/nbtree.h"
 #include "access/subtrans.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, active_gtt_shared_hash_size());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* global temporary table */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 3c2b369..092bd80 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -63,6 +63,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -4084,3 +4085,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/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 2fa90cc..22f0f81 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -177,7 +177,9 @@ static const char *const BuiltinTrancheNames[] = {
 	/* LWTRANCHE_PARALLEL_APPEND: */
 	"ParallelAppend",
 	/* LWTRANCHE_PER_XACT_PREDICATE_LIST: */
-	"PerXactPredicateList"
+	"PerXactPredicateList",
+	/* LWTRANCHE_GTT_CTL */
+	"GlobalTempTableControl"
 };
 
 StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index e57fcd2..e0e5691 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 2320c06..234e20c 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -993,6 +993,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index be08eb4..c6dcb0a 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -108,6 +108,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -4884,12 +4885,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))
 							{
@@ -5014,15 +5028,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))
 		{
@@ -6443,6 +6469,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;
@@ -6460,6 +6487,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,
@@ -6471,6 +6505,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;
@@ -6486,6 +6522,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,
@@ -7404,6 +7447,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))
@@ -7416,6 +7461,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 =
@@ -7428,6 +7481,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.
@@ -7447,6 +7502,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 f3bf413..1aabd25 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -2988,6 +2989,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 0b9eb00..a098f4b 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -65,6 +65,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1133,6 +1134,24 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1187,6 +1206,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			gtt_fix_index_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -1317,7 +1337,17 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+			if (newrelnode != InvalidOid &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2246,6 +2276,8 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		gtt_fix_index_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3472,6 +3504,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3579,28 +3615,34 @@ void
 RelationSetNewRelfilenode(Relation relation, char persistence)
 {
 	Oid			newrelfilenode;
-	Relation	pg_class;
-	HeapTuple	tuple;
+	Relation	pg_class = NULL;
+	HeapTuple	tuple = NULL;
 	Form_pg_class classform;
 	MultiXactId minmulti = InvalidMultiXactId;
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
+	bool		modify_pg_class = !RELATION_IS_GLOBAL_TEMP(relation);
 
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
 
-	/*
-	 * Get a writable copy of the pg_class tuple for the given relation.
-	 */
-	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+	if (modify_pg_class)
+	{
+		/*
+		 * Get a writable copy of the pg_class tuple for the given relation.
+		 */
+		pg_class = table_open(RelationRelationId, RowExclusiveLock);
 
-	tuple = SearchSysCacheCopy1(RELOID,
-								ObjectIdGetDatum(RelationGetRelid(relation)));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "could not find tuple for relation %u",
-			 RelationGetRelid(relation));
-	classform = (Form_pg_class) GETSTRUCT(tuple);
+		tuple = SearchSysCacheCopy1(RELOID,
+									ObjectIdGetDatum(RelationGetRelid(relation)));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u",
+				 RelationGetRelid(relation));
+		classform = (Form_pg_class) GETSTRUCT(tuple);
+	}
+	else
+		memset(&classform, 0, sizeof(classform));
 
 	/*
 	 * Schedule unlinking of the old storage at transaction commit.
@@ -3626,7 +3668,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;
@@ -3646,6 +3688,15 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 			break;
 	}
 
+	if (!modify_pg_class)
+	{
+		Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+		Assert(RELATION_IS_GLOBAL_TEMP(relation));
+		Assert(!RelationIsMapped(relation));
+		relation->rd_node.relNode = relnode;
+		CacheInvalidateRelcache(relation);
+	}
 	/*
 	 * If we're dealing with a mapped index, pg_class.relfilenode doesn't
 	 * change; instead we have to send the update to the relation mapper.
@@ -3655,7 +3706,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	 * possibly-inaccurate values of relpages etc, but those will be fixed up
 	 * later.
 	 */
-	if (RelationIsMapped(relation))
+	else if (RelationIsMapped(relation))
 	{
 		/* This case is only supported for indexes */
 		Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3701,9 +3752,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 		CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 	}
 
-	heap_freetuple(tuple);
+	if (modify_pg_class)
+	{
+		heap_freetuple(tuple);
 
-	table_close(pg_class, RowExclusiveLock);
+		table_close(pg_class, RowExclusiveLock);
+	}
 
 	/*
 	 * Make the pg_class row change or relation map change visible.  This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 3a802d8..5395b56 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -37,6 +37,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "commands/prepare.h"
 #include "commands/trigger.h"
@@ -142,6 +143,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,
@@ -2051,6 +2064,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."),
@@ -2558,6 +2580,16 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"vacuum_gtt_defer_check_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("The defer check age of GTT, used to check expired data after vacuum."),
+			NULL
+		},
+		&vacuum_gtt_defer_check_age,
+		10000, 0, 1000000,
+		NULL, NULL, NULL
+	},
+
 	/*
 	 * See also CheckRequiredParameterValues() if this parameter changes
 	 */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a41a3db..9b4b4f1 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2416,6 +2416,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
 		dopt->no_unlogged_table_data)
 		return;
 
+	/* Don't dump data in global temp table/sequence */
+	if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		return;
+
 	/* Check that the data is not explicitly excluded */
 	if (simple_oid_list_member(&tabledata_exclude_oids,
 							   tbinfo->dobj.catId.oid))
@@ -15748,6 +15752,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
 		char	   *foreign = "";
+		char	   *table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15801,9 +15806,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);
 
@@ -16197,7 +16208,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
-			 tbinfo->relkind == RELKIND_MATVIEW))
+			 tbinfo->relkind == RELKIND_MATVIEW) &&
+			 tbinfo->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
 		{
 			appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
 			appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
@@ -17102,6 +17114,7 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	PQExpBuffer query = createPQExpBuffer();
 	PQExpBuffer delqry = createPQExpBuffer();
 	char	   *qseqname;
+	bool	   global_temp_seq = false;
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
@@ -17111,9 +17124,11 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
-						  "FROM pg_catalog.pg_sequence "
-						  "WHERE seqrelid = '%u'::oid",
+						  "seqcache, seqcycle, c.relpersistence "
+						  "FROM pg_catalog.pg_sequence s, "
+						  "pg_catalog.pg_class c "
+						  "WHERE seqrelid = '%u'::oid "
+						  "and s.seqrelid = c.oid",
 						  tbinfo->dobj.catId.oid);
 	}
 	else if (fout->remoteVersion >= 80400)
@@ -17158,6 +17173,9 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
 
+	if (fout->remoteVersion >= 130000)
+		global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
 	if (strcmp(seqtype, "smallint") == 0)
@@ -17232,9 +17250,13 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	}
 	else
 	{
-		appendPQExpBuffer(query,
-						  "CREATE SEQUENCE %s\n",
-						  fmtQualifiedDumpable(tbinfo));
+		appendPQExpBuffer(query, "CREATE ");
+
+		if (global_temp_seq)
+			appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+		appendPQExpBuffer(query, "SEQUENCE %s\n",
+								  fmtQualifiedDumpable(tbinfo));
 
 		if (strcmp(seqtype, "bigint") != 0)
 			appendPQExpBuffer(query, "    AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 00aef85..9ad71c8 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -84,7 +84,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -157,7 +157,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 7e524ea..371168f 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -441,22 +443,46 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 	 * output, so we have to copy that system table.  It's easiest to do that
 	 * by treating it as a user table.
 	 */
-	snprintf(query + strlen(query), sizeof(query) - strlen(query),
-			 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
-			 "  SELECT c.oid, 0::oid, 0::oid "
-			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
-			 "         ON c.relnamespace = n.oid "
-			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
-	/* exclude possible orphaned temp tables */
-			 "    ((n.nspname !~ '^pg_temp_' AND "
-			 "      n.nspname !~ '^pg_toast_temp_' AND "
-			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
-			 "                        'binary_upgrade', 'pg_toast') AND "
-			 "      c.oid >= %u::pg_catalog.oid) OR "
-			 "     (n.nspname = 'pg_catalog' AND "
-			 "      relname IN ('pg_largeobject') ))), ",
-			 FirstNormalObjectId);
+	if (skip_gtt)
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude global temp tables */
+				 "    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
+	else
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
 
 	/*
 	 * Add a CTE that collects OIDs of toast tables belonging to the tables
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 70194eb..8dd9e66 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -411,7 +411,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -642,7 +642,9 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -653,7 +655,9 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 8b90cef..d155205 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -389,7 +389,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index cd39b91..03705da 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3738,7 +3738,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index eb01885..1887421 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1023,6 +1023,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2394,6 +2396,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2602,6 +2607,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index cbfdfe2..be39472 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -59,7 +59,8 @@ extern Relation heap_create(const char *relname,
 							bool mapped_relation,
 							bool allow_system_table_mods,
 							TransactionId *relfrozenxid,
-							MultiXactId *relminmxid);
+							MultiXactId *relminmxid,
+							bool skip_create_storage);
 
 extern Oid	heap_create_with_catalog(const char *relname,
 									 Oid relnamespace,
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 78b33b2..4be89f6 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -168,6 +168,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 38295ac..66e6c42 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5560,6 +5560,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4388',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4389',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4390',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4391',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 30c38e0..7ff2408 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,7 +22,7 @@
 /* GUC variables */
 extern int	wal_skip_threshold;
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationPreTruncate(Relation rel);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..902375d
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern int		vacuum_gtt_defer_check_age;
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Relation relation,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void gtt_force_enable_index(Relation index);
+extern void gtt_fix_index_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+extern void gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index e2638ab..89a5ce4 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 3f88683..f870e9a 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index af9b417..6e16f99 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -218,6 +218,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_SHARED_TIDBITMAP,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_PER_XACT_PREDICATE_LIST,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index b20e2ad..97bf2df 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -124,6 +124,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId session_gtt_frozenxid;	/* session level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 2819282..363fc33 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -282,6 +282,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 0b5957b..0720a4c 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	struct SMgrRelationData *rd_smgr;	/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -305,6 +305,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -570,11 +571,13 @@ typedef struct ViewOptions
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -582,6 +585,7 @@ typedef struct ViewOptions
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -594,6 +598,14 @@ typedef struct ViewOptions
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is either local temp relation of this session
+ * 		or global temp relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -637,6 +649,17 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..ca2d135
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,14 @@
+reset search_path;
+select pg_sleep(5);
+ pg_sleep 
+----------
+ 
+(1 row)
+
+drop schema gtt cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
+drop cascades to table gtt.gtt_with_seq
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..d6a675a
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,383 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+ n 
+---
+ 9
+(1 row)
+
+commit;
+-- 0 row
+select * from gtt6;
+ n 
+---
+(0 rows)
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temp table
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  could not create global temporary table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR:  insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count 
+-------
+     1
+(1 row)
+
+select count(*) from orders;
+ count 
+-------
+     0
+(1 row)
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  2 |  3
+(2 rows)
+
+commit;
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+select * from gtt_s_2 order by c1;
+ c1 | c2 
+----+----
+  1 |  2
+  2 |  4
+(2 rows)
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 1)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 300)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 33 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test_new
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.gtt6
+drop cascades to table gtt_function.foo
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to sequence gtt_function.seq_1
+drop cascades to table gtt_function.gtt_s_1
+drop cascades to table gtt_function.gtt_s_2
+drop cascades to table gtt_function.gt1
+drop cascades to table gtt_function.gtt_test1
+drop cascades to table gtt_function.gtt_test2
+drop cascades to table gtt_function.gtt_test3
+drop cascades to table gtt_function.gtt_test4
+drop cascades to table gtt_function.gtt_test5
+drop cascades to table gtt_function.gtt_test6
+drop cascades to table gtt_function.gtt_test7
+drop cascades to table gtt_function.gtt_test8
+drop cascades to table gtt_function.gtt_test9
+drop cascades to table gtt_function.gtt_test10
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..0646aae
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,90 @@
+set search_path=gtt,sys;
+select nextval('gtt_with_seq_c2_seq');
+ nextval 
+---------
+       1
+(1 row)
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..2f084be
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,343 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+rollback;
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+commit;
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          114688 |                 614400
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |            49152 |         49152 |                  49152
+(2 rows)
+
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2000
+(1 row)
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+ c1 | c2 
+----+----
+  1 |  1
+(1 row)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..8c0c376
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,10 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..4420fdb
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,80 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+ gtt_stats  | gtt_pkey  |        1 |         0 |             0
+(2 rows)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+reindex table gtt;
+reindex index gtt_pkey;
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+ gtt_stats  | gtt_pkey  |       30 |     10000 |             0
+(2 rows)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index b813e32..400a161 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1349,6 +1349,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 026ea88..592ba9c 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..2c8e586
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,8 @@
+
+
+reset search_path;
+
+select pg_sleep(5);
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..81f0bfc
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,253 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+commit;
+-- 0 row
+select * from gtt6;
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+commit;
+select * from gtt_s_1 order by c1;
+select * from gtt_s_2 order by c1;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d05745e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,44 @@
+
+
+set search_path=gtt,sys;
+
+select nextval('gtt_with_seq_c2_seq');
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..39cca5e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,154 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+rollback;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+commit;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+select count(*) from gtt_t_kenyon;
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..dbe84d1
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,19 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..d61b0ff
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,46 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+reindex table gtt;
+
+reindex index gtt_pkey;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#276Pavel Stehule
pavel.stehule@gmail.com
In reply to: 曾文旌 (#275)
Re: [Proposal] Global temporary tables

Hi

GTT Merge the latest PGMaster and resolves conflicts.

I tested it and it looks fine. I think it is very usable in current form,
but still there are some issues:

postgres=# create global temp table foo(a int);
CREATE TABLE
postgres=# insert into foo values(10);
INSERT 0 1
postgres=# alter table foo add column x int;
ALTER TABLE
postgres=# analyze foo;
WARNING: reloid 16400 not support update attstat after add colunm
WARNING: reloid 16400 not support update attstat after add colunm
ANALYZE

Please, can you summarize what is done, what limits are there, what can be
implemented hard, what can be implemented easily?

I found one open question - how can be implemented table locks - because
data is physically separated, then we don't need table locks as protection
against race conditions.

Now, table locks are implemented on a global level. So exclusive lock on
GTT in one session block insertion on the second session. Is it expected
behaviour? It is safe, but maybe it is too strict.

We should define what table lock is meaning on GTT.

Regards

Pavel

Show quoted text

Pavel

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

#277wenjing zeng
wjzeng2012@gmail.com
In reply to: Pavel Stehule (#276)
Re: [Proposal] Global temporary tables

HI all

I started using my personal email to respond to community issue.

2020年7月7日 下午6:05,Pavel Stehule <pavel.stehule@gmail.com> 写道:

Hi

GTT Merge the latest PGMaster and resolves conflicts.

I tested it and it looks fine. I think it is very usable in current form, but still there are some issues:

postgres=# create global temp table foo(a int);
CREATE TABLE
postgres=# insert into foo values(10);
INSERT 0 1
postgres=# alter table foo add column x int;
ALTER TABLE
postgres=# analyze foo;
WARNING: reloid 16400 not support update attstat after add colunm
WARNING: reloid 16400 not support update attstat after add colunm
ANALYZE

This is a limitation that we can completely eliminate.

Please, can you summarize what is done, what limits are there, what can be implemented hard, what can be implemented easily?

Sure.

The current version of the GTT implementation supports all regular table operations.
1 what is done
1.1 insert/update/delete on GTT.
1.2 The GTT supports all types of indexes, and the query statement supports the use of GTT indexes to speed up the reading of data in the GTT.
1.3 GTT statistics keep a copy of THE GTT local statistics, which are provided to the optimizer to choose the best query plan.
1.4 analyze vacuum GTT.
1.5 truncate cluster GTT.
1.6 all DDL on GTT.
1.7 GTT table can use GTT sequence or Regular sequence.
1.8 Support for creating views on GTT.
1.9 Support for creating views on foreign key.
1.10 support global temp partition.

I feel like I cover all the necessary GTT requirements.

For cluster GTT,I think it's complicated.
I'm not sure the current implementation is quite reasonable. Maybe you can help review it.

I found one open question - how can be implemented table locks - because data is physically separated, then we don't need table locks as protection against race conditions.

Yes, but GTT’s DML DDL still requires table locking.
1 The DML requires table locks (RowExclusiveLock) to ensure that
definitions do not change during run time (the DDL may modify or delete them).
This part of the implementation does not actually change the code,
because the DML on GTT does not block each other between sessions.

2 For truncate/analyze/vacuum reinidex cluster GTT is now like DML,
they only modify local data and do not modify the GTT definition.
So I lowered the table lock level held by the GTT, only need RowExclusiveLock.

3 For DDLs that need to be modified the GTT table definition(Drop GTT Alter GTT),
an exclusive level of table locking is required(AccessExclusiveLock),
as is the case for regular table.
This part of the implementation also does not actually change the code.

Summary: What I have done is to adjust the GTT lock levels in different types of statements based on the above thinking.
For example, truncate GTT, I'm reducing the GTT holding table lock level to RowExclusiveLock,
So We can truncate data in the same GTT between different sessions at the same time.

What do you think about table locks on GTT?

Wenjing

Show quoted text

Now, table locks are implemented on a global level. So exclusive lock on GTT in one session block insertion on the second session. Is it expected behaviour? It is safe, but maybe it is too strict.

We should define what table lock is meaning on GTT.

Regards

Pavel

Pavel

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

#278wenjing zeng
wjzeng2012@gmail.com
In reply to: wenjing zeng (#277)
Re: [Proposal] Global temporary tables

2020年7月10日 下午5:03,wenjing zeng <wjzeng2012@gmail.com> 写道:

HI all

I started using my personal email to respond to community issue.

2020年7月7日 下午6:05,Pavel Stehule <pavel.stehule@gmail.com <mailto:pavel.stehule@gmail.com>> 写道:

Hi

GTT Merge the latest PGMaster and resolves conflicts.

I tested it and it looks fine. I think it is very usable in current form, but still there are some issues:

postgres=# create global temp table foo(a int);
CREATE TABLE
postgres=# insert into foo values(10);
INSERT 0 1
postgres=# alter table foo add column x int;
ALTER TABLE
postgres=# analyze foo;
WARNING: reloid 16400 not support update attstat after add colunm
WARNING: reloid 16400 not support update attstat after add colunm
ANALYZE

This is a limitation that we can completely eliminate.

Please, can you summarize what is done, what limits are there, what can be implemented hard, what can be implemented easily?

Sure.

The current version of the GTT implementation supports all regular table operations.
1 what is done
1.1 insert/update/delete on GTT.
1.2 The GTT supports all types of indexes, and the query statement supports the use of GTT indexes to speed up the reading of data in the GTT.
1.3 GTT statistics keep a copy of THE GTT local statistics, which are provided to the optimizer to choose the best query plan.
1.4 analyze vacuum GTT.
1.5 truncate cluster GTT.
1.6 all DDL on GTT.
1.7 GTT table can use GTT sequence or Regular sequence.
1.8 Support for creating views on GTT.
1.9 Support for creating views on foreign key.
1.10 support global temp partition.

I feel like I cover all the necessary GTT requirements.

For cluster GTT,I think it's complicated.
I'm not sure the current implementation is quite reasonable. Maybe you can help review it.

I found one open question - how can be implemented table locks - because data is physically separated, then we don't need table locks as protection against race conditions.

Yes, but GTT’s DML DDL still requires table locking.
1 The DML requires table locks (RowExclusiveLock) to ensure that
definitions do not change during run time (the DDL may modify or delete them).
This part of the implementation does not actually change the code,
because the DML on GTT does not block each other between sessions.

As a side note, since the same row of GTT data can not modified by different sessions,
So, I don't see the need to care the GTT's PG_class.relminmxID.
What do you think?

Wenjing

Show quoted text

2 For truncate/analyze/vacuum reinidex cluster GTT is now like DML,
they only modify local data and do not modify the GTT definition.
So I lowered the table lock level held by the GTT, only need RowExclusiveLock.

3 For DDLs that need to be modified the GTT table definition(Drop GTT Alter GTT),
an exclusive level of table locking is required(AccessExclusiveLock),
as is the case for regular table.
This part of the implementation also does not actually change the code.

Summary: What I have done is to adjust the GTT lock levels in different types of statements based on the above thinking.
For example, truncate GTT, I'm reducing the GTT holding table lock level to RowExclusiveLock,
So We can truncate data in the same GTT between different sessions at the same time.

What do you think about table locks on GTT?

Wenjing

Now, table locks are implemented on a global level. So exclusive lock on GTT in one session block insertion on the second session. Is it expected behaviour? It is safe, but maybe it is too strict.

We should define what table lock is meaning on GTT.

Regards

Pavel

Pavel

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

#279Pavel Stehule
pavel.stehule@gmail.com
In reply to: wenjing zeng (#278)
Re: [Proposal] Global temporary tables

po 13. 7. 2020 v 13:59 odesílatel wenjing zeng <wjzeng2012@gmail.com>
napsal:

2020年7月10日 下午5:03,wenjing zeng <wjzeng2012@gmail.com> 写道:

HI all

I started using my personal email to respond to community issue.

2020年7月7日 下午6:05,Pavel Stehule <pavel.stehule@gmail.com> 写道:

Hi

GTT Merge the latest PGMaster and resolves conflicts.

I tested it and it looks fine. I think it is very usable in current form,
but still there are some issues:

postgres=# create global temp table foo(a int);
CREATE TABLE
postgres=# insert into foo values(10);
INSERT 0 1
postgres=# alter table foo add column x int;
ALTER TABLE
postgres=# analyze foo;
WARNING: reloid 16400 not support update attstat after add colunm
WARNING: reloid 16400 not support update attstat after add colunm
ANALYZE

This is a limitation that we can completely eliminate.

Please, can you summarize what is done, what limits are there, what can be
implemented hard, what can be implemented easily?

Sure.

The current version of the GTT implementation supports all regular table
operations.
1 what is done
1.1 insert/update/delete on GTT.
1.2 The GTT supports all types of indexes, and the query statement
supports the use of GTT indexes to speed up the reading of data in the GTT.
1.3 GTT statistics keep a copy of THE GTT local statistics, which are
provided to the optimizer to choose the best query plan.
1.4 analyze vacuum GTT.
1.5 truncate cluster GTT.
1.6 all DDL on GTT.
1.7 GTT table can use GTT sequence or Regular sequence.
1.8 Support for creating views on GTT.
1.9 Support for creating views on foreign key.
1.10 support global temp partition.

I feel like I cover all the necessary GTT requirements.

For cluster GTT,I think it's complicated.
I'm not sure the current implementation is quite reasonable. Maybe you can
help review it.

I found one open question - how can be implemented table locks - because
data is physically separated, then we don't need table locks as protection
against race conditions.

Yes, but GTT’s DML DDL still requires table locking.
1 The DML requires table locks (RowExclusiveLock) to ensure that
definitions do not change during run time (the DDL may modify or delete
them).
This part of the implementation does not actually change the code,
because the DML on GTT does not block each other between sessions.

As a side note, since the same row of GTT data can not modified by
different sessions,
So, I don't see the need to care the GTT's PG_class.relminmxID.
What do you think?

yes, probably it is not necessary

Regards

Pavel

Show quoted text

Wenjing

2 For truncate/analyze/vacuum reinidex cluster GTT is now like DML,
they only modify local data and do not modify the GTT definition.
So I lowered the table lock level held by the GTT, only need
RowExclusiveLock.

3 For DDLs that need to be modified the GTT table definition(Drop
GTT Alter GTT),
an exclusive level of table locking is required(AccessExclusiveLock),
as is the case for regular table.
This part of the implementation also does not actually change the code.

Summary: What I have done is to adjust the GTT lock levels in different
types of statements based on the above thinking.
For example, truncate GTT, I'm reducing the GTT holding table lock level
to RowExclusiveLock,
So We can truncate data in the same GTT between different sessions at the
same time.

What do you think about table locks on GTT?

Wenjing

Now, table locks are implemented on a global level. So exclusive lock on
GTT in one session block insertion on the second session. Is it expected
behaviour? It is safe, but maybe it is too strict.

We should define what table lock is meaning on GTT.

Regards

Pavel

Pavel

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

#280Pavel Stehule
pavel.stehule@gmail.com
In reply to: wenjing zeng (#277)
Re: [Proposal] Global temporary tables

pá 10. 7. 2020 v 11:04 odesílatel wenjing zeng <wjzeng2012@gmail.com>
napsal:

HI all

I started using my personal email to respond to community issue.

2020年7月7日 下午6:05,Pavel Stehule <pavel.stehule@gmail.com> 写道:

Hi

GTT Merge the latest PGMaster and resolves conflicts.

I tested it and it looks fine. I think it is very usable in current form,
but still there are some issues:

postgres=# create global temp table foo(a int);
CREATE TABLE
postgres=# insert into foo values(10);
INSERT 0 1
postgres=# alter table foo add column x int;
ALTER TABLE
postgres=# analyze foo;
WARNING: reloid 16400 not support update attstat after add colunm
WARNING: reloid 16400 not support update attstat after add colunm
ANALYZE

This is a limitation that we can completely eliminate.

Please, can you summarize what is done, what limits are there, what can be
implemented hard, what can be implemented easily?

Sure.

The current version of the GTT implementation supports all regular table
operations.
1 what is done
1.1 insert/update/delete on GTT.
1.2 The GTT supports all types of indexes, and the query statement
supports the use of GTT indexes to speed up the reading of data in the GTT.
1.3 GTT statistics keep a copy of THE GTT local statistics, which are
provided to the optimizer to choose the best query plan.
1.4 analyze vacuum GTT.
1.5 truncate cluster GTT.
1.6 all DDL on GTT.
1.7 GTT table can use GTT sequence or Regular sequence.
1.8 Support for creating views on GTT.
1.9 Support for creating views on foreign key.
1.10 support global temp partition.

I feel like I cover all the necessary GTT requirements.

For cluster GTT,I think it's complicated.
I'm not sure the current implementation is quite reasonable. Maybe you can
help review it.

I found one open question - how can be implemented table locks - because
data is physically separated, then we don't need table locks as protection
against race conditions.

Yes, but GTT’s DML DDL still requires table locking.
1 The DML requires table locks (RowExclusiveLock) to ensure that
definitions do not change during run time (the DDL may modify or delete
them).
This part of the implementation does not actually change the code,
because the DML on GTT does not block each other between sessions.

2 For truncate/analyze/vacuum reinidex cluster GTT is now like DML,
they only modify local data and do not modify the GTT definition.
So I lowered the table lock level held by the GTT, only need
RowExclusiveLock.

3 For DDLs that need to be modified the GTT table definition(Drop
GTT Alter GTT),
an exclusive level of table locking is required(AccessExclusiveLock),
as is the case for regular table.
This part of the implementation also does not actually change the code.

Summary: What I have done is to adjust the GTT lock levels in different
types of statements based on the above thinking.
For example, truncate GTT, I'm reducing the GTT holding table lock level
to RowExclusiveLock,
So We can truncate data in the same GTT between different sessions at the
same time.

What do you think about table locks on GTT?

I am thinking about explicit LOCK statements. Some applications use
explicit locking from some reasons - typically as protection against race
conditions.

But on GTT race conditions are not possible. So my question is - does the
exclusive lock on GTT protection other sessions do insert into their own
instances of the same GTT?

What is a level where table locks are active? shared part of GTT or session
instance part of GTT?

Show quoted text

Wenjing

Now, table locks are implemented on a global level. So exclusive lock on
GTT in one session block insertion on the second session. Is it expected
behaviour? It is safe, but maybe it is too strict.

We should define what table lock is meaning on GTT.

Regards

Pavel

Pavel

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

#281wenjing zeng
wjzeng2012@gmail.com
In reply to: Pavel Stehule (#280)
Re: [Proposal] Global temporary tables

2020年7月14日 下午10:28,Pavel Stehule <pavel.stehule@gmail.com> 写道:

pá 10. 7. 2020 v 11:04 odesílatel wenjing zeng <wjzeng2012@gmail.com <mailto:wjzeng2012@gmail.com>> napsal:
HI all

I started using my personal email to respond to community issue.

2020年7月7日 下午6:05,Pavel Stehule <pavel.stehule@gmail.com <mailto:pavel.stehule@gmail.com>> 写道:

Hi

GTT Merge the latest PGMaster and resolves conflicts.

I tested it and it looks fine. I think it is very usable in current form, but still there are some issues:

postgres=# create global temp table foo(a int);
CREATE TABLE
postgres=# insert into foo values(10);
INSERT 0 1
postgres=# alter table foo add column x int;
ALTER TABLE
postgres=# analyze foo;
WARNING: reloid 16400 not support update attstat after add colunm
WARNING: reloid 16400 not support update attstat after add colunm
ANALYZE

This is a limitation that we can completely eliminate.

Please, can you summarize what is done, what limits are there, what can be implemented hard, what can be implemented easily?

Sure.

The current version of the GTT implementation supports all regular table operations.
1 what is done
1.1 insert/update/delete on GTT.
1.2 The GTT supports all types of indexes, and the query statement supports the use of GTT indexes to speed up the reading of data in the GTT.
1.3 GTT statistics keep a copy of THE GTT local statistics, which are provided to the optimizer to choose the best query plan.
1.4 analyze vacuum GTT.
1.5 truncate cluster GTT.
1.6 all DDL on GTT.
1.7 GTT table can use GTT sequence or Regular sequence.
1.8 Support for creating views on GTT.
1.9 Support for creating views on foreign key.
1.10 support global temp partition.

I feel like I cover all the necessary GTT requirements.

For cluster GTT,I think it's complicated.
I'm not sure the current implementation is quite reasonable. Maybe you can help review it.

I found one open question - how can be implemented table locks - because data is physically separated, then we don't need table locks as protection against race conditions.

Yes, but GTT’s DML DDL still requires table locking.
1 The DML requires table locks (RowExclusiveLock) to ensure that
definitions do not change during run time (the DDL may modify or delete them).
This part of the implementation does not actually change the code,
because the DML on GTT does not block each other between sessions.

2 For truncate/analyze/vacuum reinidex cluster GTT is now like DML,
they only modify local data and do not modify the GTT definition.
So I lowered the table lock level held by the GTT, only need RowExclusiveLock.

3 For DDLs that need to be modified the GTT table definition(Drop GTT Alter GTT),
an exclusive level of table locking is required(AccessExclusiveLock),
as is the case for regular table.
This part of the implementation also does not actually change the code.

Summary: What I have done is to adjust the GTT lock levels in different types of statements based on the above thinking.
For example, truncate GTT, I'm reducing the GTT holding table lock level to RowExclusiveLock,
So We can truncate data in the same GTT between different sessions at the same time.

What do you think about table locks on GTT?

I am thinking about explicit LOCK statements. Some applications use explicit locking from some reasons - typically as protection against race conditions.

But on GTT race conditions are not possible. So my question is - does the exclusive lock on GTT protection other sessions do insert into their own instances of the same GTT?

In my opinion, with a GTT, always work on the private data of the session,
there is no need to do anything by holding the lock, so the lock statement should do nothing (The same is true for ORACLE GTT)

What do you think?

What is a level where table locks are active? shared part of GTT or session instance part of GTT?

I don't quite understand what you mean, could you explain it a little bit?

Wenjing

Show quoted text

Wenjing

Now, table locks are implemented on a global level. So exclusive lock on GTT in one session block insertion on the second session. Is it expected behaviour? It is safe, but maybe it is too strict.

We should define what table lock is meaning on GTT.

Regards

Pavel

Pavel

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

#282Pavel Stehule
pavel.stehule@gmail.com
In reply to: wenjing zeng (#281)
Re: [Proposal] Global temporary tables

I am thinking about explicit LOCK statements. Some applications use
explicit locking from some reasons - typically as protection against race
conditions.

But on GTT race conditions are not possible. So my question is - does the
exclusive lock on GTT protection other sessions do insert into their own
instances of the same GTT?

In my opinion, with a GTT, always work on the private data of the session,
there is no need to do anything by holding the lock, so the lock statement
should do nothing (The same is true for ORACLE GTT)

What do you think?

What is a level where table locks are active? shared part of GTT or
session instance part of GTT?

I don't quite understand what you mean, could you explain it a little bit?

It is about perspective, how we should see GTT tables. GTT table is a mix
of two concepts - session private (data), and session shared (catalog). And
hypothetically we can place locks to the private part (no effect) or shared
part (usual effect how we know it). But can has sense, and both have an
advantage and disadvantage. I afraid little bit about behaviour of stupid
ORM systems - but the most important part of table are data - and then I
prefer empty lock implementation for GTT.

Regards

Pavel

Show quoted text

Wenjing

Wenjing

Now, table locks are implemented on a global level. So exclusive lock on
GTT in one session block insertion on the second session. Is it expected
behaviour? It is safe, but maybe it is too strict.

We should define what table lock is meaning on GTT.

Regards

Pavel

Pavel

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

#283wenjing zeng
wjzeng2012@gmail.com
In reply to: Pavel Stehule (#282)
1 attachment(s)
Re: [Proposal] Global temporary tables

2020年7月23日 下午2:54,Pavel Stehule <pavel.stehule@gmail.com> 写道:

I am thinking about explicit LOCK statements. Some applications use explicit locking from some reasons - typically as protection against race conditions.

But on GTT race conditions are not possible. So my question is - does the exclusive lock on GTT protection other sessions do insert into their own instances of the same GTT?

In my opinion, with a GTT, always work on the private data of the session,
there is no need to do anything by holding the lock, so the lock statement should do nothing (The same is true for ORACLE GTT)

What do you think?

What is a level where table locks are active? shared part of GTT or session instance part of GTT?

I don't quite understand what you mean, could you explain it a little bit?

It is about perspective, how we should see GTT tables. GTT table is a mix of two concepts - session private (data), and session shared (catalog). And hypothetically we can place locks to the private part (no effect) or shared part (usual effect how we know it). But can has sense, and both have an advantage and disadvantage. I afraid little bit about behaviour of stupid ORM systems - but the most important part of table are data - and then I prefer empty lock implementation for GTT.

This is empty lock implementation for GTT.

Please continue to review the code.

Thanks

Wenjing

Show quoted text

Regards

Pavel

Wenjing

Wenjing

Now, table locks are implemented on a global level. So exclusive lock on GTT in one session block insertion on the second session. Is it expected behaviour? It is safe, but maybe it is too strict.

We should define what table lock is meaning on GTT.

Regards

Pavel

Pavel

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;

Attachments:

global_temporary_table_v35-pg13.patchapplication/octet-stream; name=global_temporary_table_v35-pg13.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 8ccc228..b061de9 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -168,6 +168,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * For global temp table only
+	 * use ShareUpdateExclusiveLock for ensure safety
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temp table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},	
 	/* list terminator */
 	{{NULL}}
 };
@@ -1817,6 +1830,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1961,13 +1976,18 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	/*
 	 * There are no options for partitioned tables yet, but this is able to do
 	 * some validation.
 	 */
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index 765329b..4761fdc 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1011,7 +1011,9 @@ gistproperty(Oid index_oid, int attno,
 XLogRecPtr
 gistGetFakeLSN(Relation rel)
 {
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	/* global temp is same as local temp table */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 3ec6d52..1222594 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -150,7 +150,9 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	/* global temp table is same as local temp table */
+	if (!(index->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		index->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 267a6ee..28b5664 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -589,7 +589,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -642,7 +642,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 1bbc459..eeac2c3 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -62,6 +62,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -439,9 +440,9 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
 	Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
-	/* not every AM requires these to be valid, but heap does */
-	Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
-	Assert(MultiXactIdIsValid(onerel->rd_rel->relminmxid));
+	/* not every AM requires these to be valid, but regular heap does */
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ MultiXactIdIsValid(onerel->rd_rel->relminmxid));
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 70bac00..2895a67 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -609,6 +610,10 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 756b838..7ad82a5 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6434,6 +6434,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/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 5eaca27..e5002aa 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -231,7 +231,8 @@ Boot_CreateStmt:
 												   mapped_relation,
 												   true,
 												   &relfrozenxid,
-												   &relminmxid);
+												   &relminmxid,
+												   false);
 						elog(DEBUG4, "bootstrap relation created");
 					}
 					else
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 93cf6d4..647f42f 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -43,6 +43,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 7d6acae..3be8d63 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -393,7 +393,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/* global temp table is same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 3985326..042be5a 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -304,7 +306,8 @@ heap_create(const char *relname,
 			bool mapped_relation,
 			bool allow_system_table_mods,
 			TransactionId *relfrozenxid,
-			MultiXactId *relminmxid)
+			MultiXactId *relminmxid,
+			bool skip_create_storage)
 {
 	bool		create_storage;
 	Relation	rel;
@@ -404,6 +407,9 @@ heap_create(const char *relname,
 									 relpersistence,
 									 relkind);
 
+	if (skip_create_storage)
+		create_storage = false;
+
 	/*
 	 * Have the storage manager create the relation's disk file, if needed.
 	 *
@@ -427,7 +433,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -953,6 +959,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -991,8 +998,18 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/* global temp table not remember transaction info in catalog */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1256,7 +1273,8 @@ heap_create_with_catalog(const char *relname,
 							   mapped_relation,
 							   allow_system_table_mods,
 							   &relfrozenxid,
-							   &relminmxid);
+							   &relminmxid,
+							   false);
 
 	Assert(relid == RelationGetRelid(new_rel_desc));
 
@@ -1362,6 +1380,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1942,6 +1961,16 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/* We allow to drop global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot drop global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3164,7 +3193,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3176,7 +3205,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3222,8 +3251,13 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
+
+		/* truncate global temp table only need RowExclusiveLock */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3256,6 +3290,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3264,23 +3299,37 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (!gtt_storage_attached(RelationGetRelid(rel)))
+			return;
+
+		/*
+		 * Truncate global temp table only need RowExclusiveLock
+		 */
+		lockmode = RowExclusiveLock;
+	}
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(rel, 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 8ec2864..2a8bd8c 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -53,6 +53,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -727,6 +728,11 @@ index_create(Relation heapRelation,
 	char		relkind;
 	TransactionId relfrozenxid;
 	MultiXactId relminmxid;
+	bool		skip_create_storage = false;
+
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation) &&
+		!gtt_storage_attached(RelationGetRelid(heapRelation)))
+		skip_create_storage = true;
 
 	/* constraint flags can only be set when a constraint is requested */
 	Assert((constr_flags == 0) ||
@@ -880,6 +886,19 @@ index_create(Relation heapRelation,
 						indexRelationName, RelationGetRelationName(heapRelation))));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temp table use concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot reindex global temporary tables concurrently")));
+
+		/* if global temp table not init storage, then skip build index */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -934,7 +953,8 @@ index_create(Relation heapRelation,
 								mapped_relation,
 								allow_system_table_mods,
 								&relfrozenxid,
-								&relminmxid);
+								&relminmxid,
+								skip_create_storage);
 
 	Assert(relfrozenxid == InvalidTransactionId);
 	Assert(relminmxid == InvalidMultiXactId);
@@ -2027,7 +2047,8 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 * lock (see comments in RemoveRelations), and a non-concurrent DROP is
 	 * more efficient.
 	 */
-	Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+	Assert(!(get_rel_persistence(indexId) == RELPERSISTENCE_TEMP ||
+			 get_rel_persistence(indexId) == RELPERSISTENCE_GLOBAL_TEMP) ||
 		   (!concurrent && !concurrent_lock_mode));
 
 	/*
@@ -2059,6 +2080,17 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/* We allow to drop index on global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot drop index %s or global temporary table %s",
+						RelationGetRelationName(userIndexRelation), RelationGetRelationName(userHeapRelation)),
+				 errhint("Because the index is created on the global temporary table and other backend attached it.")));
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2667,6 +2699,11 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	/* update index stats into localhash and rel_rd_rel for global temp table */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		is_gtt = true;
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2752,21 +2789,35 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
+		if (is_gtt)
+			rel->rd_rel->relpages = (int32) relpages;
+		else if (rd_rel->relpages != (int32) relpages)
 		{
 			rd_rel->relpages = (int32) relpages;
 			dirty = true;
 		}
-		if (rd_rel->reltuples != (float4) reltuples)
+
+		if (is_gtt)
+			rel->rd_rel->reltuples = (float4) reltuples;
+		else if (rd_rel->reltuples != (float4) reltuples)
 		{
 			rd_rel->reltuples = (float4) reltuples;
 			dirty = true;
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+
+		if (is_gtt)
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+		else if (rd_rel->relallvisible != (int32) relallvisible)
 		{
 			rd_rel->relallvisible = (int32) relallvisible;
 			dirty = true;
 		}
+
+		if (is_gtt)
+		{
+			up_gtt_relstats(rel, relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
+		}
 	}
 
 	/*
@@ -2880,6 +2931,15 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, index, val);
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			gtt_force_enable_index(indexRelation);
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3435,6 +3495,16 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	 * we only need to be sure no schema or data changes are going on.
 	 */
 	heapId = IndexGetRelation(indexId, false);
+
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		!gtt_storage_attached(indexId))
+	{
+		/* Correction status flag, and return */
+		SetReindexProcessing(heapId, indexId);
+		ResetReindexProcessing();
+		return;
+	}
+
 	heapRelation = table_open(heapId, ShareLock);
 
 	if (progress)
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 0152e38..a5b40af 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -655,6 +655,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		/* global temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp relations in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index ec143b6..204a0e0 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,6 +27,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -61,6 +62,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			relOid;			/* InvalidOid if not a global temp rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -115,7 +117,7 @@ AddPendingSync(const RelFileNode *rnode)
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -127,6 +129,8 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		/* global temp table use same storage strategy as local temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -154,6 +158,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->relOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
@@ -165,6 +170,13 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		AddPendingSync(&rnode);
 	}
 
+	/* remember global temp table storage info to localhash */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel)
+	{
+		pending->relOid = RelationGetRelid(rel);
+		remember_gtt_storage_info(rnode, rel);
+	}
+
 	return srel;
 }
 
@@ -201,11 +213,15 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->relOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->relOid = RelationGetRelid(rel);
+
 	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
@@ -602,6 +618,7 @@ smgrDoPendingDeletes(bool isCommit)
 				i = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -631,14 +648,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->relOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -651,9 +672,18 @@ smgrDoPendingDeletes(bool isCommit)
 		smgrdounlinkall(srels, nrels, false);
 
 		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* clean global temp table flags when transaction commit or rollback */
+			if (SmgrIsTemp(srels[i]) &&
+				reloids[i] != InvalidOid &&
+				gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..451cf82
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1491 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  code to create and destroy physical storage for global temparary table
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_relstats_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+int		vacuum_gtt_defer_check_age = 0;
+
+typedef struct gtt_ctl_data
+{
+	LWLock			lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+	Oid			relfilenode;
+	Oid			spcnode;
+
+	/* pg_class stat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+
+	/* pg_statistic */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+
+	Oid			oldrelid;			/* remember the source of relid, before the switch relfilenode. */
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_reset_statistics(gtt_local_hash_entry *entry);
+static void gtt_free_statistics(gtt_local_hash_entry *entry);
+static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok);
+static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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();
+	}
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(gtt_fnode);
+	info.entrysize = action_gtt_shared_hash_entry_size();
+	active_gtt_shared_hash =
+		ShmemInitHash("active gtt shared hash",
+						gtt_shared_ctl->max_entry,
+						gtt_shared_ctl->max_entry,
+						&info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			found;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *)&(fnode), 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_global_temporary_table.")));
+	}
+
+	if (found == false)
+	{
+		int			wordnum;
+
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode				fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	if (!skiplock)
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		if (!skiplock)
+			LWLockRelease(&gtt_shared_ctl->lock);
+
+		if (isCommit)
+			elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+	bms_del_member(entry->map, MyBackendId);
+
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+
+	if (!skiplock)
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+	return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	Bitmapset	*map_copy = NULL;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool		in_use = false;
+	int			num_use = 0;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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;
+	gtt_relfilenode			*new_node = NULL;
+	Oid						relid = RelationGetRelid(rel);
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		/* First time through: initialize the hash table */
+		HASHCTL		ctl;
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_relstats_context =
+			AllocSetContextCreate(CacheMemoryContext,
+							"gtt relstats context",
+							ALLOCSET_DEFAULT_SIZES);
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+	entry = gtt_search_by_relid(relid, true);
+	if (!entry)
+	{
+		bool		found = false;
+		int 		natts = 0;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash,
+						(void *) &relid, HASH_ENTER, &found);
+
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+		entry->natts = 0;
+		entry->attnum = NULL;
+		entry->att_stat_tups = NULL;
+		entry->oldrelid = InvalidOid;
+
+		natts = RelationGetNumberOfAttributes(rel);
+		entry->attnum = palloc0(sizeof(int) * natts);
+		entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+		entry->natts = natts;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			gtt_storage_checkin(relid);
+		}
+	}
+
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* only heap contain transaction information */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	gtt_reset_statistics(entry);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	gtt_relfilenode *d_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+		return;
+	}
+
+	d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+		else if (entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+			gtt_relfilenode *gttnode2 = NULL;
+
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+			Assert(gttnode2->relfilenode == rnode.relNode);
+			Assert(list_length(entry->relfilenode_list) == 1);
+			/* rollback switch relfilenode */
+			gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+									   entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+									   false);
+			/* clean up footprint */
+			entry2->oldrelid = InvalidOid;
+			d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+			Assert(d_rnode);
+		}
+		else
+		{
+			if (entry->relfilenode_list == NIL)
+			{
+				if (entry->relkind == RELKIND_RELATION ||
+					entry->relkind == RELKIND_SEQUENCE)
+					gtt_storage_checkout(relid, false, isCommit);
+
+				gtt_free_statistics(entry);
+				hash_search(gtt_storage_local_hash,
+						(void *) &(relid), HASH_REMOVE, NULL);
+			}
+
+			return;
+		}
+	}
+
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	pfree(d_rnode);
+	if (entry->relfilenode_list == NIL)
+	{
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(relid, false, isCommit);
+
+		if (isCommit && entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			/* clean up footprint */
+			entry2->oldrelid = InvalidOid;
+		}
+
+		gtt_free_statistics(entry);
+		hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_REMOVE, NULL);
+	}
+	else
+		gtt_reset_statistics(entry);
+
+	return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool found = false;
+	gtt_local_hash_entry *entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry)
+		found = true;
+
+	return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS				status;
+	gtt_local_hash_entry		*entry;
+	SMgrRelation	*srels = NULL;
+	Oid				*relids = NULL;
+	char			*relkinds = NULL;
+	int			nrels = 0,
+				nfiles = 0,
+				maxrels = 0,
+				maxfiles = 0,
+				i = 0;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel;
+			RelFileNode 	rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel = smgropen(rnode, MyBackendId);
+
+			if (maxfiles == 0)
+			{
+				maxfiles = 32;
+				srels = palloc(sizeof(SMgrRelation) * maxfiles);
+			}
+			else if (maxfiles <= nfiles)
+			{
+				maxfiles *= 2;
+				srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+			}
+
+			srels[nfiles++] = srel;
+		}
+
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			relids  = palloc(sizeof(Oid) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			relids  = repalloc(relids , sizeof(Oid) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		relkinds[nrels] = entry->relkind;
+		relids[nrels] = entry->relid;
+		nrels++;
+	}
+
+	if (nfiles > 0)
+	{
+		smgrdounlinkall(srels, nfiles, false);
+		for (i = 0; i < nfiles; i++)
+			smgrclose(srels[i]);
+
+		pfree(srels);
+	}
+
+	if (nrels)
+	{
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+		for (i = 0; i < nrels; i++)
+		{
+			if (relkinds[i] == RELKIND_RELATION ||
+				relkinds[i] == RELKIND_SEQUENCE)
+				gtt_storage_checkout(relids[i], true, false);
+		}
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+		pfree(relids);
+		pfree(relkinds);
+	}
+
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+	return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible) 
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+					BlockNumber num_pages,
+					double num_tuples,
+					BlockNumber num_all_visible_pages,
+					TransactionId relfrozenxid,
+					TransactionId relminmxid)
+{
+	Oid		relid = RelationGetRelid(relation);
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (num_pages >= 0 &&
+		gtt_rnode->relpages != (int32)num_pages)
+		gtt_rnode->relpages = (int32)num_pages;
+
+	if (num_tuples >= 0 &&
+		gtt_rnode->reltuples != (float4)num_tuples)
+		gtt_rnode->reltuples = (float4)num_tuples;
+
+	/* only heap contain transaction information and relallvisible */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (gtt_rnode->relallvisible >= 0 &&
+			gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+		{
+			gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+		}
+
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNewTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible) 
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	MemoryContext			oldcontext;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return;
+
+	if (entry->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	Assert(entry->relid == reloid);
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == 0)
+		{
+			entry->attnum[i] = attnum;
+			break;
+		}
+		else if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < entry->natts);
+	Assert(entry->att_stat_tups[i] == NULL);
+	entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return NULL;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			return entry->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int			i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	HeapTuple	tuple;
+	Relation	rel = NULL;
+	int		attnum = PG_GETARG_INT32(1);
+	Oid		reloid = PG_GETARG_OID(0);
+	char		rel_persistence;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	  tupdesc;
+	MemoryContext oldcontext;
+	Tuplestorestate *tupstore;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+
+	relation_close(rel, NoLock);
+	relation_close(pg_tatistic, AccessShareLock);
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate	*tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Oid			relnode = 0;
+	Relation	rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[6];
+		bool	isnull[6];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(reloid);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(RelationGetRelid(index)));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid relOid = index->rd_index->indrelid;
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	if (gtt_storage_attached(relOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int			i;
+	Oid			toastrelid;
+
+	if (!(operation == CMD_UTILITY || operation == CMD_INSERT))
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+
+		index_build(relation, index, info, true, false);
+	}
+
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid 		indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo 	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+static void
+gtt_reset_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+
+		entry->attnum[i] = 0;
+	}
+
+	return;
+}
+
+static void
+gtt_free_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (entry->attnum)
+		pfree(entry->attnum);
+
+	if (entry->att_stat_tups)
+		pfree(entry->att_stat_tups);
+
+	return;
+}
+
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
+void
+gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint)
+{
+	gtt_local_hash_entry	*entry1;
+	gtt_local_hash_entry	*entry2;
+	gtt_relfilenode			*gtt_rnode1 = NULL;
+	gtt_relfilenode			*gtt_rnode2 = NULL;
+	MemoryContext			oldcontext;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry1 = gtt_search_by_relid(rel1, false);
+	gtt_rnode1 = gtt_search_relfilenode(entry1, relfilenode1, false);
+
+	entry2 = gtt_search_by_relid(rel2, false);
+	gtt_rnode2 = gtt_search_relfilenode(entry2, relfilenode2, false);
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	entry1->relfilenode_list = list_delete_ptr(entry1->relfilenode_list, gtt_rnode1);
+	entry2->relfilenode_list = lappend(entry2->relfilenode_list, gtt_rnode1);
+
+	entry2->relfilenode_list = list_delete_ptr(entry2->relfilenode_list, gtt_rnode2);
+	entry1->relfilenode_list = lappend(entry1->relfilenode_list, gtt_rnode2);
+	MemoryContextSwitchTo(oldcontext);
+
+	if (footprint)
+	{
+		entry1->oldrelid = rel2;
+		entry2->oldrelid = rel1;
+	}
+
+	return;
+}
+
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok)
+{
+	gtt_relfilenode			*rnode = NULL;
+	ListCell				*lc;
+
+	Assert(entry);
+
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == relfilenode)
+		{
+			rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (!missing_ok && rnode == NULL)
+		elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+	return rnode;
+}
+
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry == NULL && !missing_ok)
+		elog(ERROR, "relid %u not found in local hash", relid);
+
+	return entry;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 8625cbe..a6699ac 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM 
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 924ef37..db12eef 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -184,6 +185,13 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -586,14 +594,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 		 * pg_statistic for columns we didn't process, we leave them alone.)
 		 */
 		update_attstats(RelationGetRelid(onerel), inh,
-						attr_cnt, vacattrstats);
+						attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
 
 		for (ind = 0; ind < nindexes; ind++)
 		{
 			AnlIndexData *thisdata = &indexdata[ind];
 
 			update_attstats(RelationGetRelid(Irel[ind]), false,
-							thisdata->attr_cnt, thisdata->vacattrstats);
+							thisdata->attr_cnt, thisdata->vacattrstats,
+							RelationGetRelPersistence(Irel[ind]));
 		}
 
 		/*
@@ -1456,7 +1465,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
  *		by taking a self-exclusive lock on the relation in analyze_rel().
  */
 static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
 {
 	Relation	sd;
 	int			attno;
@@ -1558,31 +1567,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/* Update column statistic to localhash, not catalog */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 04d12a7..42c82c8 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/toasting.h"
 #include "commands/cluster.h"
 #include "commands/progress.h"
@@ -72,6 +73,12 @@ static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
 							bool verbose, bool *pSwapToastByContent,
 							TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
 static List *get_tables_to_cluster(MemoryContext cluster_context);
+static void gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables);
 
 
 /*---------------------------------------------------------------------------
@@ -366,6 +373,14 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap) &&
+		!gtt_storage_attached(RelationGetRelid(OldHeap)))
+	{
+		relation_close(OldHeap, AccessExclusiveLock);
+		pgstat_progress_end_command();
+		return;
+	}
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
@@ -750,6 +765,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	BlockNumber num_pages;
 	int			elevel = verbose ? INFO : DEBUG2;
 	PGRUsage	ru0;
+	bool		is_gtt = false;
+	uint32		gtt_relfrozenxid = 0;
+	uint32		gtt_relminmxid = 0;
 
 	pg_rusage_init(&ru0);
 
@@ -763,6 +781,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	else
 		OldIndex = NULL;
 
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		is_gtt = true;
+
 	/*
 	 * Their tuple descriptors should be exactly alike, but here we only need
 	 * assume that they have the same number of columns.
@@ -830,20 +851,37 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 						  &OldestXmin, &FreezeXid, NULL, &MultiXactCutoff,
 						  NULL);
 
-	/*
-	 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
-	 * backwards, so take the max.
-	 */
-	if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
-		TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
-		FreezeXid = OldHeap->rd_rel->relfrozenxid;
+	if (is_gtt)
+	{
+		get_gtt_relstats(OIDOldHeap,
+					NULL, NULL, NULL,
+					&gtt_relfrozenxid, &gtt_relminmxid);
 
-	/*
-	 * MultiXactCutoff, similarly, shouldn't go backwards either.
-	 */
-	if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
-		MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
-		MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+		if (TransactionIdIsValid(gtt_relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, gtt_relfrozenxid))
+			FreezeXid = gtt_relfrozenxid;
+
+		if (MultiXactIdIsValid(gtt_relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, gtt_relminmxid))
+			MultiXactCutoff = gtt_relminmxid;
+	}
+	else
+	{
+		/*
+		 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
+		 * backwards, so take the max.
+		 */
+		if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
+			FreezeXid = OldHeap->rd_rel->relfrozenxid;
+
+		/*
+		 * MultiXactCutoff, similarly, shouldn't go backwards either.
+		 */
+		if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
+			MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+	}
 
 	/*
 	 * Decide whether to use an indexscan or seqscan-and-optional-sort to scan
@@ -911,6 +949,12 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	table_close(OldHeap, NoLock);
 	table_close(NewHeap, NoLock);
 
+	if (is_gtt)
+	{
+		CommandCounterIncrement();
+		return;
+	}
+
 	/* Update pg_class to reflect the correct values of pages and tuples. */
 	relRelation = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1346,10 +1390,21 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 	 * Swap the contents of the heap relations (including any toast tables).
 	 * Also set old heap's relfrozenxid to frozenXid.
 	 */
-	swap_relation_files(OIDOldHeap, OIDNewHeap,
+	if (newrelpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		Assert(!is_system_catalog);
+		gtt_swap_relation_files(OIDOldHeap, OIDNewHeap,
+								(OIDOldHeap == RelationRelationId),
+								swap_toast_by_content, is_internal,
+								frozenXid, cutoffMulti, mapped_tables);
+	}
+	else
+	{
+		swap_relation_files(OIDOldHeap, OIDNewHeap,
 						(OIDOldHeap == RelationRelationId),
 						swap_toast_by_content, is_internal,
 						frozenXid, cutoffMulti, mapped_tables);
+	}
 
 	/*
 	 * If it's a system catalog, queue a sinval message to flush all catcaches
@@ -1557,3 +1612,141 @@ get_tables_to_cluster(MemoryContext cluster_context)
 
 	return rvs;
 }
+
+static void
+gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables)
+{
+	Relation	relRelation;
+	Oid			relfilenode1,
+				relfilenode2;
+	Relation	rel1;
+	Relation	rel2;
+
+	relRelation = table_open(RelationRelationId, RowExclusiveLock);
+
+	rel1 = relation_open(r1, AccessExclusiveLock);
+	rel2 = relation_open(r2, AccessExclusiveLock);
+
+	relfilenode1 = gtt_fetch_current_relfilenode(r1);
+	relfilenode2 = gtt_fetch_current_relfilenode(r2);
+
+	Assert(OidIsValid(relfilenode1) && OidIsValid(relfilenode2));
+	gtt_switch_rel_relfilenode(r1, relfilenode1, r2, relfilenode2, true);
+
+	CacheInvalidateRelcache(rel1);
+	CacheInvalidateRelcache(rel2);
+
+	InvokeObjectPostAlterHookArg(RelationRelationId, r1, 0,
+								 InvalidOid, is_internal);
+	InvokeObjectPostAlterHookArg(RelationRelationId, r2, 0,
+								 InvalidOid, true);
+
+	if (rel1->rd_rel->reltoastrelid || rel2->rd_rel->reltoastrelid)
+	{
+		if (swap_toast_by_content)
+		{
+			if (rel1->rd_rel->reltoastrelid && rel2->rd_rel->reltoastrelid)
+			{
+				gtt_swap_relation_files(rel1->rd_rel->reltoastrelid,
+									rel2->rd_rel->reltoastrelid,
+									target_is_pg_class,
+									swap_toast_by_content,
+									is_internal,
+									frozenXid,
+									cutoffMulti,
+									mapped_tables);
+			}
+			else
+				elog(ERROR, "cannot swap toast files by content when there's only one");
+		}
+		else
+		{
+			ObjectAddress baseobject,
+						toastobject;
+			long		count;
+
+			if (IsSystemRelation(rel1))
+				elog(ERROR, "cannot swap toast files by links for system catalogs");
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel1->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel2->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+
+			/* Register new dependencies */
+			baseobject.classId = RelationRelationId;
+			baseobject.objectSubId = 0;
+			toastobject.classId = RelationRelationId;
+			toastobject.objectSubId = 0;
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r1;
+				toastobject.objectId = rel1->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r2;
+				toastobject.objectId = rel2->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+		}
+	}
+
+	if (swap_toast_by_content &&
+		rel1->rd_rel->relkind == RELKIND_TOASTVALUE &&
+		rel2->rd_rel->relkind == RELKIND_TOASTVALUE)
+	{
+		Oid			toastIndex1,
+					toastIndex2;
+
+		/* Get valid index for each relation */
+		toastIndex1 = toast_get_valid_index(r1,
+											AccessExclusiveLock);
+		toastIndex2 = toast_get_valid_index(r2,
+											AccessExclusiveLock);
+
+		gtt_swap_relation_files(toastIndex1,
+							toastIndex2,
+							target_is_pg_class,
+							swap_toast_by_content,
+							is_internal,
+							InvalidTransactionId,
+							InvalidMultiXactId,
+							mapped_tables);
+	}
+
+	relation_close(rel1, NoLock);
+	relation_close(rel2, NoLock);
+
+	table_close(relRelation, RowExclusiveLock);
+
+	RelationCloseSmgrByOid(r1);
+	RelationCloseSmgrByOid(r2);
+
+	CommandCounterIncrement();
+}
+
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index db7d24a..daf9d1c 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -27,6 +27,7 @@
 #include "catalog/dependency.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -1113,7 +1114,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, stmt->filename, stmt->is_program,
@@ -2838,6 +2839,8 @@ CopyFrom(CopyState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	estate->es_result_relations = resultRelInfo;
 	estate->es_num_result_relations = 1;
 	estate->es_result_relation_info = resultRelInfo;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 2baca12..5338db9 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -521,6 +521,7 @@ DefineIndex(Oid relationId,
 	Snapshot	snapshot;
 	int			save_nestlevel = -1;
 	int			i;
+	char		rel_persistence;
 
 	/*
 	 * Some callers need us to run with an empty default_tablespace; this is a
@@ -542,7 +543,9 @@ DefineIndex(Oid relationId,
 	 * is more efficient.  Do this before any use of the concurrent option is
 	 * done.
 	 */
-	if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+	rel_persistence = get_rel_persistence(relationId);
+	if (stmt->concurrent &&
+		!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		concurrent = true;
 	else
 		concurrent = false;
@@ -2460,7 +2463,8 @@ ReindexIndex(RangeVar *indexRelation, int options, bool concurrent)
 	persistence = irel->rd_rel->relpersistence;
 	index_close(irel, NoLock);
 
-	if (concurrent && persistence != RELPERSISTENCE_TEMP)
+	if (concurrent &&
+		!(persistence == RELPERSISTENCE_TEMP || persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		ReindexRelationConcurrently(indOid, options);
 	else
 		reindex_index(indOid, false, persistence,
@@ -2546,6 +2550,7 @@ ReindexTable(RangeVar *relation, int options, bool concurrent)
 {
 	Oid			heapOid;
 	bool		result;
+	char		rel_persistence;
 
 	/*
 	 * The lock level used here should match reindex_relation().
@@ -2560,7 +2565,9 @@ ReindexTable(RangeVar *relation, int options, bool concurrent)
 									   0,
 									   RangeVarCallbackOwnsTable, NULL);
 
-	if (concurrent && get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+	rel_persistence = get_rel_persistence(heapOid);
+	if (concurrent &&
+		!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 	{
 		result = ReindexRelationConcurrently(heapOid, options);
 
@@ -2761,12 +2768,15 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 	foreach(l, relids)
 	{
 		Oid			relid = lfirst_oid(l);
+		char		rel_persistence;
 
 		StartTransactionCommand();
 		/* functions in indexes may want a snapshot set */
 		PushActiveSnapshot(GetTransactionSnapshot());
 
-		if (concurrent && get_rel_persistence(relid) != RELPERSISTENCE_TEMP)
+		rel_persistence = get_rel_persistence(relid);
+		if (concurrent &&
+			!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		{
 			(void) ReindexRelationConcurrently(relid, options);
 			/* ReindexRelationConcurrently() does the verbose output */
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index d8cafc4..9ad05dc 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -51,6 +51,13 @@ LockTableCommand(LockStmt *lockstmt)
 		bool		recurse = rv->inh;
 		Oid			reloid;
 
+		reloid = RangeVarGetRelid(rv, NoLock, true);
+		if(OidIsValid(reloid) &&
+			get_rel_persistence(reloid) == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			continue;
+		}
+
 		reloid = RangeVarGetRelidExtended(rv, lockstmt->mode,
 										  lockstmt->nowait ? RVR_NOWAIT : 0,
 										  RangeVarCallbackForLockTable,
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..234ffa6 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -94,7 +96,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -222,7 +225,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* now initialize the sequence's data */
 	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -327,7 +330,7 @@ ResetSequence(Oid seq_relid)
 	/*
 	 * Insert the modified tuple into the new storage file.
 	 */
-	fill_seq_with_data(seq_rel, tuple);
+	fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -340,17 +343,21 @@ ResetSequence(Oid seq_relid)
  * Initialize a sequence's relation with the specified tuple as content
  */
 static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 	sequence_magic *sm;
 	OffsetNumber offnum;
+	bool lockBuffer = false;
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ReadBuffer(rel, P_NEW);
-	Assert(BufferGetBlockNumber(buf) == 0);
+	if (buf == InvalidBuffer)
+	{
+		buf = ReadBuffer(rel, P_NEW);
+		Assert(BufferGetBlockNumber(buf) == 0);
+		lockBuffer = true;
+	}
 
 	page = BufferGetPage(buf);
 
@@ -360,7 +367,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* Now insert sequence tuple */
 
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+	if (lockBuffer)
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +418,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	END_CRIT_SECTION();
 
-	UnlockReleaseBuffer(buf);
+	if (lockBuffer)
+		UnlockReleaseBuffer(buf);
 }
 
 /*
@@ -451,6 +460,15 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 
 	init_sequence(relid, &elm, &seqrel);
 
+	if (RELATION_IS_GLOBAL_TEMP(seqrel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(seqrel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary sequence %s when other backend attached it.",
+						RelationGetRelationName(seqrel))));
+	}
+
 	rel = table_open(SequenceRelationId, RowExclusiveLock);
 	seqtuple = SearchSysCacheCopy1(SEQRELID,
 								   ObjectIdGetDatum(relid));
@@ -502,7 +520,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * Insert the modified tuple into the new storage file.
 		 */
-		fill_seq_with_data(seqrel, newdatatuple);
+		fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
 	}
 
 	/* process OWNED BY if given */
@@ -611,7 +629,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +954,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1154,6 +1172,13 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1954,3 +1979,46 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+void
+gtt_init_seq(Relation rel)
+{
+	/* Initialize sequence for global temporary tables */
+	Datum		value[SEQ_COL_LASTCOL] = {0};
+	bool		null[SEQ_COL_LASTCOL] = {false};
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 27b596c..06c10e9 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -556,6 +557,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 								  Relation partitionTbl);
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static OnCommitAction gtt_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -601,6 +603,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -611,8 +614,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")));
@@ -642,7 +647,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	/* global temp table same as local temp table */
+	if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+		 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -743,6 +750,55 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* global temp table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* check parent table*/
+		if (inheritOids)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			table_close(relation, NoLock);
+		}
+
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temp table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1330,6 +1386,7 @@ RemoveRelations(DropStmt *drop)
 		Oid			relOid;
 		ObjectAddress obj;
 		struct DropRelationCallbackState state;
+		char		rel_persistence;
 
 		/*
 		 * These next few steps are a great deal like relation_openrv, but we
@@ -1363,8 +1420,9 @@ RemoveRelations(DropStmt *drop)
 		 * Decide if concurrent mode needs to be used here or not.  The
 		 * relation persistence cannot be known without its OID.
 		 */
+		rel_persistence = get_rel_persistence(relOid);
 		if (drop->concurrent &&
-			get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+			!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		{
 			Assert(list_length(drop->objects) == 1 &&
 				   drop->removeType == OBJECT_INDEX);
@@ -1559,7 +1617,12 @@ ExecuteTruncate(TruncateStmt *stmt)
 		Relation	rel;
 		bool		recurse = rv->inh;
 		Oid			myrelid;
-		LOCKMODE	lockmode = AccessExclusiveLock;
+		LOCKMODE	lockmode;
+
+		if (rv->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+		else
+			lockmode = AccessExclusiveLock;
 
 		myrelid = RangeVarGetRelidExtended(rv, lockmode,
 										   0, RangeVarCallbackForTruncate,
@@ -1819,6 +1882,10 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
 		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 			continue;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			continue;
+
 		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
@@ -3622,6 +3689,16 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -4897,6 +4974,38 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot rewrite temporary tables of other sessions")));
 
+			if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+			{
+				if (tab->chgPersistence)
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot change global temporary table persistence setting")));
+
+				/* gtt may not attached, create it */
+				if(!gtt_storage_attached(tab->relid))
+				{
+					ResultRelInfo *resultRelInfo;
+					MemoryContext oldcontext;
+					MemoryContext ctx_alter_gtt;
+
+					ctx_alter_gtt = AllocSetContextCreate(CurrentMemoryContext,
+											"gtt alter table", ALLOCSET_DEFAULT_SIZES);
+
+					oldcontext = MemoryContextSwitchTo(ctx_alter_gtt);
+					resultRelInfo = makeNode(ResultRelInfo);
+					InitResultRelInfo(resultRelInfo, OldHeap,
+									1, NULL, 0);
+					if (resultRelInfo->ri_RelationDesc->rd_rel->relhasindex &&
+						resultRelInfo->ri_IndexRelationDescs == NULL)
+						ExecOpenIndices(resultRelInfo, false);
+
+					init_gtt_storage(CMD_UTILITY, resultRelInfo);
+					ExecCloseIndices(resultRelInfo);
+					MemoryContextSwitchTo(oldcontext);
+					MemoryContextDelete(ctx_alter_gtt);
+				}
+			}
+
 			/*
 			 * Select destination tablespace (same as original unless user
 			 * requested a change)
@@ -8281,6 +8390,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -12781,6 +12896,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -12983,6 +13101,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temp table");
+
 	/*
 	 * No work if no change in tablespace.
 	 */
@@ -13357,7 +13478,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,
@@ -14764,7 +14885,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",
@@ -17422,3 +17545,36 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
 		table_close(rel, NoLock);
 	}
 }
+
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			bool	res = false;
+			char	*sval = defGetString(def);
+
+			if (!parse_bool(sval, &res))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
+
+			if (res)
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 576c7e6..7927c40 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"
@@ -1215,6 +1216,17 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	 /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		is_gtt = true;
+		up_gtt_relstats(relation,
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1228,17 +1240,26 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relpages = (int32) num_pages;
+	else if (pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (is_gtt)
+		relation->rd_rel->reltuples = (float4) num_tuples;
+	else if (pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	else if (pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1283,7 +1304,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1294,7 +1316,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1392,6 +1415,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
@@ -1449,6 +1476,42 @@ 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	safe_age;
+		TransactionId	oldest_gtt_frozenxid =
+			list_all_session_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid))
+		{
+			safe_age = oldest_gtt_frozenxid + vacuum_gtt_defer_check_age;
+			if (safe_age < FirstNormalTransactionId)
+				safe_age += FirstNormalTransactionId;
+
+			/*
+			 * We tolerate that the minimum age of gtt is less than
+			 * the minimum age of conventional tables, otherwise it will
+			 * throw warning message.
+			 */
+			if (TransactionIdIsNormal(safe_age) &&
+				TransactionIdPrecedes(safe_age, newFrozenXid))
+			{
+				ereport(WARNING,
+					(errmsg("global temp table oldest relfrozenxid %u is the oldest in the entire db",
+							oldest_gtt_frozenxid),
+					 errdetail("The oldest relfrozenxid in pg_class is %u", newFrozenXid),
+					 errhint("If they differ greatly, please consider cleaning up the data in global temp table.")));
+			}
+
+			if (TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+				newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1776,6 +1839,15 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6e65103..04706ee 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -530,6 +530,11 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 4fdffad..0aa7559 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index fb6ce49..06d9237 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
@@ -545,6 +546,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	init_gtt_storage(mtstate->operation, leaf_part_rri);
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 20a4c47..e9f6dcd 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,6 +42,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -2379,6 +2380,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			ExecOpenIndices(resultRelInfo,
 							node->onConflictAction != ONCONFLICT_NONE);
 
+		init_gtt_storage(operation, resultRelInfo);
+
 		/*
 		 * If this is an UPDATE and a BEFORE UPDATE trigger is present, the
 		 * trigger itself might modify the partition-key values. So arrange
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 6da0dcd..1df88a9 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 b40a112..a7368ba 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6412,7 +6412,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
+	/* global temp table is same as local temp table */
 	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 2554502..04fb5d4 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -28,6 +28,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temp table */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index c159fb2..7dc0be8 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2594,6 +2594,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 dbb47d4..71cd86c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3315,17 +3315,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; }
@@ -11407,19 +11401,13 @@ OptTempTableName:
 				}
 			| GLOBAL TEMPORARY opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED opt_table qualified_name
 				{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b875a50..123945b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 25abc54..b5e3e01 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
@@ -3108,6 +3111,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		cxt.isforeign = false;
 	}
 	cxt.relation = stmt->relation;
+	cxt.relation->relpersistence = RelationGetRelPersistence(rel);
 	cxt.rel = rel;
 	cxt.inhRelations = NIL;
 	cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 9c7d4b0..03173a6 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2088,6 +2088,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);
@@ -2154,7 +2159,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 f1ae6f9..f9f420a 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -54,6 +55,7 @@
 #include "utils/rel.h"
 #include "utils/resowner_private.h"
 #include "utils/timestamp.h"
+#include "utils/guc.h"
 
 
 /* Note: these two macros only work on shared buffers, not local ones! */
@@ -2854,6 +2856,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index e850ebd..8cc1f36 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -22,6 +22,7 @@
 #include "access/subtrans.h"
 #include "access/syncscan.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -148,6 +149,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
@@ -218,6 +220,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 b448533..5a2c127 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -63,6 +63,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -4084,3 +4085,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/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 2fa90cc..22f0f81 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -177,7 +177,9 @@ static const char *const BuiltinTrancheNames[] = {
 	/* LWTRANCHE_PARALLEL_APPEND: */
 	"ParallelAppend",
 	/* LWTRANCHE_PER_XACT_PREDICATE_LIST: */
-	"PerXactPredicateList"
+	"PerXactPredicateList",
+	/* LWTRANCHE_GTT_CTL */
+	"GlobalTempTableControl"
 };
 
 StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index e57fcd2..e0e5691 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 2320c06..234e20c 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -993,6 +993,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 53d9741..ca51a81 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -108,6 +108,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -4887,12 +4888,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))
 							{
@@ -5017,15 +5031,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))
 		{
@@ -6446,6 +6472,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;
@@ -6463,6 +6490,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,
@@ -6474,6 +6508,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;
@@ -6489,6 +6525,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,
@@ -7407,6 +7450,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))
@@ -7419,6 +7464,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 =
@@ -7431,6 +7484,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.
@@ -7450,6 +7505,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 f3bf413..1aabd25 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -2988,6 +2989,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 a2453cf..0f3cf8f 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -65,6 +65,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1134,6 +1135,24 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1188,6 +1207,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			gtt_fix_index_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -1318,7 +1338,17 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+			if (newrelnode != InvalidOid &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2247,6 +2277,8 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		gtt_fix_index_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3473,6 +3505,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3580,28 +3616,34 @@ void
 RelationSetNewRelfilenode(Relation relation, char persistence)
 {
 	Oid			newrelfilenode;
-	Relation	pg_class;
-	HeapTuple	tuple;
+	Relation	pg_class = NULL;
+	HeapTuple	tuple = NULL;
 	Form_pg_class classform;
 	MultiXactId minmulti = InvalidMultiXactId;
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
+	bool		modify_pg_class = !RELATION_IS_GLOBAL_TEMP(relation);
 
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
 
-	/*
-	 * Get a writable copy of the pg_class tuple for the given relation.
-	 */
-	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+	if (modify_pg_class)
+	{
+		/*
+		 * Get a writable copy of the pg_class tuple for the given relation.
+		 */
+		pg_class = table_open(RelationRelationId, RowExclusiveLock);
 
-	tuple = SearchSysCacheCopy1(RELOID,
-								ObjectIdGetDatum(RelationGetRelid(relation)));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "could not find tuple for relation %u",
-			 RelationGetRelid(relation));
-	classform = (Form_pg_class) GETSTRUCT(tuple);
+		tuple = SearchSysCacheCopy1(RELOID,
+									ObjectIdGetDatum(RelationGetRelid(relation)));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u",
+				 RelationGetRelid(relation));
+		classform = (Form_pg_class) GETSTRUCT(tuple);
+	}
+	else
+		memset(&classform, 0, sizeof(classform));
 
 	/*
 	 * Schedule unlinking of the old storage at transaction commit.
@@ -3627,7 +3669,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;
@@ -3647,6 +3689,15 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 			break;
 	}
 
+	if (!modify_pg_class)
+	{
+		Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+		Assert(RELATION_IS_GLOBAL_TEMP(relation));
+		Assert(!RelationIsMapped(relation));
+		relation->rd_node.relNode = relnode;
+		CacheInvalidateRelcache(relation);
+	}
 	/*
 	 * If we're dealing with a mapped index, pg_class.relfilenode doesn't
 	 * change; instead we have to send the update to the relation mapper.
@@ -3656,7 +3707,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	 * possibly-inaccurate values of relpages etc, but those will be fixed up
 	 * later.
 	 */
-	if (RelationIsMapped(relation))
+	else if (RelationIsMapped(relation))
 	{
 		/* This case is only supported for indexes */
 		Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3702,9 +3753,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 		CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 	}
 
-	heap_freetuple(tuple);
+	if (modify_pg_class)
+	{
+		heap_freetuple(tuple);
 
-	table_close(pg_class, RowExclusiveLock);
+		table_close(pg_class, RowExclusiveLock);
+	}
 
 	/*
 	 * Make the pg_class row change or relation map change visible.  This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index c20885e..c1d2c52 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -40,6 +40,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "commands/prepare.h"
 #include "commands/trigger.h"
@@ -145,6 +146,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global 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,
@@ -2046,6 +2059,15 @@ static struct config_bool ConfigureNamesBool[] =
 static struct config_int ConfigureNamesInt[] =
 {
 	{
+		{"max_active_global_temporary_table", PGC_POSTMASTER, UNGROUPED,
+			gettext_noop("max active global temporary table."),
+			NULL
+		},
+		&max_active_gtt,
+		DEFAULT_NUM_ACTIVE_GTT, MIN_NUM_ACTIVE_GTT, MAX_NUM_ACTIVE_GTT,
+		NULL, NULL, NULL
+	},
+	{
 		{"archive_timeout", PGC_SIGHUP, WAL_ARCHIVING,
 			gettext_noop("Forces a switch to the next WAL file if a "
 						 "new file has not been started within N seconds."),
@@ -2553,6 +2575,16 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"vacuum_gtt_defer_check_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("The defer check age of GTT, used to check expired data after vacuum."),
+			NULL
+		},
+		&vacuum_gtt_defer_check_age,
+		10000, 0, 1000000,
+		NULL, NULL, NULL
+	},
+
 	/*
 	 * See also CheckRequiredParameterValues() if this parameter changes
 	 */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 94459b3..00482b0 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2416,6 +2416,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
 		dopt->no_unlogged_table_data)
 		return;
 
+	/* Don't dump data in global temp table/sequence */
+	if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		return;
+
 	/* Check that the data is not explicitly excluded */
 	if (simple_oid_list_member(&tabledata_exclude_oids,
 							   tbinfo->dobj.catId.oid))
@@ -15555,6 +15559,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
 		char	   *foreign = "";
+		char	   *table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15608,9 +15613,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);
 
@@ -16004,7 +16015,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
-			 tbinfo->relkind == RELKIND_MATVIEW))
+			 tbinfo->relkind == RELKIND_MATVIEW) &&
+			 tbinfo->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
 		{
 			appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
 			appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
@@ -16909,6 +16921,7 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	PQExpBuffer query = createPQExpBuffer();
 	PQExpBuffer delqry = createPQExpBuffer();
 	char	   *qseqname;
+	bool	   global_temp_seq = false;
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
@@ -16918,9 +16931,11 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
-						  "FROM pg_catalog.pg_sequence "
-						  "WHERE seqrelid = '%u'::oid",
+						  "seqcache, seqcycle, c.relpersistence "
+						  "FROM pg_catalog.pg_sequence s, "
+						  "pg_catalog.pg_class c "
+						  "WHERE seqrelid = '%u'::oid "
+						  "and s.seqrelid = c.oid",
 						  tbinfo->dobj.catId.oid);
 	}
 	else if (fout->remoteVersion >= 80400)
@@ -16965,6 +16980,9 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
 
+	if (fout->remoteVersion >= 130000)
+		global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
 	if (strcmp(seqtype, "smallint") == 0)
@@ -17042,9 +17060,13 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	}
 	else
 	{
-		appendPQExpBuffer(query,
-						  "CREATE SEQUENCE %s\n",
-						  fmtQualifiedDumpable(tbinfo));
+		appendPQExpBuffer(query, "CREATE ");
+
+		if (global_temp_seq)
+			appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+		appendPQExpBuffer(query, "SEQUENCE %s\n",
+								  fmtQualifiedDumpable(tbinfo));
 
 		if (strcmp(seqtype, "bigint") != 0)
 			appendPQExpBuffer(query, "    AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 00aef85..9ad71c8 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -84,7 +84,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -157,7 +157,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 7e524ea..371168f 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -441,22 +443,46 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 	 * output, so we have to copy that system table.  It's easiest to do that
 	 * by treating it as a user table.
 	 */
-	snprintf(query + strlen(query), sizeof(query) - strlen(query),
-			 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
-			 "  SELECT c.oid, 0::oid, 0::oid "
-			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
-			 "         ON c.relnamespace = n.oid "
-			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
-	/* exclude possible orphaned temp tables */
-			 "    ((n.nspname !~ '^pg_temp_' AND "
-			 "      n.nspname !~ '^pg_toast_temp_' AND "
-			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
-			 "                        'binary_upgrade', 'pg_toast') AND "
-			 "      c.oid >= %u::pg_catalog.oid) OR "
-			 "     (n.nspname = 'pg_catalog' AND "
-			 "      relname IN ('pg_largeobject') ))), ",
-			 FirstNormalObjectId);
+	if (skip_gtt)
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude global temp tables */
+				 "    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
+	else
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
 
 	/*
 	 * Add a CTE that collects OIDs of toast tables belonging to the tables
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 70194eb..8dd9e66 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -411,7 +411,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -642,7 +642,9 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -653,7 +655,9 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 8b90cef..d155205 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -389,7 +389,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index e197dcd..caed670 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3738,7 +3738,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 8b73547..c80f2f2 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1023,6 +1023,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2400,6 +2402,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2608,6 +2613,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index cbfdfe2..be39472 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -59,7 +59,8 @@ extern Relation heap_create(const char *relname,
 							bool mapped_relation,
 							bool allow_system_table_mods,
 							TransactionId *relfrozenxid,
-							MultiXactId *relminmxid);
+							MultiXactId *relminmxid,
+							bool skip_create_storage);
 
 extern Oid	heap_create_with_catalog(const char *relname,
 									 Oid relnamespace,
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 78b33b2..4be89f6 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -168,6 +168,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 082a11f..db0bebe 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5560,6 +5560,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4388',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4389',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4390',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4391',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 30c38e0..7ff2408 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,7 +22,7 @@
 /* GUC variables */
 extern int	wal_skip_threshold;
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationPreTruncate(Relation rel);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..902375d
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern int		vacuum_gtt_defer_check_age;
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Relation relation,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void gtt_force_enable_index(Relation index);
+extern void gtt_fix_index_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+extern void gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index e2638ab..89a5ce4 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 3f88683..f870e9a 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index af9b417..6e16f99 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -218,6 +218,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_SHARED_TIDBITMAP,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_PER_XACT_PREDICATE_LIST,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index b20e2ad..97bf2df 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -124,6 +124,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId session_gtt_frozenxid;	/* session level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 2819282..363fc33 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -282,6 +282,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 0b5957b..0720a4c 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	struct SMgrRelationData *rd_smgr;	/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -305,6 +305,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -570,11 +571,13 @@ typedef struct ViewOptions
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -582,6 +585,7 @@ typedef struct ViewOptions
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -594,6 +598,14 @@ typedef struct ViewOptions
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is either local temp relation of this session
+ * 		or global temp relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -637,6 +649,17 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..ca2d135
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,14 @@
+reset search_path;
+select pg_sleep(5);
+ pg_sleep 
+----------
+ 
+(1 row)
+
+drop schema gtt cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
+drop cascades to table gtt.gtt_with_seq
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..d6a675a
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,383 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+ n 
+---
+ 9
+(1 row)
+
+commit;
+-- 0 row
+select * from gtt6;
+ n 
+---
+(0 rows)
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temp table
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  could not create global temporary table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR:  insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count 
+-------
+     1
+(1 row)
+
+select count(*) from orders;
+ count 
+-------
+     0
+(1 row)
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  2 |  3
+(2 rows)
+
+commit;
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+select * from gtt_s_2 order by c1;
+ c1 | c2 
+----+----
+  1 |  2
+  2 |  4
+(2 rows)
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 1)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 300)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 33 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test_new
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.gtt6
+drop cascades to table gtt_function.foo
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to sequence gtt_function.seq_1
+drop cascades to table gtt_function.gtt_s_1
+drop cascades to table gtt_function.gtt_s_2
+drop cascades to table gtt_function.gt1
+drop cascades to table gtt_function.gtt_test1
+drop cascades to table gtt_function.gtt_test2
+drop cascades to table gtt_function.gtt_test3
+drop cascades to table gtt_function.gtt_test4
+drop cascades to table gtt_function.gtt_test5
+drop cascades to table gtt_function.gtt_test6
+drop cascades to table gtt_function.gtt_test7
+drop cascades to table gtt_function.gtt_test8
+drop cascades to table gtt_function.gtt_test9
+drop cascades to table gtt_function.gtt_test10
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..0646aae
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,90 @@
+set search_path=gtt,sys;
+select nextval('gtt_with_seq_c2_seq');
+ nextval 
+---------
+       1
+(1 row)
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..2f084be
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,343 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+rollback;
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+commit;
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          114688 |                 614400
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |            49152 |         49152 |                  49152
+(2 rows)
+
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2000
+(1 row)
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+ c1 | c2 
+----+----
+  1 |  1
+(1 row)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..8c0c376
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,10 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..4420fdb
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,80 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+ gtt_stats  | gtt_pkey  |        1 |         0 |             0
+(2 rows)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+reindex table gtt;
+reindex index gtt_pkey;
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+ gtt_stats  | gtt_pkey  |       30 |     10000 |             0
+(2 rows)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 601734a..591e881 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1349,6 +1349,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 026ea88..592ba9c 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..2c8e586
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,8 @@
+
+
+reset search_path;
+
+select pg_sleep(5);
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..81f0bfc
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,253 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+commit;
+-- 0 row
+select * from gtt6;
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+commit;
+select * from gtt_s_1 order by c1;
+select * from gtt_s_2 order by c1;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d05745e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,44 @@
+
+
+set search_path=gtt,sys;
+
+select nextval('gtt_with_seq_c2_seq');
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..39cca5e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,154 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+rollback;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+commit;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+select count(*) from gtt_t_kenyon;
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..dbe84d1
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,19 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..d61b0ff
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,46 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+reindex table gtt;
+
+reindex index gtt_pkey;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
#284Robert Haas
robertmhaas@gmail.com
In reply to: wenjing zeng (#283)
Re: [Proposal] Global temporary tables

On Thu, Jul 30, 2020 at 8:09 AM wenjing zeng <wjzeng2012@gmail.com> wrote:

Please continue to review the code.

This patch is pretty light on comments. Many of the new functions have
no header comments, for example. There are comments here and there in
the body of the new functions that are added, and in places where
existing code is changed there are comments here and there, but
overall it's not a whole lot. There's no documentation and no README,
either. Since this adds a new feature and a bunch of new SQL-callable
functions that interact with that feature, the feature itself should
be documented, along with its limitations and the new SQL-callable
functions that interact with it. I think there should be either a
lengthy comment in some suitable file, or maybe various comments in
various files, or else a README file, that clearly sets out the major
design principles behind the patch, and explaining also what that
means in terms of features and limitations. Without that, it's really
hard for anyone to jump into reviewing this code, and it will be hard
for people who have to maintain it in the future to understand it,
either. Or for users, for that matter.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#285movead.li@highgo.ca
movead.li@highgo.ca
In reply to: Konstantin Knizhnik (#8)
Re: [Proposal] Global temporary tables

Fixed in global_temporary_table_v29-pg13.patch
Please check.

I find this is the most latest mail with an attachment, so I test and reply on
this thread, several points as below:

1. I notice it produces new relfilenode when new session login and some
data insert. But the relfilenode column in pg_class still the one when create
the global temp table. I think you can try to show 0 in this area as what nail
relation does.

2. The nail relations handle their relfilenodes by RelMapFile struct, and this
patch use hash entry and relfilenode_list, maybe RelMapFile approach more
understandable in my opinion. Sorry if I miss the real design for that.

3. I get a wrong result of pg_relation_filepath() function for global temp table,
I think it's necessaryto keep this an correct output.

4. In gtt_search_by_relid() function, it has not handle the missing_ok argument
if gtt_storage_local_hash is null. There should be some comments if it's the right
code.

5. It's a long patch and hard to review, I think it will pretty good if it can be
divided into several subpatches with relatively independent subfunctions.

Regards,
Highgo Software (Canada/China/Pakistan)
URL : www.highgo.ca
EMAIL: mailto:movead(dot)li(at)highgo(dot)ca

#286曾文旌
wenjing.zwj@alibaba-inc.com
In reply to: movead.li@highgo.ca (#285)
1 attachment(s)
Re: [Proposal] Global temporary tables

Thank you very much for reviewing this patch.
This is very important to improve the GTT.

2020年8月3日 下午3:09,movead.li@highgo.ca 写道:

Fixed in global_temporary_table_v29-pg13.patch
Please check.

I find this is the most latest mail with an attachment, so I test and reply on
this thread, several points as below:

1. I notice it produces new relfilenode when new session login and some
data insert. But the relfilenode column in pg_class still the one when create
the global temp table. I think you can try to show 0 in this area as what nail
relation does.

I think getting the GTT to have a default relfilenode looks closer to the existing implementation, and setting it to 0 requires extra work and has no clear benefit.
What do you think?
I'd like to know the reasons for your suggestion.

2. The nail relations handle their relfilenodes by RelMapFile struct, and this
patch use hash entry and relfilenode_list, maybe RelMapFile approach more
understandable in my opinion. Sorry if I miss the real design for that.

We can see the STORAGE and statistics info for the GTT, including relfilenode, through view pg_gtt_relstats

postgres=# \d gtt
Table "public.gtt"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
a | integer | | |
b | integer | | |

postgres=# insert into gtt values(1,1);
INSERT 0 1
postgres=# select * from pg_gtt_relstats ;
schemaname | tablename | relfilenode | relpages | reltuples | relallvisible | relfrozenxid | relminmxid
------------+-----------+-------------+----------+-----------+---------------+--------------+------------
public | gtt | 16384 | 0 | 0 | 0 | 532 | 1
(1 row)

postgres=# truncate gtt;
TRUNCATE TABLE
postgres=# select * from pg_gtt_relstats ;
schemaname | tablename | relfilenode | relpages | reltuples | relallvisible | relfrozenxid | relminmxid
------------+-----------+-------------+----------+-----------+---------------+--------------+------------
public | gtt | 16387 | 0 | 0 | 0 | 533 | 1
(1 row)

3. I get a wrong result of pg_relation_filepath() function for global temp table,
I think it's necessaryto keep this an correct output.

postgres=# select pg_relation_filepath(oid) from pg_class where relname = 'gtt';
pg_relation_filepath
----------------------
base/13835/t3_16384
(1 row)

I didn't find anything wrong. Could you please give me a demo.

4. In gtt_search_by_relid() function, it has not handle the missing_ok argument
if gtt_storage_local_hash is null. There should be some comments if it's the right
code.

This is a problem that has been fixed in global_temporary_table_v34-pg13.patch.

5. It's a long patch and hard to review, I think it will pretty good if it can be
divided into several subpatches with relatively independent subfunctions.

Thank you for your suggestion, and I am considering doing so, including adding comments.

Wenjing

Show quoted text

Regards,
Highgo Software (Canada/China/Pakistan)
URL : www.highgo.ca <http://www.highgo.ca/&gt;
EMAIL: mailto:movead(dot)li(at)highgo(dot)ca

Attachments:

smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#287曾文旌
wenjing.zwj@alibaba-inc.com
In reply to: Robert Haas (#284)
1 attachment(s)
Re: [Proposal] Global temporary tables

2020年7月31日 上午4:57,Robert Haas <robertmhaas@gmail.com> 写道:

On Thu, Jul 30, 2020 at 8:09 AM wenjing zeng <wjzeng2012@gmail.com> wrote:

Please continue to review the code.

This patch is pretty light on comments. Many of the new functions have
no header comments, for example. There are comments here and there in
the body of the new functions that are added, and in places where
existing code is changed there are comments here and there, but
overall it's not a whole lot. There's no documentation and no README,
either. Since this adds a new feature and a bunch of new SQL-callable
functions that interact with that feature, the feature itself should
be documented, along with its limitations and the new SQL-callable
functions that interact with it. I think there should be either a
lengthy comment in some suitable file, or maybe various comments in
various files, or else a README file, that clearly sets out the major
design principles behind the patch, and explaining also what that
means in terms of features and limitations. Without that, it's really
hard for anyone to jump into reviewing this code, and it will be hard
for people who have to maintain it in the future to understand it,
either. Or for users, for that matter.

Your suggestion is to the point. I do lack a lot of comments, as is necessary.
I'll do this.

Wenjing

Show quoted text

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

Attachments:

smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#288movead.li@highgo.ca
movead.li@highgo.ca
In reply to: Konstantin Knizhnik (#8)
Re: [Proposal] Global temporary tables

I find this is the most latest mail with an attachment, so I test and reply on
this thread, several points as below:

1. I notice it produces new relfilenode when new session login and some
data insert. But the relfilenode column in pg_class still the one when create
the global temp table. I think you can try to show 0 in this area as what nail
relation does.
I think getting the GTT to have a default relfilenode looks closer to the existing implementation, and setting it to 0 requires extra work and has no clear benefit.
What do you think?
I'd like to know the reasons for your suggestion.

The 'relfilenode' mean the file no on disk which different from oid of a relation,
the default one is a wrong for gtt, so I think it's not so good to show it in
pg_class.

2. The nail relations handle their relfilenodes by RelMapFile struct, and this
patch use hash entry and relfilenode_list, maybe RelMapFile approach more
understandable in my opinion. Sorry if I miss the real design for that.
We can see the STORAGE and statistics info for the GTT, including relfilenode, through view pg_gtt_relstats

postgres=# \d gtt
Table "public.gtt"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
a | integer | | |
b | integer | | |

postgres=# insert into gtt values(1,1);
INSERT 0 1
postgres=# select * from pg_gtt_relstats ;
schemaname | tablename | relfilenode | relpages | reltuples | relallvisible | relfrozenxid | relminmxid
------------+-----------+-------------+----------+-----------+---------------+--------------+------------
public | gtt | 16384 | 0 | 0 | 0 | 532 | 1
(1 row)

postgres=# truncate gtt;
TRUNCATE TABLE
postgres=# select * from pg_gtt_relstats ;
schemaname | tablename | relfilenode | relpages | reltuples | relallvisible | relfrozenxid | relminmxid
------------+-----------+-------------+----------+-----------+---------------+--------------+------------
public | gtt | 16387 | 0 | 0 | 0 | 533 | 1
(1 row)

I just suggest a way which maybe most naturely to the exist code struct, and it's
uo to you.

3. I get a wrong result of pg_relation_filepath() function for global temp table,
I think it's necessaryto keep this an correct output.

postgres=# select pg_relation_filepath(oid) from pg_class where relname = 'gtt';
pg_relation_filepath
----------------------
base/13835/t3_16384
(1 row)

I didn't find anything wrong. Could you please give me a demo.

In my opinoin it should show 'base/13835/t3_16387', other than 'base/13835/t3_16384',
because the relfilenode change to 16387 when you truncate it in step 2.

4. In gtt_search_by_relid() function, it has not handle the missing_ok argument
if gtt_storage_local_hash is null. There should be some comments if it's the right
code.
This is a problem that has been fixed in global_temporary_table_v34-pg13.patch.

Sorry about it, I can not find it in mail thread and maybe I miss something. The mail thread
is so long, it's better to create a new mail thread I think.

Regards,
Highgo Software (Canada/China/Pakistan)
URL : www.highgo.ca
EMAIL: mailto:movead(dot)li(at)highgo(dot)ca

#289曾文旌
wenjing.zwj@alibaba-inc.com
In reply to: movead.li@highgo.ca (#288)
2 attachment(s)
Re: [Proposal] Global temporary tables

2020年8月7日 下午5:30,movead.li@highgo.ca 写道:

I find this is the most latest mail with an attachment, so I test and reply on
this thread, several points as below:

1. I notice it produces new relfilenode when new session login and some
data insert. But the relfilenode column in pg_class still the one when create
the global temp table. I think you can try to show 0 in this area as what nail
relation does.

I think getting the GTT to have a default relfilenode looks closer to the existing implementation, and setting it to 0 requires extra work and has no clear benefit.
What do you think?
I'd like to know the reasons for your suggestion.

The 'relfilenode' mean the file no on disk which different from oid of a relation,
the default one is a wrong for gtt, so I think it's not so good to show it in
pg_class.

2. The nail relations handle their relfilenodes by RelMapFile struct, and this
patch use hash entry and relfilenode_list, maybe RelMapFile approach more
understandable in my opinion. Sorry if I miss the real design for that.

We can see the STORAGE and statistics info for the GTT, including relfilenode, through view pg_gtt_relstats

postgres=# \d gtt
Table "public.gtt"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
a | integer | | |
b | integer | | |

postgres=# insert into gtt values(1,1);
INSERT 0 1
postgres=# select * from pg_gtt_relstats ;
schemaname | tablename | relfilenode | relpages | reltuples | relallvisible | relfrozenxid | relminmxid
------------+-----------+-------------+----------+-----------+---------------+--------------+------------
public | gtt | 16384 | 0 | 0 | 0 | 532 | 1
(1 row)

postgres=# truncate gtt;
TRUNCATE TABLE
postgres=# select * from pg_gtt_relstats ;
schemaname | tablename | relfilenode | relpages | reltuples | relallvisible | relfrozenxid | relminmxid
------------+-----------+-------------+----------+-----------+---------------+--------------+------------
public | gtt | 16387 | 0 | 0 | 0 | 533 | 1
(1 row)

I just suggest a way which maybe most naturely to the exist code struct, and it's
uo to you.

3. I get a wrong result of pg_relation_filepath() function for global temp table,
I think it's necessaryto keep this an correct output.

postgres=# select pg_relation_filepath(oid) from pg_class where relname = 'gtt';
pg_relation_filepath
----------------------
base/13835/t3_16384
(1 row)

I didn't find anything wrong. Could you please give me a demo.

In my opinoin it should show 'base/13835/t3_16387', other than 'base/13835/t3_16384',
because the relfilenode change to 16387 when you truncate it in step 2.

4. In gtt_search_by_relid() function, it has not handle the missing_ok argument
if gtt_storage_local_hash is null. There should be some comments if it's the right
code.

This is a problem that has been fixed in global_temporary_table_v34-pg13.patch.

Sorry about it, I can not find it in mail thread and maybe I miss something. The mail thread
is so long, it's better to create a new mail thread I think.

The latest status is tracked here
https://commitfest.postgresql.org/28/2349/ <https://commitfest.postgresql.org/28/2349/&gt;

The latest patch is V35. I don't know why the patches in some of my emails are indexed, but some of them are not.

Wenjing

Show quoted text

Regards,
Highgo Software (Canada/China/Pakistan)
URL : www.highgo.ca <http://www.highgo.ca/&gt;
EMAIL: mailto:movead(dot)li(at)highgo(dot)ca

Attachments:

global_temporary_table_v35-pg13.patchapplication/octet-stream; name=global_temporary_table_v35-pg13.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 8ccc228..b061de9 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -168,6 +168,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * For global temp table only
+	 * use ShareUpdateExclusiveLock for ensure safety
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temp table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},	
 	/* list terminator */
 	{{NULL}}
 };
@@ -1817,6 +1830,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1961,13 +1976,18 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	/*
 	 * There are no options for partitioned tables yet, but this is able to do
 	 * some validation.
 	 */
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index 765329b..4761fdc 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1011,7 +1011,9 @@ gistproperty(Oid index_oid, int attno,
 XLogRecPtr
 gistGetFakeLSN(Relation rel)
 {
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	/* global temp is same as local temp table */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 7c9ccf4..60c50db 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -151,7 +151,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 267a6ee..28b5664 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -589,7 +589,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -642,7 +642,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 1bbc459..eeac2c3 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -62,6 +62,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -439,9 +440,9 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
 	Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
-	/* not every AM requires these to be valid, but heap does */
-	Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
-	Assert(MultiXactIdIsValid(onerel->rd_rel->relminmxid));
+	/* not every AM requires these to be valid, but regular heap does */
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ MultiXactIdIsValid(onerel->rd_rel->relminmxid));
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index d5db9aa..df8395e 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -609,6 +610,10 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 8f72fae..3951f35 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6434,6 +6434,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/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 5eaca27..e5002aa 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -231,7 +231,8 @@ Boot_CreateStmt:
 												   mapped_relation,
 												   true,
 												   &relfrozenxid,
-												   &relminmxid);
+												   &relminmxid,
+												   false);
 						elog(DEBUG4, "bootstrap relation created");
 					}
 					else
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 93cf6d4..647f42f 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -43,6 +43,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 7d6acae..3be8d63 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -393,7 +393,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/* global temp table is same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index f2ca686..167041a 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -304,7 +306,8 @@ heap_create(const char *relname,
 			bool mapped_relation,
 			bool allow_system_table_mods,
 			TransactionId *relfrozenxid,
-			MultiXactId *relminmxid)
+			MultiXactId *relminmxid,
+			bool skip_create_storage)
 {
 	bool		create_storage;
 	Relation	rel;
@@ -404,6 +407,9 @@ heap_create(const char *relname,
 									 relpersistence,
 									 relkind);
 
+	if (skip_create_storage)
+		create_storage = false;
+
 	/*
 	 * Have the storage manager create the relation's disk file, if needed.
 	 *
@@ -427,7 +433,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -994,6 +1000,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -1032,8 +1039,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;
@@ -1297,7 +1314,8 @@ heap_create_with_catalog(const char *relname,
 							   mapped_relation,
 							   allow_system_table_mods,
 							   &relfrozenxid,
-							   &relminmxid);
+							   &relminmxid,
+							   false);
 
 	Assert(relid == RelationGetRelid(new_rel_desc));
 
@@ -1403,6 +1421,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1983,6 +2002,16 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/* We allow to drop global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot drop global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3205,7 +3234,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3217,7 +3246,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3263,8 +3292,13 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
+
+		/* truncate global temp table only need RowExclusiveLock */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3297,6 +3331,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3305,23 +3340,37 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (!gtt_storage_attached(RelationGetRelid(rel)))
+			return;
+
+		/*
+		 * Truncate global temp table only need RowExclusiveLock
+		 */
+		lockmode = RowExclusiveLock;
+	}
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(rel, 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 1be27ee..586d56b 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -53,6 +53,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -717,6 +718,11 @@ index_create(Relation heapRelation,
 	char		relkind;
 	TransactionId relfrozenxid;
 	MultiXactId relminmxid;
+	bool		skip_create_storage = false;
+
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation) &&
+		!gtt_storage_attached(RelationGetRelid(heapRelation)))
+		skip_create_storage = true;
 
 	/* constraint flags can only be set when a constraint is requested */
 	Assert((constr_flags == 0) ||
@@ -870,6 +876,19 @@ index_create(Relation heapRelation,
 						indexRelationName, RelationGetRelationName(heapRelation))));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temp table use concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot reindex global temporary tables concurrently")));
+
+		/* if global temp table not init storage, then skip build index */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -924,7 +943,8 @@ index_create(Relation heapRelation,
 								mapped_relation,
 								allow_system_table_mods,
 								&relfrozenxid,
-								&relminmxid);
+								&relminmxid,
+								skip_create_storage);
 
 	Assert(relfrozenxid == InvalidTransactionId);
 	Assert(relminmxid == InvalidMultiXactId);
@@ -2016,7 +2036,8 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 * lock (see comments in RemoveRelations), and a non-concurrent DROP is
 	 * more efficient.
 	 */
-	Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+	Assert(!(get_rel_persistence(indexId) == RELPERSISTENCE_TEMP ||
+			 get_rel_persistence(indexId) == RELPERSISTENCE_GLOBAL_TEMP) ||
 		   (!concurrent && !concurrent_lock_mode));
 
 	/*
@@ -2048,6 +2069,17 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/* We allow to drop index on global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot drop index %s or global temporary table %s",
+						RelationGetRelationName(userIndexRelation), RelationGetRelationName(userHeapRelation)),
+				 errhint("Because the index is created on the global temporary table and other backend attached it.")));
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2656,6 +2688,11 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	/* update index stats into localhash and rel_rd_rel for global temp table */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		is_gtt = true;
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2741,21 +2778,35 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
+		if (is_gtt)
+			rel->rd_rel->relpages = (int32) relpages;
+		else if (rd_rel->relpages != (int32) relpages)
 		{
 			rd_rel->relpages = (int32) relpages;
 			dirty = true;
 		}
-		if (rd_rel->reltuples != (float4) reltuples)
+
+		if (is_gtt)
+			rel->rd_rel->reltuples = (float4) reltuples;
+		else if (rd_rel->reltuples != (float4) reltuples)
 		{
 			rd_rel->reltuples = (float4) reltuples;
 			dirty = true;
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+
+		if (is_gtt)
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+		else if (rd_rel->relallvisible != (int32) relallvisible)
 		{
 			rd_rel->relallvisible = (int32) relallvisible;
 			dirty = true;
 		}
+
+		if (is_gtt)
+		{
+			up_gtt_relstats(rel, relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
+		}
 	}
 
 	/*
@@ -2869,6 +2920,15 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, index, val);
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			gtt_force_enable_index(indexRelation);
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3424,6 +3484,16 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	 * we only need to be sure no schema or data changes are going on.
 	 */
 	heapId = IndexGetRelation(indexId, false);
+
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		!gtt_storage_attached(indexId))
+	{
+		/* Correction status flag, and return */
+		SetReindexProcessing(heapId, indexId);
+		ResetReindexProcessing();
+		return;
+	}
+
 	heapRelation = table_open(heapId, ShareLock);
 
 	if (progress)
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 0152e38..a5b40af 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -655,6 +655,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		/* global temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp relations in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index 9e6e6c4..f073293 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,6 +27,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -61,6 +62,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			relOid;			/* InvalidOid if not a global temp rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -115,7 +117,7 @@ AddPendingSync(const RelFileNode *rnode)
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -127,6 +129,8 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		/* global temp table use same storage strategy as local temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -154,6 +158,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->relOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
@@ -165,6 +170,13 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		AddPendingSync(&rnode);
 	}
 
+	/* remember global temp table storage info to localhash */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel)
+	{
+		pending->relOid = RelationGetRelid(rel);
+		remember_gtt_storage_info(rnode, rel);
+	}
+
 	return srel;
 }
 
@@ -201,11 +213,15 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->relOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->relOid = RelationGetRelid(rel);
+
 	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
@@ -602,6 +618,7 @@ smgrDoPendingDeletes(bool isCommit)
 				i = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -631,14 +648,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->relOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -651,9 +672,18 @@ smgrDoPendingDeletes(bool isCommit)
 		smgrdounlinkall(srels, nrels, false);
 
 		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* clean global temp table flags when transaction commit or rollback */
+			if (SmgrIsTemp(srels[i]) &&
+				reloids[i] != InvalidOid &&
+				gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..451cf82
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1491 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  code to create and destroy physical storage for global temparary table
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_relstats_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+int		vacuum_gtt_defer_check_age = 0;
+
+typedef struct gtt_ctl_data
+{
+	LWLock			lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+	Oid			relfilenode;
+	Oid			spcnode;
+
+	/* pg_class stat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+
+	/* pg_statistic */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+
+	Oid			oldrelid;			/* remember the source of relid, before the switch relfilenode. */
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_reset_statistics(gtt_local_hash_entry *entry);
+static void gtt_free_statistics(gtt_local_hash_entry *entry);
+static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok);
+static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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();
+	}
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(gtt_fnode);
+	info.entrysize = action_gtt_shared_hash_entry_size();
+	active_gtt_shared_hash =
+		ShmemInitHash("active gtt shared hash",
+						gtt_shared_ctl->max_entry,
+						gtt_shared_ctl->max_entry,
+						&info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			found;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *)&(fnode), 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_global_temporary_table.")));
+	}
+
+	if (found == false)
+	{
+		int			wordnum;
+
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode				fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	if (!skiplock)
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		if (!skiplock)
+			LWLockRelease(&gtt_shared_ctl->lock);
+
+		if (isCommit)
+			elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+	bms_del_member(entry->map, MyBackendId);
+
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+
+	if (!skiplock)
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+	return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	Bitmapset	*map_copy = NULL;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool		in_use = false;
+	int			num_use = 0;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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;
+	gtt_relfilenode			*new_node = NULL;
+	Oid						relid = RelationGetRelid(rel);
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		/* First time through: initialize the hash table */
+		HASHCTL		ctl;
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_relstats_context =
+			AllocSetContextCreate(CacheMemoryContext,
+							"gtt relstats context",
+							ALLOCSET_DEFAULT_SIZES);
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+	entry = gtt_search_by_relid(relid, true);
+	if (!entry)
+	{
+		bool		found = false;
+		int 		natts = 0;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash,
+						(void *) &relid, HASH_ENTER, &found);
+
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+		entry->natts = 0;
+		entry->attnum = NULL;
+		entry->att_stat_tups = NULL;
+		entry->oldrelid = InvalidOid;
+
+		natts = RelationGetNumberOfAttributes(rel);
+		entry->attnum = palloc0(sizeof(int) * natts);
+		entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+		entry->natts = natts;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			gtt_storage_checkin(relid);
+		}
+	}
+
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* only heap contain transaction information */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	gtt_reset_statistics(entry);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	gtt_relfilenode *d_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+		return;
+	}
+
+	d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+		else if (entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+			gtt_relfilenode *gttnode2 = NULL;
+
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+			Assert(gttnode2->relfilenode == rnode.relNode);
+			Assert(list_length(entry->relfilenode_list) == 1);
+			/* rollback switch relfilenode */
+			gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+									   entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+									   false);
+			/* clean up footprint */
+			entry2->oldrelid = InvalidOid;
+			d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+			Assert(d_rnode);
+		}
+		else
+		{
+			if (entry->relfilenode_list == NIL)
+			{
+				if (entry->relkind == RELKIND_RELATION ||
+					entry->relkind == RELKIND_SEQUENCE)
+					gtt_storage_checkout(relid, false, isCommit);
+
+				gtt_free_statistics(entry);
+				hash_search(gtt_storage_local_hash,
+						(void *) &(relid), HASH_REMOVE, NULL);
+			}
+
+			return;
+		}
+	}
+
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	pfree(d_rnode);
+	if (entry->relfilenode_list == NIL)
+	{
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(relid, false, isCommit);
+
+		if (isCommit && entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			/* clean up footprint */
+			entry2->oldrelid = InvalidOid;
+		}
+
+		gtt_free_statistics(entry);
+		hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_REMOVE, NULL);
+	}
+	else
+		gtt_reset_statistics(entry);
+
+	return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool found = false;
+	gtt_local_hash_entry *entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry)
+		found = true;
+
+	return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS				status;
+	gtt_local_hash_entry		*entry;
+	SMgrRelation	*srels = NULL;
+	Oid				*relids = NULL;
+	char			*relkinds = NULL;
+	int			nrels = 0,
+				nfiles = 0,
+				maxrels = 0,
+				maxfiles = 0,
+				i = 0;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel;
+			RelFileNode 	rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel = smgropen(rnode, MyBackendId);
+
+			if (maxfiles == 0)
+			{
+				maxfiles = 32;
+				srels = palloc(sizeof(SMgrRelation) * maxfiles);
+			}
+			else if (maxfiles <= nfiles)
+			{
+				maxfiles *= 2;
+				srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+			}
+
+			srels[nfiles++] = srel;
+		}
+
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			relids  = palloc(sizeof(Oid) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			relids  = repalloc(relids , sizeof(Oid) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		relkinds[nrels] = entry->relkind;
+		relids[nrels] = entry->relid;
+		nrels++;
+	}
+
+	if (nfiles > 0)
+	{
+		smgrdounlinkall(srels, nfiles, false);
+		for (i = 0; i < nfiles; i++)
+			smgrclose(srels[i]);
+
+		pfree(srels);
+	}
+
+	if (nrels)
+	{
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+		for (i = 0; i < nrels; i++)
+		{
+			if (relkinds[i] == RELKIND_RELATION ||
+				relkinds[i] == RELKIND_SEQUENCE)
+				gtt_storage_checkout(relids[i], true, false);
+		}
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+		pfree(relids);
+		pfree(relkinds);
+	}
+
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+	return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible) 
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+					BlockNumber num_pages,
+					double num_tuples,
+					BlockNumber num_all_visible_pages,
+					TransactionId relfrozenxid,
+					TransactionId relminmxid)
+{
+	Oid		relid = RelationGetRelid(relation);
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (num_pages >= 0 &&
+		gtt_rnode->relpages != (int32)num_pages)
+		gtt_rnode->relpages = (int32)num_pages;
+
+	if (num_tuples >= 0 &&
+		gtt_rnode->reltuples != (float4)num_tuples)
+		gtt_rnode->reltuples = (float4)num_tuples;
+
+	/* only heap contain transaction information and relallvisible */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (gtt_rnode->relallvisible >= 0 &&
+			gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+		{
+			gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+		}
+
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNewTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible) 
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	MemoryContext			oldcontext;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return;
+
+	if (entry->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	Assert(entry->relid == reloid);
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == 0)
+		{
+			entry->attnum[i] = attnum;
+			break;
+		}
+		else if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < entry->natts);
+	Assert(entry->att_stat_tups[i] == NULL);
+	entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return NULL;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			return entry->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int			i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	HeapTuple	tuple;
+	Relation	rel = NULL;
+	int		attnum = PG_GETARG_INT32(1);
+	Oid		reloid = PG_GETARG_OID(0);
+	char		rel_persistence;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	  tupdesc;
+	MemoryContext oldcontext;
+	Tuplestorestate *tupstore;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+
+	relation_close(rel, NoLock);
+	relation_close(pg_tatistic, AccessShareLock);
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate	*tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Oid			relnode = 0;
+	Relation	rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[6];
+		bool	isnull[6];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(reloid);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(RelationGetRelid(index)));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid relOid = index->rd_index->indrelid;
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	if (gtt_storage_attached(relOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int			i;
+	Oid			toastrelid;
+
+	if (!(operation == CMD_UTILITY || operation == CMD_INSERT))
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+
+		index_build(relation, index, info, true, false);
+	}
+
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid 		indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo 	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+static void
+gtt_reset_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+
+		entry->attnum[i] = 0;
+	}
+
+	return;
+}
+
+static void
+gtt_free_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (entry->attnum)
+		pfree(entry->attnum);
+
+	if (entry->att_stat_tups)
+		pfree(entry->att_stat_tups);
+
+	return;
+}
+
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
+void
+gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint)
+{
+	gtt_local_hash_entry	*entry1;
+	gtt_local_hash_entry	*entry2;
+	gtt_relfilenode			*gtt_rnode1 = NULL;
+	gtt_relfilenode			*gtt_rnode2 = NULL;
+	MemoryContext			oldcontext;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry1 = gtt_search_by_relid(rel1, false);
+	gtt_rnode1 = gtt_search_relfilenode(entry1, relfilenode1, false);
+
+	entry2 = gtt_search_by_relid(rel2, false);
+	gtt_rnode2 = gtt_search_relfilenode(entry2, relfilenode2, false);
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	entry1->relfilenode_list = list_delete_ptr(entry1->relfilenode_list, gtt_rnode1);
+	entry2->relfilenode_list = lappend(entry2->relfilenode_list, gtt_rnode1);
+
+	entry2->relfilenode_list = list_delete_ptr(entry2->relfilenode_list, gtt_rnode2);
+	entry1->relfilenode_list = lappend(entry1->relfilenode_list, gtt_rnode2);
+	MemoryContextSwitchTo(oldcontext);
+
+	if (footprint)
+	{
+		entry1->oldrelid = rel2;
+		entry2->oldrelid = rel1;
+	}
+
+	return;
+}
+
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok)
+{
+	gtt_relfilenode			*rnode = NULL;
+	ListCell				*lc;
+
+	Assert(entry);
+
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == relfilenode)
+		{
+			rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (!missing_ok && rnode == NULL)
+		elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+	return rnode;
+}
+
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry == NULL && !missing_ok)
+		elog(ERROR, "relid %u not found in local hash", relid);
+
+	return entry;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 8625cbe..a6699ac 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM 
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index e0fa73b..cb890eb 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -184,6 +185,13 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -575,14 +583,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 		 * pg_statistic for columns we didn't process, we leave them alone.)
 		 */
 		update_attstats(RelationGetRelid(onerel), inh,
-						attr_cnt, vacattrstats);
+						attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
 
 		for (ind = 0; ind < nindexes; ind++)
 		{
 			AnlIndexData *thisdata = &indexdata[ind];
 
 			update_attstats(RelationGetRelid(Irel[ind]), false,
-							thisdata->attr_cnt, thisdata->vacattrstats);
+							thisdata->attr_cnt, thisdata->vacattrstats,
+							RelationGetRelPersistence(Irel[ind]));
 		}
 
 		/*
@@ -1445,7 +1454,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
  *		by taking a self-exclusive lock on the relation in analyze_rel().
  */
 static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
 {
 	Relation	sd;
 	int			attno;
@@ -1547,31 +1556,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/* Update column statistic to localhash, not catalog */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 04d12a7..42c82c8 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/toasting.h"
 #include "commands/cluster.h"
 #include "commands/progress.h"
@@ -72,6 +73,12 @@ static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
 							bool verbose, bool *pSwapToastByContent,
 							TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
 static List *get_tables_to_cluster(MemoryContext cluster_context);
+static void gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables);
 
 
 /*---------------------------------------------------------------------------
@@ -366,6 +373,14 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap) &&
+		!gtt_storage_attached(RelationGetRelid(OldHeap)))
+	{
+		relation_close(OldHeap, AccessExclusiveLock);
+		pgstat_progress_end_command();
+		return;
+	}
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
@@ -750,6 +765,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	BlockNumber num_pages;
 	int			elevel = verbose ? INFO : DEBUG2;
 	PGRUsage	ru0;
+	bool		is_gtt = false;
+	uint32		gtt_relfrozenxid = 0;
+	uint32		gtt_relminmxid = 0;
 
 	pg_rusage_init(&ru0);
 
@@ -763,6 +781,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	else
 		OldIndex = NULL;
 
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		is_gtt = true;
+
 	/*
 	 * Their tuple descriptors should be exactly alike, but here we only need
 	 * assume that they have the same number of columns.
@@ -830,20 +851,37 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 						  &OldestXmin, &FreezeXid, NULL, &MultiXactCutoff,
 						  NULL);
 
-	/*
-	 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
-	 * backwards, so take the max.
-	 */
-	if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
-		TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
-		FreezeXid = OldHeap->rd_rel->relfrozenxid;
+	if (is_gtt)
+	{
+		get_gtt_relstats(OIDOldHeap,
+					NULL, NULL, NULL,
+					&gtt_relfrozenxid, &gtt_relminmxid);
 
-	/*
-	 * MultiXactCutoff, similarly, shouldn't go backwards either.
-	 */
-	if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
-		MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
-		MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+		if (TransactionIdIsValid(gtt_relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, gtt_relfrozenxid))
+			FreezeXid = gtt_relfrozenxid;
+
+		if (MultiXactIdIsValid(gtt_relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, gtt_relminmxid))
+			MultiXactCutoff = gtt_relminmxid;
+	}
+	else
+	{
+		/*
+		 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
+		 * backwards, so take the max.
+		 */
+		if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
+			FreezeXid = OldHeap->rd_rel->relfrozenxid;
+
+		/*
+		 * MultiXactCutoff, similarly, shouldn't go backwards either.
+		 */
+		if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
+			MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+	}
 
 	/*
 	 * Decide whether to use an indexscan or seqscan-and-optional-sort to scan
@@ -911,6 +949,12 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	table_close(OldHeap, NoLock);
 	table_close(NewHeap, NoLock);
 
+	if (is_gtt)
+	{
+		CommandCounterIncrement();
+		return;
+	}
+
 	/* Update pg_class to reflect the correct values of pages and tuples. */
 	relRelation = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1346,10 +1390,21 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 	 * Swap the contents of the heap relations (including any toast tables).
 	 * Also set old heap's relfrozenxid to frozenXid.
 	 */
-	swap_relation_files(OIDOldHeap, OIDNewHeap,
+	if (newrelpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		Assert(!is_system_catalog);
+		gtt_swap_relation_files(OIDOldHeap, OIDNewHeap,
+								(OIDOldHeap == RelationRelationId),
+								swap_toast_by_content, is_internal,
+								frozenXid, cutoffMulti, mapped_tables);
+	}
+	else
+	{
+		swap_relation_files(OIDOldHeap, OIDNewHeap,
 						(OIDOldHeap == RelationRelationId),
 						swap_toast_by_content, is_internal,
 						frozenXid, cutoffMulti, mapped_tables);
+	}
 
 	/*
 	 * If it's a system catalog, queue a sinval message to flush all catcaches
@@ -1557,3 +1612,141 @@ get_tables_to_cluster(MemoryContext cluster_context)
 
 	return rvs;
 }
+
+static void
+gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables)
+{
+	Relation	relRelation;
+	Oid			relfilenode1,
+				relfilenode2;
+	Relation	rel1;
+	Relation	rel2;
+
+	relRelation = table_open(RelationRelationId, RowExclusiveLock);
+
+	rel1 = relation_open(r1, AccessExclusiveLock);
+	rel2 = relation_open(r2, AccessExclusiveLock);
+
+	relfilenode1 = gtt_fetch_current_relfilenode(r1);
+	relfilenode2 = gtt_fetch_current_relfilenode(r2);
+
+	Assert(OidIsValid(relfilenode1) && OidIsValid(relfilenode2));
+	gtt_switch_rel_relfilenode(r1, relfilenode1, r2, relfilenode2, true);
+
+	CacheInvalidateRelcache(rel1);
+	CacheInvalidateRelcache(rel2);
+
+	InvokeObjectPostAlterHookArg(RelationRelationId, r1, 0,
+								 InvalidOid, is_internal);
+	InvokeObjectPostAlterHookArg(RelationRelationId, r2, 0,
+								 InvalidOid, true);
+
+	if (rel1->rd_rel->reltoastrelid || rel2->rd_rel->reltoastrelid)
+	{
+		if (swap_toast_by_content)
+		{
+			if (rel1->rd_rel->reltoastrelid && rel2->rd_rel->reltoastrelid)
+			{
+				gtt_swap_relation_files(rel1->rd_rel->reltoastrelid,
+									rel2->rd_rel->reltoastrelid,
+									target_is_pg_class,
+									swap_toast_by_content,
+									is_internal,
+									frozenXid,
+									cutoffMulti,
+									mapped_tables);
+			}
+			else
+				elog(ERROR, "cannot swap toast files by content when there's only one");
+		}
+		else
+		{
+			ObjectAddress baseobject,
+						toastobject;
+			long		count;
+
+			if (IsSystemRelation(rel1))
+				elog(ERROR, "cannot swap toast files by links for system catalogs");
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel1->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel2->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+
+			/* Register new dependencies */
+			baseobject.classId = RelationRelationId;
+			baseobject.objectSubId = 0;
+			toastobject.classId = RelationRelationId;
+			toastobject.objectSubId = 0;
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r1;
+				toastobject.objectId = rel1->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r2;
+				toastobject.objectId = rel2->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+		}
+	}
+
+	if (swap_toast_by_content &&
+		rel1->rd_rel->relkind == RELKIND_TOASTVALUE &&
+		rel2->rd_rel->relkind == RELKIND_TOASTVALUE)
+	{
+		Oid			toastIndex1,
+					toastIndex2;
+
+		/* Get valid index for each relation */
+		toastIndex1 = toast_get_valid_index(r1,
+											AccessExclusiveLock);
+		toastIndex2 = toast_get_valid_index(r2,
+											AccessExclusiveLock);
+
+		gtt_swap_relation_files(toastIndex1,
+							toastIndex2,
+							target_is_pg_class,
+							swap_toast_by_content,
+							is_internal,
+							InvalidTransactionId,
+							InvalidMultiXactId,
+							mapped_tables);
+	}
+
+	relation_close(rel1, NoLock);
+	relation_close(rel2, NoLock);
+
+	table_close(relRelation, RowExclusiveLock);
+
+	RelationCloseSmgrByOid(r1);
+	RelationCloseSmgrByOid(r2);
+
+	CommandCounterIncrement();
+}
+
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index db7d24a..daf9d1c 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -27,6 +27,7 @@
 #include "catalog/dependency.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -1113,7 +1114,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, stmt->filename, stmt->is_program,
@@ -2838,6 +2839,8 @@ CopyFrom(CopyState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	estate->es_result_relations = resultRelInfo;
 	estate->es_num_result_relations = 1;
 	estate->es_result_relation_info = resultRelInfo;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 7819266..d68b6d1 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -521,6 +521,7 @@ DefineIndex(Oid relationId,
 	Snapshot	snapshot;
 	int			save_nestlevel = -1;
 	int			i;
+	char		rel_persistence;
 
 	/*
 	 * Some callers need us to run with an empty default_tablespace; this is a
@@ -542,7 +543,9 @@ DefineIndex(Oid relationId,
 	 * is more efficient.  Do this before any use of the concurrent option is
 	 * done.
 	 */
-	if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+	rel_persistence = get_rel_persistence(relationId);
+	if (stmt->concurrent &&
+		!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		concurrent = true;
 	else
 		concurrent = false;
@@ -2460,7 +2463,8 @@ ReindexIndex(RangeVar *indexRelation, int options, bool concurrent)
 	persistence = irel->rd_rel->relpersistence;
 	index_close(irel, NoLock);
 
-	if (concurrent && persistence != RELPERSISTENCE_TEMP)
+	if (concurrent &&
+		!(persistence == RELPERSISTENCE_TEMP || persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		ReindexRelationConcurrently(indOid, options);
 	else
 		reindex_index(indOid, false, persistence,
@@ -2546,6 +2550,7 @@ ReindexTable(RangeVar *relation, int options, bool concurrent)
 {
 	Oid			heapOid;
 	bool		result;
+	char		rel_persistence;
 
 	/*
 	 * The lock level used here should match reindex_relation().
@@ -2560,7 +2565,9 @@ ReindexTable(RangeVar *relation, int options, bool concurrent)
 									   0,
 									   RangeVarCallbackOwnsTable, NULL);
 
-	if (concurrent && get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+	rel_persistence = get_rel_persistence(heapOid);
+	if (concurrent &&
+		!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 	{
 		result = ReindexRelationConcurrently(heapOid, options);
 
@@ -2761,12 +2768,15 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 	foreach(l, relids)
 	{
 		Oid			relid = lfirst_oid(l);
+		char		rel_persistence;
 
 		StartTransactionCommand();
 		/* functions in indexes may want a snapshot set */
 		PushActiveSnapshot(GetTransactionSnapshot());
 
-		if (concurrent && get_rel_persistence(relid) != RELPERSISTENCE_TEMP)
+		rel_persistence = get_rel_persistence(relid);
+		if (concurrent &&
+			!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		{
 			(void) ReindexRelationConcurrently(relid, options);
 			/* ReindexRelationConcurrently() does the verbose output */
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index d8cafc4..d7a7604 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -56,7 +56,9 @@ LockTableCommand(LockStmt *lockstmt)
 										  RangeVarCallbackForLockTable,
 										  (void *) &lockstmt->mode);
 
-		if (get_rel_relkind(reloid) == RELKIND_VIEW)
+		if (get_rel_persistence(reloid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+		else if (get_rel_relkind(reloid) == RELKIND_VIEW)
 			LockViewRecurse(reloid, lockstmt->mode, lockstmt->nowait, NIL);
 		else if (recurse)
 			LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait);
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..234ffa6 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -94,7 +96,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -222,7 +225,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* now initialize the sequence's data */
 	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -327,7 +330,7 @@ ResetSequence(Oid seq_relid)
 	/*
 	 * Insert the modified tuple into the new storage file.
 	 */
-	fill_seq_with_data(seq_rel, tuple);
+	fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -340,17 +343,21 @@ ResetSequence(Oid seq_relid)
  * Initialize a sequence's relation with the specified tuple as content
  */
 static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 	sequence_magic *sm;
 	OffsetNumber offnum;
+	bool lockBuffer = false;
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ReadBuffer(rel, P_NEW);
-	Assert(BufferGetBlockNumber(buf) == 0);
+	if (buf == InvalidBuffer)
+	{
+		buf = ReadBuffer(rel, P_NEW);
+		Assert(BufferGetBlockNumber(buf) == 0);
+		lockBuffer = true;
+	}
 
 	page = BufferGetPage(buf);
 
@@ -360,7 +367,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* Now insert sequence tuple */
 
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+	if (lockBuffer)
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +418,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	END_CRIT_SECTION();
 
-	UnlockReleaseBuffer(buf);
+	if (lockBuffer)
+		UnlockReleaseBuffer(buf);
 }
 
 /*
@@ -451,6 +460,15 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 
 	init_sequence(relid, &elm, &seqrel);
 
+	if (RELATION_IS_GLOBAL_TEMP(seqrel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(seqrel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary sequence %s when other backend attached it.",
+						RelationGetRelationName(seqrel))));
+	}
+
 	rel = table_open(SequenceRelationId, RowExclusiveLock);
 	seqtuple = SearchSysCacheCopy1(SEQRELID,
 								   ObjectIdGetDatum(relid));
@@ -502,7 +520,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * Insert the modified tuple into the new storage file.
 		 */
-		fill_seq_with_data(seqrel, newdatatuple);
+		fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
 	}
 
 	/* process OWNED BY if given */
@@ -611,7 +629,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +954,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1154,6 +1172,13 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1954,3 +1979,46 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+void
+gtt_init_seq(Relation rel)
+{
+	/* Initialize sequence for global temporary tables */
+	Datum		value[SEQ_COL_LASTCOL] = {0};
+	bool		null[SEQ_COL_LASTCOL] = {false};
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cd989c9..5abdc6e 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -556,6 +557,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 								  Relation partitionTbl);
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static OnCommitAction gtt_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -601,6 +603,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -611,8 +614,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")));
@@ -642,7 +647,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	/* global temp table same as local temp table */
+	if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+		 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -743,6 +750,55 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* global temp table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* check parent table*/
+		if (inheritOids)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			table_close(relation, NoLock);
+		}
+
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temp table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1330,6 +1386,7 @@ RemoveRelations(DropStmt *drop)
 		Oid			relOid;
 		ObjectAddress obj;
 		struct DropRelationCallbackState state;
+		char		rel_persistence;
 
 		/*
 		 * These next few steps are a great deal like relation_openrv, but we
@@ -1363,8 +1420,9 @@ RemoveRelations(DropStmt *drop)
 		 * Decide if concurrent mode needs to be used here or not.  The
 		 * relation persistence cannot be known without its OID.
 		 */
+		rel_persistence = get_rel_persistence(relOid);
 		if (drop->concurrent &&
-			get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+			!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		{
 			Assert(list_length(drop->objects) == 1 &&
 				   drop->removeType == OBJECT_INDEX);
@@ -1559,7 +1617,12 @@ ExecuteTruncate(TruncateStmt *stmt)
 		Relation	rel;
 		bool		recurse = rv->inh;
 		Oid			myrelid;
-		LOCKMODE	lockmode = AccessExclusiveLock;
+		LOCKMODE	lockmode;
+
+		if (rv->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+		else
+			lockmode = AccessExclusiveLock;
 
 		myrelid = RangeVarGetRelidExtended(rv, lockmode,
 										   0, RangeVarCallbackForTruncate,
@@ -1819,6 +1882,10 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
 		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 			continue;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			continue;
+
 		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
@@ -3622,6 +3689,16 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -4897,6 +4974,38 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot rewrite temporary tables of other sessions")));
 
+			if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+			{
+				if (tab->chgPersistence)
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot change global temporary table persistence setting")));
+
+				/* gtt may not attached, create it */
+				if(!gtt_storage_attached(tab->relid))
+				{
+					ResultRelInfo *resultRelInfo;
+					MemoryContext oldcontext;
+					MemoryContext ctx_alter_gtt;
+
+					ctx_alter_gtt = AllocSetContextCreate(CurrentMemoryContext,
+											"gtt alter table", ALLOCSET_DEFAULT_SIZES);
+
+					oldcontext = MemoryContextSwitchTo(ctx_alter_gtt);
+					resultRelInfo = makeNode(ResultRelInfo);
+					InitResultRelInfo(resultRelInfo, OldHeap,
+									1, NULL, 0);
+					if (resultRelInfo->ri_RelationDesc->rd_rel->relhasindex &&
+						resultRelInfo->ri_IndexRelationDescs == NULL)
+						ExecOpenIndices(resultRelInfo, false);
+
+					init_gtt_storage(CMD_UTILITY, resultRelInfo);
+					ExecCloseIndices(resultRelInfo);
+					MemoryContextSwitchTo(oldcontext);
+					MemoryContextDelete(ctx_alter_gtt);
+				}
+			}
+
 			/*
 			 * Select destination tablespace (same as original unless user
 			 * requested a change)
@@ -8285,6 +8394,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -12785,6 +12900,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -12987,6 +13105,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temp table");
+
 	/*
 	 * No work if no change in tablespace.
 	 */
@@ -13361,7 +13482,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,
@@ -14768,7 +14889,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",
@@ -17426,3 +17549,36 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
 		table_close(rel, NoLock);
 	}
 }
+
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			bool	res = false;
+			char	*sval = defGetString(def);
+
+			if (!parse_bool(sval, &res))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
+
+			if (res)
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 576c7e6..7927c40 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"
@@ -1215,6 +1216,17 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	 /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		is_gtt = true;
+		up_gtt_relstats(relation,
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1228,17 +1240,26 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relpages = (int32) num_pages;
+	else if (pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (is_gtt)
+		relation->rd_rel->reltuples = (float4) num_tuples;
+	else if (pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	else if (pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1283,7 +1304,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1294,7 +1316,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1392,6 +1415,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
@@ -1449,6 +1476,42 @@ 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	safe_age;
+		TransactionId	oldest_gtt_frozenxid =
+			list_all_session_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid))
+		{
+			safe_age = oldest_gtt_frozenxid + vacuum_gtt_defer_check_age;
+			if (safe_age < FirstNormalTransactionId)
+				safe_age += FirstNormalTransactionId;
+
+			/*
+			 * We tolerate that the minimum age of gtt is less than
+			 * the minimum age of conventional tables, otherwise it will
+			 * throw warning message.
+			 */
+			if (TransactionIdIsNormal(safe_age) &&
+				TransactionIdPrecedes(safe_age, newFrozenXid))
+			{
+				ereport(WARNING,
+					(errmsg("global temp table oldest relfrozenxid %u is the oldest in the entire db",
+							oldest_gtt_frozenxid),
+					 errdetail("The oldest relfrozenxid in pg_class is %u", newFrozenXid),
+					 errhint("If they differ greatly, please consider cleaning up the data in global temp table.")));
+			}
+
+			if (TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+				newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1776,6 +1839,15 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6e65103..04706ee 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -530,6 +530,11 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 4fdffad..0aa7559 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 79fcbd6..6a674d7 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
@@ -545,6 +546,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	init_gtt_storage(mtstate->operation, leaf_part_rri);
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 20a4c47..e9f6dcd 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,6 +42,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -2379,6 +2380,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			ExecOpenIndices(resultRelInfo,
 							node->onConflictAction != ONCONFLICT_NONE);
 
+		init_gtt_storage(operation, resultRelInfo);
+
 		/*
 		 * If this is an UPDATE and a BEFORE UPDATE trigger is present, the
 		 * trigger itself might modify the partition-key values. So arrange
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 6da0dcd..1df88a9 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 b40a112..a7368ba 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6412,7 +6412,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
+	/* global temp table is same as local temp table */
 	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 2554502..04fb5d4 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -28,6 +28,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temp table */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index c159fb2..7dc0be8 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2594,6 +2594,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 dbb47d4..71cd86c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3315,17 +3315,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; }
@@ -11407,19 +11401,13 @@ OptTempTableName:
 				}
 			| GLOBAL TEMPORARY opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED opt_table qualified_name
 				{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b875a50..123945b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 25abc54..b5e3e01 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
@@ -3108,6 +3111,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		cxt.isforeign = false;
 	}
 	cxt.relation = stmt->relation;
+	cxt.relation->relpersistence = RelationGetRelPersistence(rel);
 	cxt.rel = rel;
 	cxt.inhRelations = NIL;
 	cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 9c7d4b0..03173a6 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2088,6 +2088,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);
@@ -2154,7 +2159,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 f1ae6f9..f9f420a 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -54,6 +55,7 @@
 #include "utils/rel.h"
 #include "utils/resowner_private.h"
 #include "utils/timestamp.h"
+#include "utils/guc.h"
 
 
 /* Note: these two macros only work on shared buffers, not local ones! */
@@ -2854,6 +2856,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 96c2aaa..73d099d 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -22,6 +22,7 @@
 #include "access/subtrans.h"
 #include "access/syncscan.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -149,6 +150,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, active_gtt_shared_hash_size());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -221,6 +223,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* 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 5225186..ff2ba22 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -63,6 +63,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -4163,3 +4164,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/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 2fa90cc..22f0f81 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -177,7 +177,9 @@ static const char *const BuiltinTrancheNames[] = {
 	/* LWTRANCHE_PARALLEL_APPEND: */
 	"ParallelAppend",
 	/* LWTRANCHE_PER_XACT_PREDICATE_LIST: */
-	"PerXactPredicateList"
+	"PerXactPredicateList",
+	/* LWTRANCHE_GTT_CTL */
+	"GlobalTempTableControl"
 };
 
 StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index e57fcd2..e0e5691 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 2320c06..234e20c 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -993,6 +993,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 53d9741..ca51a81 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -108,6 +108,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -4887,12 +4888,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))
 							{
@@ -5017,15 +5031,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))
 		{
@@ -6446,6 +6472,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;
@@ -6463,6 +6490,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,
@@ -6474,6 +6508,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;
@@ -6489,6 +6525,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,
@@ -7407,6 +7450,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))
@@ -7419,6 +7464,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 =
@@ -7431,6 +7484,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.
@@ -7450,6 +7505,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 f3bf413..1aabd25 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -2988,6 +2989,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 a2453cf..0f3cf8f 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -65,6 +65,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1134,6 +1135,24 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1188,6 +1207,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			gtt_fix_index_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -1318,7 +1338,17 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+			if (newrelnode != InvalidOid &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2247,6 +2277,8 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		gtt_fix_index_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3473,6 +3505,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3580,28 +3616,34 @@ void
 RelationSetNewRelfilenode(Relation relation, char persistence)
 {
 	Oid			newrelfilenode;
-	Relation	pg_class;
-	HeapTuple	tuple;
+	Relation	pg_class = NULL;
+	HeapTuple	tuple = NULL;
 	Form_pg_class classform;
 	MultiXactId minmulti = InvalidMultiXactId;
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
+	bool		modify_pg_class = !RELATION_IS_GLOBAL_TEMP(relation);
 
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
 
-	/*
-	 * Get a writable copy of the pg_class tuple for the given relation.
-	 */
-	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+	if (modify_pg_class)
+	{
+		/*
+		 * Get a writable copy of the pg_class tuple for the given relation.
+		 */
+		pg_class = table_open(RelationRelationId, RowExclusiveLock);
 
-	tuple = SearchSysCacheCopy1(RELOID,
-								ObjectIdGetDatum(RelationGetRelid(relation)));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "could not find tuple for relation %u",
-			 RelationGetRelid(relation));
-	classform = (Form_pg_class) GETSTRUCT(tuple);
+		tuple = SearchSysCacheCopy1(RELOID,
+									ObjectIdGetDatum(RelationGetRelid(relation)));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u",
+				 RelationGetRelid(relation));
+		classform = (Form_pg_class) GETSTRUCT(tuple);
+	}
+	else
+		memset(&classform, 0, sizeof(classform));
 
 	/*
 	 * Schedule unlinking of the old storage at transaction commit.
@@ -3627,7 +3669,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;
@@ -3647,6 +3689,15 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 			break;
 	}
 
+	if (!modify_pg_class)
+	{
+		Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+		Assert(RELATION_IS_GLOBAL_TEMP(relation));
+		Assert(!RelationIsMapped(relation));
+		relation->rd_node.relNode = relnode;
+		CacheInvalidateRelcache(relation);
+	}
 	/*
 	 * If we're dealing with a mapped index, pg_class.relfilenode doesn't
 	 * change; instead we have to send the update to the relation mapper.
@@ -3656,7 +3707,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	 * possibly-inaccurate values of relpages etc, but those will be fixed up
 	 * later.
 	 */
-	if (RelationIsMapped(relation))
+	else if (RelationIsMapped(relation))
 	{
 		/* This case is only supported for indexes */
 		Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3702,9 +3753,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 		CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 	}
 
-	heap_freetuple(tuple);
+	if (modify_pg_class)
+	{
+		heap_freetuple(tuple);
 
-	table_close(pg_class, RowExclusiveLock);
+		table_close(pg_class, RowExclusiveLock);
+	}
 
 	/*
 	 * Make the pg_class row change or relation map change visible.  This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index de87ad6..24817ea 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -40,6 +40,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "commands/prepare.h"
 #include "commands/trigger.h"
@@ -145,6 +146,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global 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,
@@ -2046,6 +2059,15 @@ static struct config_bool ConfigureNamesBool[] =
 static struct config_int ConfigureNamesInt[] =
 {
 	{
+		{"max_active_global_temporary_table", PGC_POSTMASTER, UNGROUPED,
+			gettext_noop("max active global temporary table."),
+			NULL
+		},
+		&max_active_gtt,
+		DEFAULT_NUM_ACTIVE_GTT, MIN_NUM_ACTIVE_GTT, MAX_NUM_ACTIVE_GTT,
+		NULL, NULL, NULL
+	},
+	{
 		{"archive_timeout", PGC_SIGHUP, WAL_ARCHIVING,
 			gettext_noop("Forces a switch to the next WAL file if a "
 						 "new file has not been started within N seconds."),
@@ -2564,6 +2586,16 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"vacuum_gtt_defer_check_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("The defer check age of GTT, used to check expired data after vacuum."),
+			NULL
+		},
+		&vacuum_gtt_defer_check_age,
+		10000, 0, 1000000,
+		NULL, NULL, NULL
+	},
+
 	/*
 	 * See also CheckRequiredParameterValues() if this parameter changes
 	 */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 9c8436d..1de30b2 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2416,6 +2416,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
 		dopt->no_unlogged_table_data)
 		return;
 
+	/* Don't dump data in global temp table/sequence */
+	if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		return;
+
 	/* Check that the data is not explicitly excluded */
 	if (simple_oid_list_member(&tabledata_exclude_oids,
 							   tbinfo->dobj.catId.oid))
@@ -15555,6 +15559,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
 		char	   *foreign = "";
+		char	   *table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15608,9 +15613,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);
 
@@ -16004,7 +16015,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
-			 tbinfo->relkind == RELKIND_MATVIEW))
+			 tbinfo->relkind == RELKIND_MATVIEW) &&
+			 tbinfo->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
 		{
 			appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
 			appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
@@ -16909,6 +16921,7 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	PQExpBuffer query = createPQExpBuffer();
 	PQExpBuffer delqry = createPQExpBuffer();
 	char	   *qseqname;
+	bool	   global_temp_seq = false;
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
@@ -16918,9 +16931,11 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
-						  "FROM pg_catalog.pg_sequence "
-						  "WHERE seqrelid = '%u'::oid",
+						  "seqcache, seqcycle, c.relpersistence "
+						  "FROM pg_catalog.pg_sequence s, "
+						  "pg_catalog.pg_class c "
+						  "WHERE seqrelid = '%u'::oid "
+						  "and s.seqrelid = c.oid",
 						  tbinfo->dobj.catId.oid);
 	}
 	else if (fout->remoteVersion >= 80400)
@@ -16965,6 +16980,9 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
 
+	if (fout->remoteVersion >= 130000)
+		global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
 	if (strcmp(seqtype, "smallint") == 0)
@@ -17042,9 +17060,13 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	}
 	else
 	{
-		appendPQExpBuffer(query,
-						  "CREATE SEQUENCE %s\n",
-						  fmtQualifiedDumpable(tbinfo));
+		appendPQExpBuffer(query, "CREATE ");
+
+		if (global_temp_seq)
+			appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+		appendPQExpBuffer(query, "SEQUENCE %s\n",
+								  fmtQualifiedDumpable(tbinfo));
 
 		if (strcmp(seqtype, "bigint") != 0)
 			appendPQExpBuffer(query, "    AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 00aef85..9ad71c8 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -84,7 +84,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -157,7 +157,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 7e524ea..371168f 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -441,22 +443,46 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 	 * output, so we have to copy that system table.  It's easiest to do that
 	 * by treating it as a user table.
 	 */
-	snprintf(query + strlen(query), sizeof(query) - strlen(query),
-			 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
-			 "  SELECT c.oid, 0::oid, 0::oid "
-			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
-			 "         ON c.relnamespace = n.oid "
-			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
-	/* exclude possible orphaned temp tables */
-			 "    ((n.nspname !~ '^pg_temp_' AND "
-			 "      n.nspname !~ '^pg_toast_temp_' AND "
-			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
-			 "                        'binary_upgrade', 'pg_toast') AND "
-			 "      c.oid >= %u::pg_catalog.oid) OR "
-			 "     (n.nspname = 'pg_catalog' AND "
-			 "      relname IN ('pg_largeobject') ))), ",
-			 FirstNormalObjectId);
+	if (skip_gtt)
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude global temp tables */
+				 "    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
+	else
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
 
 	/*
 	 * Add a CTE that collects OIDs of toast tables belonging to the tables
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 70194eb..8dd9e66 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -411,7 +411,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -642,7 +642,9 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -653,7 +655,9 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 8b90cef..d155205 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -389,7 +389,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index d81f157..2a1afcd 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3738,7 +3738,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index f41785f..ed841dd 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1029,6 +1029,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2406,6 +2408,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2614,6 +2619,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index d31141c..44f767a 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -59,7 +59,8 @@ extern Relation heap_create(const char *relname,
 							bool mapped_relation,
 							bool allow_system_table_mods,
 							TransactionId *relfrozenxid,
-							MultiXactId *relminmxid);
+							MultiXactId *relminmxid,
+							bool skip_create_storage);
 
 extern Oid	heap_create_with_catalog(const char *relname,
 									 Oid relnamespace,
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 78b33b2..4be89f6 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -168,6 +168,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 082a11f..db0bebe 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5560,6 +5560,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4388',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4389',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4390',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4391',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 30c38e0..7ff2408 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,7 +22,7 @@
 /* GUC variables */
 extern int	wal_skip_threshold;
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationPreTruncate(Relation rel);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..902375d
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern int		vacuum_gtt_defer_check_age;
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Relation relation,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void gtt_force_enable_index(Relation index);
+extern void gtt_fix_index_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+extern void gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index e2638ab..89a5ce4 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 3f88683..f870e9a 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index af9b417..6e16f99 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -218,6 +218,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_SHARED_TIDBITMAP,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_PER_XACT_PREDICATE_LIST,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 5ceb249..e34db8b 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -123,6 +123,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 01040d7..031a80f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -118,4 +118,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 2819282..363fc33 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -282,6 +282,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 0b5957b..0720a4c 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	struct SMgrRelationData *rd_smgr;	/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -305,6 +305,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -570,11 +571,13 @@ typedef struct ViewOptions
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -582,6 +585,7 @@ typedef struct ViewOptions
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -594,6 +598,14 @@ typedef struct ViewOptions
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is either local temp relation of this session
+ * 		or global temp relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -637,6 +649,17 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..ca2d135
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,14 @@
+reset search_path;
+select pg_sleep(5);
+ pg_sleep 
+----------
+ 
+(1 row)
+
+drop schema gtt cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
+drop cascades to table gtt.gtt_with_seq
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..d6a675a
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,383 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+ n 
+---
+ 9
+(1 row)
+
+commit;
+-- 0 row
+select * from gtt6;
+ n 
+---
+(0 rows)
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temp table
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  could not create global temporary table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR:  insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count 
+-------
+     1
+(1 row)
+
+select count(*) from orders;
+ count 
+-------
+     0
+(1 row)
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  2 |  3
+(2 rows)
+
+commit;
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+select * from gtt_s_2 order by c1;
+ c1 | c2 
+----+----
+  1 |  2
+  2 |  4
+(2 rows)
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 1)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 300)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 33 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test_new
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.gtt6
+drop cascades to table gtt_function.foo
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to sequence gtt_function.seq_1
+drop cascades to table gtt_function.gtt_s_1
+drop cascades to table gtt_function.gtt_s_2
+drop cascades to table gtt_function.gt1
+drop cascades to table gtt_function.gtt_test1
+drop cascades to table gtt_function.gtt_test2
+drop cascades to table gtt_function.gtt_test3
+drop cascades to table gtt_function.gtt_test4
+drop cascades to table gtt_function.gtt_test5
+drop cascades to table gtt_function.gtt_test6
+drop cascades to table gtt_function.gtt_test7
+drop cascades to table gtt_function.gtt_test8
+drop cascades to table gtt_function.gtt_test9
+drop cascades to table gtt_function.gtt_test10
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..0646aae
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,90 @@
+set search_path=gtt,sys;
+select nextval('gtt_with_seq_c2_seq');
+ nextval 
+---------
+       1
+(1 row)
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..2f084be
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,343 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+rollback;
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+commit;
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          114688 |                 614400
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |            49152 |         49152 |                  49152
+(2 rows)
+
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2000
+(1 row)
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+ c1 | c2 
+----+----
+  1 |  1
+(1 row)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..8c0c376
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,10 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..4420fdb
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,80 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+ gtt_stats  | gtt_pkey  |        1 |         0 |             0
+(2 rows)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+reindex table gtt;
+reindex index gtt_pkey;
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+ gtt_stats  | gtt_pkey  |       30 |     10000 |             0
+(2 rows)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 601734a..591e881 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1349,6 +1349,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 026ea88..592ba9c 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..2c8e586
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,8 @@
+
+
+reset search_path;
+
+select pg_sleep(5);
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..81f0bfc
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,253 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+commit;
+-- 0 row
+select * from gtt6;
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+commit;
+select * from gtt_s_1 order by c1;
+select * from gtt_s_2 order by c1;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d05745e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,44 @@
+
+
+set search_path=gtt,sys;
+
+select nextval('gtt_with_seq_c2_seq');
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..39cca5e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,154 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+rollback;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+commit;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+select count(*) from gtt_t_kenyon;
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..dbe84d1
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,19 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..d61b0ff
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,46 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+reindex table gtt;
+
+reindex index gtt_pkey;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#290曾文旌
wenjing.zwj@alibaba-inc.com
In reply to: Robert Haas (#284)
2 attachment(s)
Re: [Proposal] Global temporary tables

I have written the README for the GTT, which contains the GTT requirements and design.
I found that compared to my first email a year ago, many GTT Limitations are now gone.
Now, I'm adding comments to some of the necessary functions.

Wenjing

Attachments:

global_temporary_table_v36-pg13.patchapplication/octet-stream; name=global_temporary_table_v36-pg13.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 8ccc228..b061de9 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -168,6 +168,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * For global temp table only
+	 * use ShareUpdateExclusiveLock for ensure safety
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temp table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},	
 	/* list terminator */
 	{{NULL}}
 };
@@ -1817,6 +1830,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1961,13 +1976,18 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	/*
 	 * There are no options for partitioned tables yet, but this is able to do
 	 * some validation.
 	 */
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index 0516059..cf22399 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1009,7 +1009,9 @@ gistproperty(Oid index_oid, int attno,
 XLogRecPtr
 gistGetFakeLSN(Relation rel)
 {
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	/* global temp is same as local temp table */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 7c9ccf4..60c50db 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -151,7 +151,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 dcaea71..536aab6 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -589,7 +589,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -642,7 +642,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 92389e6..8cd17ea 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -62,6 +62,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -445,9 +446,9 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
 	Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
-	/* not every AM requires these to be valid, but heap does */
-	Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
-	Assert(MultiXactIdIsValid(onerel->rd_rel->relminmxid));
+	/* not every AM requires these to be valid, but regular heap does */
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ MultiXactIdIsValid(onerel->rd_rel->relminmxid));
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 7f39248..3b8853a 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -609,6 +610,10 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 09c01ed..26e0be2 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6434,6 +6434,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/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 5eaca27..e5002aa 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -231,7 +231,8 @@ Boot_CreateStmt:
 												   mapped_relation,
 												   true,
 												   &relfrozenxid,
-												   &relminmxid);
+												   &relminmxid,
+												   false);
 						elog(DEBUG4, "bootstrap relation created");
 					}
 					else
diff --git a/src/backend/catalog/GTT_README b/src/backend/catalog/GTT_README
new file mode 100644
index 0000000..864cdae
--- /dev/null
+++ b/src/backend/catalog/GTT_README
@@ -0,0 +1,170 @@
+Global temporary table(GTT)
+==============
+
+Feature description
+--------------------------------
+
+Previously, temporary tables are defined once and automatically
+created (starting with empty contents) in every session before using them.
+
+The temporary table implementation in PostgreSQL, known as Local temp tables(LTT),
+did not fully comply with the SQL standard. This version added the support of
+Global temporary Table .
+
+The metadata of Global temporary table is persistent and shared among sessions.
+The data stored in the Global temporary table is independent of sessions. This
+means, when a session creates a global temporary table and writes some data.
+Other sessions cannot see those data, but they have an empty Global temporary
+table with same schema.
+
+Like local temporary table, global temporary table supports ON COMMIT PRESERVE ROWS
+or ON COMMIT DELETE ROWS clause, so that data in the temporary table can be
+cleaned up or reserved automatically when a session exits or a transaction COMMITs.
+
+Unlike Local Temporary Table, Global temporary table does not support
+ON COMMIT DROP clauses.
+
+In following paragraphs, we use GTT for global temporary table and LTT for
+local temporary table.
+
+Main design ideas
+-----------------------------------------
+
+STORAGE & BUFFER
+
+In general, GTT and LTT use the same storage and buffer design and
+implementation. The storage files for both types of temporary tables are named
+as t_backendid_relfilenode, and the local buffer is used to cache the data.
+
+The schema of GTTs is shared among sessions while their data are not. We build
+a new mechanisms to manage those non-shared data and their statistics.
+Here is the summary of changes:
+
+1) CATALOG
+GTTs store session-specific data. The storage information of GTTs'data, their
+transaction information, and their statistics are not stored in the catalog.
+
+2) STORAGE INFO & STATISTICS & TRANSACTION
+In order to maintain durability and availability of GTTs'session-specific data,
+their storage information, statistics, and transaction information is managed
+in a local hash table tt_storage_local_hash.
+
+3) DDL
+A shared hash table active_gtt_shared_hash is added to track the state of the
+GTT in a different session. This information is recorded in the hash table
+during the DDL execution of the GTT.
+
+4) LOCK
+The data stored in a GTT can only be modified or accessed by owning session.
+The statements that only modify data in a GTT do not need a high level of table
+locking.
+Changes to the GTT's metadata affect all sessions.
+The operations making those changes include truncate GTT, Vacuum/Cluster GTT,
+and Lock GTT.
+
+Detailed design
+-----------------------------------------
+
+1. CATALOG
+1.1 relpersistence
+define RELPERSISTENCEGLOBALTEMP 'g'
+Mark global temp table in pg_class relpersistence to 'g'. The relpersistence
+of indexes created on the GTT, sequences on GTT and toast tables on GTT are
+also set to 'g'
+
+1.2 on commit clause
+LTT's status associated with on commit DELETE ROWS and on commit PRESERVE ROWS
+is not stored in catalog. Instead, GTTs need a bool value on_commit_delete_rows
+in reloptions which is shared among sessions.
+
+1.3 gram.y
+GTT is already supported in syntax tree. We remove the warning message
+"GLOBAL is deprecated in temporary table creation" and mark
+relpersistence = RELPERSISTENCEGLOBALTEMP.
+
+2. STORAGE INFO & STATISTICS DATA & TRANSACTION INFO
+2.1. gtt_storage_local_hash
+Each backend creates a local hashtable gtt_storage_local_hash to track a GTT's
+storage file information, statistics, and transaction information.
+
+2.2 GTT storage file info track
+1) When one session inserts data into a GTT for the first time, record the
+storage info to gtt_storage_local_hash.
+2) Use beforeshmemexit to ensure that all files ofa session GTT are deleted when
+the session exits.
+3) GTT storage file cleanup during abnormal situations
+When a backend exits abnormally (such as oom kill), the startup process starts
+recovery before accepting client connection. The same startup process checks
+nd removes all GTT files before redo WAL.
+
+2.3 statistics info
+1) relpages reltuples relallvisible
+2) The statistics of each column from pg_statistic
+All the above information is stored in gtt_storage_local_hash.
+When doing vacuum or analyze, GTT's statistic is updated, which is used by
+the SQL planner.
+The statistics summarizes only data in the current session.
+
+2.3 transaction info track
+frozenxid minmulti from pg_class is stored to gtt_storage_local_hash.
+
+4 DDL
+4.1. active_gtt_shared_hash
+This is the hash table created in shared memory to trace the GTT files initialized
+in each session. Each hash entry contains a bitmap that records the backendid of
+the initialized GTT file. With this hash table, we know which backend/session
+is using this GTT. Such information is used during GTT's DDL operations.
+
+4.1 DROP GTT
+One GTT is allowed to be deleted when there is only one session using the table
+and the session is the current session.
+After holding the AccessExclusiveLock lock on GTT, active_gtt_shared_hash
+is checked to ensure that.
+
+4.2 ALTER GTT/DROP INDEX ON GTT
+Same as drop GTT.
+
+4.3 CREATE INDEX ON GTT
+1) create index on GTT statements build index based on local data in a session.
+2) After the index is created, record the index metadata to the catalog.
+3) Other sessions can enable or disable the local GTT index.
+
+5 LOCK
+
+5.1 TRUNCATE GTT
+The truncate GTT command uses RowExclusiveLock, not AccessExclusiveLock, because
+this command only cleans up local data and local buffers in current session.
+
+5.2 CLUSTER GTT/VACUUM FULL GTT
+Same as truncate GTT.
+
+5.3 Lock GTT
+A lock GTT statement does not hold any table locks.
+
+6 MVCC commit log(clog) cleanup
+
+The GTT storage file contains transaction information. Queries for GTT data rely
+on transaction information such as clog. The transaction information required by
+each session may be completely different. We need to ensure that the transaction
+information of the GTT data is not cleaned up during its lifetime and that
+transaction resources are recycled at the instance level.
+
+6.1 The session level GTT oldest frozenxid
+1) To manage all GTT transaction information, add session level oldest frozenxid
+in each session. When one GTT is created or removed, record the session level
+oldest frozenxid and store it in MyProc.
+2) When vacuum advances the database's frozenxid, session level oldest frozenxid
+should be considered. This is acquired by searching all of MyProc. This way,
+we can avoid the clog required by GTTs to be cleaned.
+
+6.2 vacuum GTT
+Users can perform vacuum over a GTT to clean up local data in the GTT.
+
+6.3 autovacuum GTT
+Autovacuum skips all GTTs, because the data in GTTs is only visible in current
+session.
+
+7 OTHERS
+Parallel query
+Planner does not produce parallel query plans for SQL related to GTT. Because 
+GTT private data cannot be accessed across processes.
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 93cf6d4..647f42f 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -43,6 +43,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 7d6acae..3be8d63 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -393,7 +393,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/* global temp table is same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 67144aa..149b36d 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -304,7 +306,8 @@ heap_create(const char *relname,
 			bool mapped_relation,
 			bool allow_system_table_mods,
 			TransactionId *relfrozenxid,
-			MultiXactId *relminmxid)
+			MultiXactId *relminmxid,
+			bool skip_create_storage)
 {
 	bool		create_storage;
 	Relation	rel;
@@ -404,6 +407,9 @@ heap_create(const char *relname,
 									 relpersistence,
 									 relkind);
 
+	if (skip_create_storage)
+		create_storage = false;
+
 	/*
 	 * Have the storage manager create the relation's disk file, if needed.
 	 *
@@ -427,7 +433,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -988,6 +994,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -1026,8 +1033,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;
@@ -1291,7 +1308,8 @@ heap_create_with_catalog(const char *relname,
 							   mapped_relation,
 							   allow_system_table_mods,
 							   &relfrozenxid,
-							   &relminmxid);
+							   &relminmxid,
+							   false);
 
 	Assert(relid == RelationGetRelid(new_rel_desc));
 
@@ -1397,6 +1415,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1975,6 +1994,16 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/* We allow to drop global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot drop global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3197,7 +3226,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3209,7 +3238,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3255,8 +3284,13 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
+
+		/* truncate global temp table only need RowExclusiveLock */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3289,6 +3323,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3297,23 +3332,37 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (!gtt_storage_attached(RelationGetRelid(rel)))
+			return;
+
+		/*
+		 * Truncate global temp table only need RowExclusiveLock
+		 */
+		lockmode = RowExclusiveLock;
+	}
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(rel, 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 117e3fd..9f7350c 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -53,6 +53,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -718,6 +719,11 @@ index_create(Relation heapRelation,
 	char		relkind;
 	TransactionId relfrozenxid;
 	MultiXactId relminmxid;
+	bool		skip_create_storage = false;
+
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation) &&
+		!gtt_storage_attached(RelationGetRelid(heapRelation)))
+		skip_create_storage = true;
 
 	/* constraint flags can only be set when a constraint is requested */
 	Assert((constr_flags == 0) ||
@@ -871,6 +877,19 @@ index_create(Relation heapRelation,
 						indexRelationName, RelationGetRelationName(heapRelation))));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temp table use concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot reindex global temporary tables concurrently")));
+
+		/* if global temp table not init storage, then skip build index */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -925,7 +944,8 @@ index_create(Relation heapRelation,
 								mapped_relation,
 								allow_system_table_mods,
 								&relfrozenxid,
-								&relminmxid);
+								&relminmxid,
+								skip_create_storage);
 
 	Assert(relfrozenxid == InvalidTransactionId);
 	Assert(relminmxid == InvalidMultiXactId);
@@ -2030,7 +2050,8 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 * lock (see comments in RemoveRelations), and a non-concurrent DROP is
 	 * more efficient.
 	 */
-	Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+	Assert(!(get_rel_persistence(indexId) == RELPERSISTENCE_TEMP ||
+			 get_rel_persistence(indexId) == RELPERSISTENCE_GLOBAL_TEMP) ||
 		   (!concurrent && !concurrent_lock_mode));
 
 	/*
@@ -2062,6 +2083,17 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/* We allow to drop index on global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot drop index %s or global temporary table %s",
+						RelationGetRelationName(userIndexRelation), RelationGetRelationName(userHeapRelation)),
+				 errhint("Because the index is created on the global temporary table and other backend attached it.")));
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2670,6 +2702,11 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	/* update index stats into localhash and rel_rd_rel for global temp table */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		is_gtt = true;
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2764,21 +2801,35 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
+		if (is_gtt)
+			rel->rd_rel->relpages = (int32) relpages;
+		else if (rd_rel->relpages != (int32) relpages)
 		{
 			rd_rel->relpages = (int32) relpages;
 			dirty = true;
 		}
-		if (rd_rel->reltuples != (float4) reltuples)
+
+		if (is_gtt)
+			rel->rd_rel->reltuples = (float4) reltuples;
+		else if (rd_rel->reltuples != (float4) reltuples)
 		{
 			rd_rel->reltuples = (float4) reltuples;
 			dirty = true;
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+
+		if (is_gtt)
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+		else if (rd_rel->relallvisible != (int32) relallvisible)
 		{
 			rd_rel->relallvisible = (int32) relallvisible;
 			dirty = true;
 		}
+
+		if (is_gtt)
+		{
+			up_gtt_relstats(rel, relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
+		}
 	}
 
 	/*
@@ -2892,6 +2943,15 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, index, val);
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			gtt_force_enable_index(indexRelation);
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3466,6 +3526,15 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	if (!heapRelation)
 		return;
 
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		!gtt_storage_attached(indexId))
+	{
+		/* Correction status flag, and return */
+		SetReindexProcessing(heapId, indexId);
+		ResetReindexProcessing();
+		return;
+	}
+
 	if (progress)
 	{
 		pgstat_progress_start_command(PROGRESS_COMMAND_CREATE_INDEX,
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 0152e38..a5b40af 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -655,6 +655,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		/* global temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp relations in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index dbbd3aa..7cf96ec 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,6 +27,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -61,6 +62,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			relOid;			/* InvalidOid if not a global temp rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -115,7 +117,7 @@ AddPendingSync(const RelFileNode *rnode)
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -127,6 +129,8 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		/* global temp table use same storage strategy as local temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -154,6 +158,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->relOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
@@ -165,6 +170,13 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		AddPendingSync(&rnode);
 	}
 
+	/* remember global temp table storage info to localhash */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel)
+	{
+		pending->relOid = RelationGetRelid(rel);
+		remember_gtt_storage_info(rnode, rel);
+	}
+
 	return srel;
 }
 
@@ -201,11 +213,15 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->relOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->relOid = RelationGetRelid(rel);
+
 	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
@@ -601,6 +617,7 @@ smgrDoPendingDeletes(bool isCommit)
 	int			nrels = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -630,14 +647,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->relOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -647,12 +668,23 @@ smgrDoPendingDeletes(bool isCommit)
 
 	if (nrels > 0)
 	{
+		int	i;
+
 		smgrdounlinkall(srels, nrels, false);
 
-		for (int i = 0; i < nrels; i++)
+		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* clean global temp table flags when transaction commit or rollback */
+			if (SmgrIsTemp(srels[i]) &&
+				reloids[i] != InvalidOid &&
+				gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..c77812a
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1494 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  The body implementation of Global Temparary table.
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *	  See src/backend/catalog/GTT_README for Global temparary table's
+ *	  requirements and design.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_relstats_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+int		vacuum_gtt_defer_check_age = 0;
+
+typedef struct gtt_ctl_data
+{
+	LWLock			lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+	Oid			relfilenode;
+	Oid			spcnode;
+
+	/* pg_class stat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+
+	/* pg_statistic */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+
+	Oid			oldrelid;			/* remember the source of relid, before the switch relfilenode. */
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_reset_statistics(gtt_local_hash_entry *entry);
+static void gtt_free_statistics(gtt_local_hash_entry *entry);
+static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok);
+static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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();
+	}
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(gtt_fnode);
+	info.entrysize = action_gtt_shared_hash_entry_size();
+	active_gtt_shared_hash =
+		ShmemInitHash("active gtt shared hash",
+						gtt_shared_ctl->max_entry,
+						gtt_shared_ctl->max_entry,
+						&info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			found;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *)&(fnode), 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_global_temporary_table.")));
+	}
+
+	if (found == false)
+	{
+		int			wordnum;
+
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode				fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	if (!skiplock)
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		if (!skiplock)
+			LWLockRelease(&gtt_shared_ctl->lock);
+
+		if (isCommit)
+			elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+	bms_del_member(entry->map, MyBackendId);
+
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+
+	if (!skiplock)
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+	return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	Bitmapset	*map_copy = NULL;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool		in_use = false;
+	int			num_use = 0;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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;
+	gtt_relfilenode			*new_node = NULL;
+	Oid						relid = RelationGetRelid(rel);
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		/* First time through: initialize the hash table */
+		HASHCTL		ctl;
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_relstats_context =
+			AllocSetContextCreate(CacheMemoryContext,
+							"gtt relstats context",
+							ALLOCSET_DEFAULT_SIZES);
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+	entry = gtt_search_by_relid(relid, true);
+	if (!entry)
+	{
+		bool		found = false;
+		int 		natts = 0;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash,
+						(void *) &relid, HASH_ENTER, &found);
+
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+		entry->natts = 0;
+		entry->attnum = NULL;
+		entry->att_stat_tups = NULL;
+		entry->oldrelid = InvalidOid;
+
+		natts = RelationGetNumberOfAttributes(rel);
+		entry->attnum = palloc0(sizeof(int) * natts);
+		entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+		entry->natts = natts;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			gtt_storage_checkin(relid);
+		}
+	}
+
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* only heap contain transaction information */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	gtt_reset_statistics(entry);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	gtt_relfilenode *d_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+		return;
+	}
+
+	d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+		else if (entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+			gtt_relfilenode *gttnode2 = NULL;
+
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+			Assert(gttnode2->relfilenode == rnode.relNode);
+			Assert(list_length(entry->relfilenode_list) == 1);
+			/* rollback switch relfilenode */
+			gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+									   entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+									   false);
+			/* clean up footprint */
+			entry2->oldrelid = InvalidOid;
+			d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+			Assert(d_rnode);
+		}
+		else
+		{
+			if (entry->relfilenode_list == NIL)
+			{
+				if (entry->relkind == RELKIND_RELATION ||
+					entry->relkind == RELKIND_SEQUENCE)
+					gtt_storage_checkout(relid, false, isCommit);
+
+				gtt_free_statistics(entry);
+				hash_search(gtt_storage_local_hash,
+						(void *) &(relid), HASH_REMOVE, NULL);
+			}
+
+			return;
+		}
+	}
+
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	pfree(d_rnode);
+	if (entry->relfilenode_list == NIL)
+	{
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(relid, false, isCommit);
+
+		if (isCommit && entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			/* clean up footprint */
+			entry2->oldrelid = InvalidOid;
+		}
+
+		gtt_free_statistics(entry);
+		hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_REMOVE, NULL);
+	}
+	else
+		gtt_reset_statistics(entry);
+
+	return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool found = false;
+	gtt_local_hash_entry *entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry)
+		found = true;
+
+	return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS				status;
+	gtt_local_hash_entry		*entry;
+	SMgrRelation	*srels = NULL;
+	Oid				*relids = NULL;
+	char			*relkinds = NULL;
+	int			nrels = 0,
+				nfiles = 0,
+				maxrels = 0,
+				maxfiles = 0,
+				i = 0;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel;
+			RelFileNode 	rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel = smgropen(rnode, MyBackendId);
+
+			if (maxfiles == 0)
+			{
+				maxfiles = 32;
+				srels = palloc(sizeof(SMgrRelation) * maxfiles);
+			}
+			else if (maxfiles <= nfiles)
+			{
+				maxfiles *= 2;
+				srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+			}
+
+			srels[nfiles++] = srel;
+		}
+
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			relids  = palloc(sizeof(Oid) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			relids  = repalloc(relids , sizeof(Oid) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		relkinds[nrels] = entry->relkind;
+		relids[nrels] = entry->relid;
+		nrels++;
+	}
+
+	if (nfiles > 0)
+	{
+		smgrdounlinkall(srels, nfiles, false);
+		for (i = 0; i < nfiles; i++)
+			smgrclose(srels[i]);
+
+		pfree(srels);
+	}
+
+	if (nrels)
+	{
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+		for (i = 0; i < nrels; i++)
+		{
+			if (relkinds[i] == RELKIND_RELATION ||
+				relkinds[i] == RELKIND_SEQUENCE)
+				gtt_storage_checkout(relids[i], true, false);
+		}
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+		pfree(relids);
+		pfree(relkinds);
+	}
+
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+	return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible) 
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+					BlockNumber num_pages,
+					double num_tuples,
+					BlockNumber num_all_visible_pages,
+					TransactionId relfrozenxid,
+					TransactionId relminmxid)
+{
+	Oid		relid = RelationGetRelid(relation);
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (num_pages >= 0 &&
+		gtt_rnode->relpages != (int32)num_pages)
+		gtt_rnode->relpages = (int32)num_pages;
+
+	if (num_tuples >= 0 &&
+		gtt_rnode->reltuples != (float4)num_tuples)
+		gtt_rnode->reltuples = (float4)num_tuples;
+
+	/* only heap contain transaction information and relallvisible */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (gtt_rnode->relallvisible >= 0 &&
+			gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+		{
+			gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+		}
+
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNewTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible) 
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	MemoryContext			oldcontext;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return;
+
+	if (entry->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	Assert(entry->relid == reloid);
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == 0)
+		{
+			entry->attnum[i] = attnum;
+			break;
+		}
+		else if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < entry->natts);
+	Assert(entry->att_stat_tups[i] == NULL);
+	entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return NULL;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			return entry->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int			i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	HeapTuple	tuple;
+	Relation	rel = NULL;
+	int		attnum = PG_GETARG_INT32(1);
+	Oid		reloid = PG_GETARG_OID(0);
+	char		rel_persistence;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	  tupdesc;
+	MemoryContext oldcontext;
+	Tuplestorestate *tupstore;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+
+	relation_close(rel, NoLock);
+	relation_close(pg_tatistic, AccessShareLock);
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate	*tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Oid			relnode = 0;
+	Relation	rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[6];
+		bool	isnull[6];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(reloid);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(RelationGetRelid(index)));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid relOid = index->rd_index->indrelid;
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	if (gtt_storage_attached(relOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int			i;
+	Oid			toastrelid;
+
+	if (!(operation == CMD_UTILITY || operation == CMD_INSERT))
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+
+		index_build(relation, index, info, true, false);
+	}
+
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid 		indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo 	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+static void
+gtt_reset_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+
+		entry->attnum[i] = 0;
+	}
+
+	return;
+}
+
+static void
+gtt_free_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (entry->attnum)
+		pfree(entry->attnum);
+
+	if (entry->att_stat_tups)
+		pfree(entry->att_stat_tups);
+
+	return;
+}
+
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
+void
+gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint)
+{
+	gtt_local_hash_entry	*entry1;
+	gtt_local_hash_entry	*entry2;
+	gtt_relfilenode			*gtt_rnode1 = NULL;
+	gtt_relfilenode			*gtt_rnode2 = NULL;
+	MemoryContext			oldcontext;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry1 = gtt_search_by_relid(rel1, false);
+	gtt_rnode1 = gtt_search_relfilenode(entry1, relfilenode1, false);
+
+	entry2 = gtt_search_by_relid(rel2, false);
+	gtt_rnode2 = gtt_search_relfilenode(entry2, relfilenode2, false);
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	entry1->relfilenode_list = list_delete_ptr(entry1->relfilenode_list, gtt_rnode1);
+	entry2->relfilenode_list = lappend(entry2->relfilenode_list, gtt_rnode1);
+
+	entry2->relfilenode_list = list_delete_ptr(entry2->relfilenode_list, gtt_rnode2);
+	entry1->relfilenode_list = lappend(entry1->relfilenode_list, gtt_rnode2);
+	MemoryContextSwitchTo(oldcontext);
+
+	if (footprint)
+	{
+		entry1->oldrelid = rel2;
+		entry2->oldrelid = rel1;
+	}
+
+	return;
+}
+
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok)
+{
+	gtt_relfilenode			*rnode = NULL;
+	ListCell				*lc;
+
+	Assert(entry);
+
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == relfilenode)
+		{
+			rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (!missing_ok && rnode == NULL)
+		elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+	return rnode;
+}
+
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry == NULL && !missing_ok)
+		elog(ERROR, "relid %u not found in local hash", relid);
+
+	return entry;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index ed4f3f1..91e5add 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM 
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 8af12b5..5e5a69d 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -184,6 +185,13 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -575,14 +583,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 		 * pg_statistic for columns we didn't process, we leave them alone.)
 		 */
 		update_attstats(RelationGetRelid(onerel), inh,
-						attr_cnt, vacattrstats);
+						attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
 
 		for (ind = 0; ind < nindexes; ind++)
 		{
 			AnlIndexData *thisdata = &indexdata[ind];
 
 			update_attstats(RelationGetRelid(Irel[ind]), false,
-							thisdata->attr_cnt, thisdata->vacattrstats);
+							thisdata->attr_cnt, thisdata->vacattrstats,
+							RelationGetRelPersistence(Irel[ind]));
 		}
 
 		/*
@@ -1445,7 +1454,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
  *		by taking a self-exclusive lock on the relation in analyze_rel().
  */
 static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
 {
 	Relation	sd;
 	int			attno;
@@ -1547,31 +1556,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/* Update column statistic to localhash, not catalog */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 0d647e9..1e85f06 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/toasting.h"
 #include "commands/cluster.h"
 #include "commands/progress.h"
@@ -75,6 +76,12 @@ static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
 							TransactionId *pFreezeXid,
 							MultiXactId *pCutoffMulti);
 static List *get_tables_to_cluster(MemoryContext cluster_context);
+static void gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables);
 
 
 /*---------------------------------------------------------------------------
@@ -370,6 +377,14 @@ cluster_rel(Oid tableOid, Oid indexOid, int options, bool isTopLevel)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap) &&
+		!gtt_storage_attached(RelationGetRelid(OldHeap)))
+	{
+		relation_close(OldHeap, AccessExclusiveLock);
+		pgstat_progress_end_command();
+		return;
+	}
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
@@ -756,6 +771,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
 	BlockNumber num_pages;
 	int			elevel = verbose ? INFO : DEBUG2;
 	PGRUsage	ru0;
+	bool		is_gtt = false;
+	uint32		gtt_relfrozenxid = 0;
+	uint32		gtt_relminmxid = 0;
 
 	pg_rusage_init(&ru0);
 
@@ -769,6 +787,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
 	else
 		OldIndex = NULL;
 
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		is_gtt = true;
+
 	/*
 	 * Their tuple descriptors should be exactly alike, but here we only need
 	 * assume that they have the same number of columns.
@@ -836,20 +857,37 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
 						  &OldestXmin, &FreezeXid, NULL, &MultiXactCutoff,
 						  NULL);
 
-	/*
-	 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
-	 * backwards, so take the max.
-	 */
-	if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
-		TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
-		FreezeXid = OldHeap->rd_rel->relfrozenxid;
+	if (is_gtt)
+	{
+		get_gtt_relstats(OIDOldHeap,
+					NULL, NULL, NULL,
+					&gtt_relfrozenxid, &gtt_relminmxid);
 
-	/*
-	 * MultiXactCutoff, similarly, shouldn't go backwards either.
-	 */
-	if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
-		MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
-		MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+		if (TransactionIdIsValid(gtt_relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, gtt_relfrozenxid))
+			FreezeXid = gtt_relfrozenxid;
+
+		if (MultiXactIdIsValid(gtt_relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, gtt_relminmxid))
+			MultiXactCutoff = gtt_relminmxid;
+	}
+	else
+	{
+		/*
+		 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
+		 * backwards, so take the max.
+		 */
+		if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
+			FreezeXid = OldHeap->rd_rel->relfrozenxid;
+
+		/*
+		 * MultiXactCutoff, similarly, shouldn't go backwards either.
+		 */
+		if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
+			MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+	}
 
 	/*
 	 * Decide whether to use an indexscan or seqscan-and-optional-sort to scan
@@ -917,6 +955,12 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
 	table_close(OldHeap, NoLock);
 	table_close(NewHeap, NoLock);
 
+	if (is_gtt)
+	{
+		CommandCounterIncrement();
+		return;
+	}
+
 	/* Update pg_class to reflect the correct values of pages and tuples. */
 	relRelation = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1352,10 +1396,21 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 	 * Swap the contents of the heap relations (including any toast tables).
 	 * Also set old heap's relfrozenxid to frozenXid.
 	 */
-	swap_relation_files(OIDOldHeap, OIDNewHeap,
+	if (newrelpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		Assert(!is_system_catalog);
+		gtt_swap_relation_files(OIDOldHeap, OIDNewHeap,
+								(OIDOldHeap == RelationRelationId),
+								swap_toast_by_content, is_internal,
+								frozenXid, cutoffMulti, mapped_tables);
+	}
+	else
+	{
+		swap_relation_files(OIDOldHeap, OIDNewHeap,
 						(OIDOldHeap == RelationRelationId),
 						swap_toast_by_content, is_internal,
 						frozenXid, cutoffMulti, mapped_tables);
+	}
 
 	/*
 	 * If it's a system catalog, queue a sinval message to flush all catcaches
@@ -1563,3 +1618,141 @@ get_tables_to_cluster(MemoryContext cluster_context)
 
 	return rvs;
 }
+
+static void
+gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables)
+{
+	Relation	relRelation;
+	Oid			relfilenode1,
+				relfilenode2;
+	Relation	rel1;
+	Relation	rel2;
+
+	relRelation = table_open(RelationRelationId, RowExclusiveLock);
+
+	rel1 = relation_open(r1, AccessExclusiveLock);
+	rel2 = relation_open(r2, AccessExclusiveLock);
+
+	relfilenode1 = gtt_fetch_current_relfilenode(r1);
+	relfilenode2 = gtt_fetch_current_relfilenode(r2);
+
+	Assert(OidIsValid(relfilenode1) && OidIsValid(relfilenode2));
+	gtt_switch_rel_relfilenode(r1, relfilenode1, r2, relfilenode2, true);
+
+	CacheInvalidateRelcache(rel1);
+	CacheInvalidateRelcache(rel2);
+
+	InvokeObjectPostAlterHookArg(RelationRelationId, r1, 0,
+								 InvalidOid, is_internal);
+	InvokeObjectPostAlterHookArg(RelationRelationId, r2, 0,
+								 InvalidOid, true);
+
+	if (rel1->rd_rel->reltoastrelid || rel2->rd_rel->reltoastrelid)
+	{
+		if (swap_toast_by_content)
+		{
+			if (rel1->rd_rel->reltoastrelid && rel2->rd_rel->reltoastrelid)
+			{
+				gtt_swap_relation_files(rel1->rd_rel->reltoastrelid,
+									rel2->rd_rel->reltoastrelid,
+									target_is_pg_class,
+									swap_toast_by_content,
+									is_internal,
+									frozenXid,
+									cutoffMulti,
+									mapped_tables);
+			}
+			else
+				elog(ERROR, "cannot swap toast files by content when there's only one");
+		}
+		else
+		{
+			ObjectAddress baseobject,
+						toastobject;
+			long		count;
+
+			if (IsSystemRelation(rel1))
+				elog(ERROR, "cannot swap toast files by links for system catalogs");
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel1->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel2->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+
+			/* Register new dependencies */
+			baseobject.classId = RelationRelationId;
+			baseobject.objectSubId = 0;
+			toastobject.classId = RelationRelationId;
+			toastobject.objectSubId = 0;
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r1;
+				toastobject.objectId = rel1->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r2;
+				toastobject.objectId = rel2->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+		}
+	}
+
+	if (swap_toast_by_content &&
+		rel1->rd_rel->relkind == RELKIND_TOASTVALUE &&
+		rel2->rd_rel->relkind == RELKIND_TOASTVALUE)
+	{
+		Oid			toastIndex1,
+					toastIndex2;
+
+		/* Get valid index for each relation */
+		toastIndex1 = toast_get_valid_index(r1,
+											AccessExclusiveLock);
+		toastIndex2 = toast_get_valid_index(r2,
+											AccessExclusiveLock);
+
+		gtt_swap_relation_files(toastIndex1,
+							toastIndex2,
+							target_is_pg_class,
+							swap_toast_by_content,
+							is_internal,
+							InvalidTransactionId,
+							InvalidMultiXactId,
+							mapped_tables);
+	}
+
+	relation_close(rel1, NoLock);
+	relation_close(rel2, NoLock);
+
+	table_close(relRelation, RowExclusiveLock);
+
+	RelationCloseSmgrByOid(r1);
+	RelationCloseSmgrByOid(r2);
+
+	CommandCounterIncrement();
+}
+
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index db7d24a..daf9d1c 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -27,6 +27,7 @@
 #include "catalog/dependency.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -1113,7 +1114,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, stmt->filename, stmt->is_program,
@@ -2838,6 +2839,8 @@ CopyFrom(CopyState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	estate->es_result_relations = resultRelInfo;
 	estate->es_num_result_relations = 1;
 	estate->es_result_relation_info = resultRelInfo;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index f1b5f87..c6cf554 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -534,6 +534,7 @@ DefineIndex(Oid relationId,
 	Snapshot	snapshot;
 	int			save_nestlevel = -1;
 	int			i;
+	char		rel_persistence;
 
 	/*
 	 * Some callers need us to run with an empty default_tablespace; this is a
@@ -555,7 +556,9 @@ DefineIndex(Oid relationId,
 	 * is more efficient.  Do this before any use of the concurrent option is
 	 * done.
 	 */
-	if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+	rel_persistence = get_rel_persistence(relationId);
+	if (stmt->concurrent &&
+		!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		concurrent = true;
 	else
 		concurrent = false;
@@ -2469,7 +2472,8 @@ ReindexIndex(RangeVar *indexRelation, int options, bool isTopLevel)
 	if (relkind == RELKIND_PARTITIONED_INDEX)
 		ReindexPartitions(indOid, options, isTopLevel);
 	else if ((options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 persistence != RELPERSISTENCE_TEMP)
+			 persistence != RELPERSISTENCE_TEMP &&
+			 persistence != RELPERSISTENCE_GLOBAL_TEMP)
 		ReindexRelationConcurrently(indOid, options);
 	else
 		reindex_index(indOid, false, persistence,
@@ -2556,6 +2560,7 @@ ReindexTable(RangeVar *relation, int options, bool isTopLevel)
 {
 	Oid			heapOid;
 	bool		result;
+	char		rel_persistence;
 
 	/*
 	 * The lock level used here should match reindex_relation().
@@ -2571,10 +2576,12 @@ ReindexTable(RangeVar *relation, int options, bool isTopLevel)
 									   0,
 									   RangeVarCallbackOwnsTable, NULL);
 
+	rel_persistence = get_rel_persistence(heapOid);
 	if (get_rel_relkind(heapOid) == RELKIND_PARTITIONED_TABLE)
 		ReindexPartitions(heapOid, options, isTopLevel);
 	else if ((options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+			 rel_persistence != RELPERSISTENCE_TEMP &&
+			 rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		result = ReindexRelationConcurrently(heapOid, options);
 
@@ -2933,7 +2940,8 @@ ReindexMultipleInternal(List *relids, int options)
 			   relkind != RELKIND_PARTITIONED_TABLE);
 
 		if ((options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			relpersistence != RELPERSISTENCE_TEMP)
+			relpersistence != RELPERSISTENCE_TEMP &&
+			relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
 		{
 			(void) ReindexRelationConcurrently(relid,
 											   options |
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index d8cafc4..d7a7604 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -56,7 +56,9 @@ LockTableCommand(LockStmt *lockstmt)
 										  RangeVarCallbackForLockTable,
 										  (void *) &lockstmt->mode);
 
-		if (get_rel_relkind(reloid) == RELKIND_VIEW)
+		if (get_rel_persistence(reloid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+		else if (get_rel_relkind(reloid) == RELKIND_VIEW)
 			LockViewRecurse(reloid, lockstmt->mode, lockstmt->nowait, NIL);
 		else if (recurse)
 			LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait);
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73b..234ffa6 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -94,7 +96,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -222,7 +225,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* now initialize the sequence's data */
 	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -327,7 +330,7 @@ ResetSequence(Oid seq_relid)
 	/*
 	 * Insert the modified tuple into the new storage file.
 	 */
-	fill_seq_with_data(seq_rel, tuple);
+	fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -340,17 +343,21 @@ ResetSequence(Oid seq_relid)
  * Initialize a sequence's relation with the specified tuple as content
  */
 static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
 {
-	Buffer		buf;
 	Page		page;
 	sequence_magic *sm;
 	OffsetNumber offnum;
+	bool lockBuffer = false;
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ReadBuffer(rel, P_NEW);
-	Assert(BufferGetBlockNumber(buf) == 0);
+	if (buf == InvalidBuffer)
+	{
+		buf = ReadBuffer(rel, P_NEW);
+		Assert(BufferGetBlockNumber(buf) == 0);
+		lockBuffer = true;
+	}
 
 	page = BufferGetPage(buf);
 
@@ -360,7 +367,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	/* Now insert sequence tuple */
 
-	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+	if (lockBuffer)
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +418,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
 
 	END_CRIT_SECTION();
 
-	UnlockReleaseBuffer(buf);
+	if (lockBuffer)
+		UnlockReleaseBuffer(buf);
 }
 
 /*
@@ -451,6 +460,15 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 
 	init_sequence(relid, &elm, &seqrel);
 
+	if (RELATION_IS_GLOBAL_TEMP(seqrel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(seqrel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary sequence %s when other backend attached it.",
+						RelationGetRelationName(seqrel))));
+	}
+
 	rel = table_open(SequenceRelationId, RowExclusiveLock);
 	seqtuple = SearchSysCacheCopy1(SEQRELID,
 								   ObjectIdGetDatum(relid));
@@ -502,7 +520,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * Insert the modified tuple into the new storage file.
 		 */
-		fill_seq_with_data(seqrel, newdatatuple);
+		fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
 	}
 
 	/* process OWNED BY if given */
@@ -611,7 +629,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +954,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1154,6 +1172,13 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1954,3 +1979,46 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+void
+gtt_init_seq(Relation rel)
+{
+	/* Initialize sequence for global temporary tables */
+	Datum		value[SEQ_COL_LASTCOL] = {0};
+	bool		null[SEQ_COL_LASTCOL] = {false};
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple, InvalidBuffer);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 3e57c7f..6527470 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -558,6 +559,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 								  Relation partitionTbl);
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static OnCommitAction gtt_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -603,6 +605,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -613,8 +616,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")));
@@ -644,7 +649,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	/* global temp table same as local temp table */
+	if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+		 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -745,6 +752,55 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* global temp table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* check parent table*/
+		if (inheritOids)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			table_close(relation, NoLock);
+		}
+
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temp table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1332,6 +1388,7 @@ RemoveRelations(DropStmt *drop)
 		Oid			relOid;
 		ObjectAddress obj;
 		struct DropRelationCallbackState state;
+		char		rel_persistence;
 
 		/*
 		 * These next few steps are a great deal like relation_openrv, but we
@@ -1365,8 +1422,9 @@ RemoveRelations(DropStmt *drop)
 		 * Decide if concurrent mode needs to be used here or not.  The
 		 * relation persistence cannot be known without its OID.
 		 */
+		rel_persistence = get_rel_persistence(relOid);
 		if (drop->concurrent &&
-			get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+			!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP))
 		{
 			Assert(list_length(drop->objects) == 1 &&
 				   drop->removeType == OBJECT_INDEX);
@@ -1572,7 +1630,12 @@ ExecuteTruncate(TruncateStmt *stmt)
 		Relation	rel;
 		bool		recurse = rv->inh;
 		Oid			myrelid;
-		LOCKMODE	lockmode = AccessExclusiveLock;
+		LOCKMODE	lockmode;
+
+		if (rv->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+		else
+			lockmode = AccessExclusiveLock;
 
 		myrelid = RangeVarGetRelidExtended(rv, lockmode,
 										   0, RangeVarCallbackForTruncate,
@@ -1832,6 +1895,10 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
 		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 			continue;
 
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			continue;
+
 		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
@@ -3677,6 +3744,16 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -4982,6 +5059,38 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot rewrite temporary tables of other sessions")));
 
+			if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+			{
+				if (tab->chgPersistence)
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot change global temporary table persistence setting")));
+
+				/* gtt may not attached, create it */
+				if(!gtt_storage_attached(tab->relid))
+				{
+					ResultRelInfo *resultRelInfo;
+					MemoryContext oldcontext;
+					MemoryContext ctx_alter_gtt;
+
+					ctx_alter_gtt = AllocSetContextCreate(CurrentMemoryContext,
+											"gtt alter table", ALLOCSET_DEFAULT_SIZES);
+
+					oldcontext = MemoryContextSwitchTo(ctx_alter_gtt);
+					resultRelInfo = makeNode(ResultRelInfo);
+					InitResultRelInfo(resultRelInfo, OldHeap,
+									1, NULL, 0);
+					if (resultRelInfo->ri_RelationDesc->rd_rel->relhasindex &&
+						resultRelInfo->ri_IndexRelationDescs == NULL)
+						ExecOpenIndices(resultRelInfo, false);
+
+					init_gtt_storage(CMD_UTILITY, resultRelInfo);
+					ExecCloseIndices(resultRelInfo);
+					MemoryContextSwitchTo(oldcontext);
+					MemoryContextDelete(ctx_alter_gtt);
+				}
+			}
+
 			/*
 			 * Select destination tablespace (same as original unless user
 			 * requested a change)
@@ -8399,6 +8508,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -12899,6 +13014,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -13101,6 +13219,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temp table");
+
 	/*
 	 * No work if no change in tablespace.
 	 */
@@ -13475,7 +13596,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,
@@ -14882,7 +15003,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",
@@ -17540,3 +17663,36 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
 		table_close(rel, NoLock);
 	}
 }
+
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			bool	res = false;
+			char	*sval = defGetString(def);
+
+			if (!parse_bool(sval, &res))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
+
+			if (res)
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index ddeec87..815b6c3 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"
@@ -1255,6 +1256,17 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = false;
+
+	 /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		is_gtt = true;
+		up_gtt_relstats(relation,
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1268,17 +1280,26 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relpages = (int32) num_pages;
+	else if (pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (is_gtt)
+		relation->rd_rel->reltuples = (float4) num_tuples;
+	else if (pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (is_gtt)
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	else if (pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1323,7 +1344,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1334,7 +1356,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1441,6 +1464,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
@@ -1498,6 +1525,42 @@ 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	safe_age;
+		TransactionId	oldest_gtt_frozenxid =
+			list_all_session_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid))
+		{
+			safe_age = oldest_gtt_frozenxid + vacuum_gtt_defer_check_age;
+			if (safe_age < FirstNormalTransactionId)
+				safe_age += FirstNormalTransactionId;
+
+			/*
+			 * We tolerate that the minimum age of gtt is less than
+			 * the minimum age of conventional tables, otherwise it will
+			 * throw warning message.
+			 */
+			if (TransactionIdIsNormal(safe_age) &&
+				TransactionIdPrecedes(safe_age, newFrozenXid))
+			{
+				ereport(WARNING,
+					(errmsg("global temp table oldest relfrozenxid %u is the oldest in the entire db",
+							oldest_gtt_frozenxid),
+					 errdetail("The oldest relfrozenxid in pg_class is %u", newFrozenXid),
+					 errhint("If they differ greatly, please consider cleaning up the data in global temp table.")));
+			}
+
+			if (TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+				newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1832,6 +1895,15 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6e65103..04706ee 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -530,6 +530,11 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 4fdffad..0aa7559 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index bd2ea25..14bee8f 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
@@ -602,6 +603,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	init_gtt_storage(mtstate->operation, leaf_part_rri);
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 20a4c47..e9f6dcd 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,6 +42,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -2379,6 +2380,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			ExecOpenIndices(resultRelInfo,
 							node->onConflictAction != ONCONFLICT_NONE);
 
+		init_gtt_storage(operation, resultRelInfo);
+
 		/*
 		 * If this is an UPDATE and a BEFORE UPDATE trigger is present, the
 		 * trigger itself might modify the partition-key values. So arrange
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index b399592..fb28fc0 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 139c5e3..2ce11e9 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6412,7 +6412,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 f9d0d67..4f8c019 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -28,6 +28,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temp table */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index c159fb2..7dc0be8 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2594,6 +2594,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 c5154b8..a47f32c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3315,17 +3315,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; }
@@ -11411,19 +11405,13 @@ OptTempTableName:
 				}
 			| GLOBAL TEMPORARY opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED opt_table qualified_name
 				{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b875a50..123945b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index ec94437..c82d853 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -438,6 +438,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
@@ -3195,6 +3198,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		cxt.isforeign = false;
 	}
 	cxt.relation = stmt->relation;
+	cxt.relation->relpersistence = RelationGetRelPersistence(rel);
 	cxt.rel = rel;
 	cxt.inhRelations = NIL;
 	cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 1b8cd7b..5bb3d7e 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2092,6 +2092,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);
@@ -2158,7 +2163,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 a2a963b..0c4228a 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -54,6 +55,7 @@
 #include "utils/rel.h"
 #include "utils/resowner_private.h"
 #include "utils/timestamp.h"
+#include "utils/guc.h"
 
 
 /* Note: these two macros only work on shared buffers, not local ones! */
@@ -2854,6 +2856,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 96c2aaa..73d099d 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -22,6 +22,7 @@
 #include "access/subtrans.h"
 #include "access/syncscan.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -149,6 +150,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, active_gtt_shared_hash_size());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -221,6 +223,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* 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 1c0cd6b..a56c13f 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -65,6 +65,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -4972,3 +4973,76 @@ 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];
+
+		if (proc->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/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 2fa90cc..22f0f81 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -177,7 +177,9 @@ static const char *const BuiltinTrancheNames[] = {
 	/* LWTRANCHE_PARALLEL_APPEND: */
 	"ParallelAppend",
 	/* LWTRANCHE_PER_XACT_PREDICATE_LIST: */
-	"PerXactPredicateList"
+	"PerXactPredicateList",
+	/* LWTRANCHE_GTT_CTL */
+	"GlobalTempTableControl"
 };
 
 StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 88566bd..7de757e 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -391,6 +391,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->vacuumFlags = 0;
@@ -572,6 +573,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;
 	MyProc->delayChkpt = false;
 	MyProc->vacuumFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 7def739..4266cd4 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -982,6 +982,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 00c7afc..cb28c7a 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -108,6 +108,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -4887,12 +4888,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))
 							{
@@ -5017,15 +5031,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))
 		{
@@ -6448,6 +6474,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;
@@ -6465,6 +6492,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,
@@ -6476,6 +6510,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;
@@ -6491,6 +6527,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,
@@ -7409,6 +7452,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))
@@ -7421,6 +7466,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 =
@@ -7433,6 +7486,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.
@@ -7452,6 +7507,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 f3bf413..1aabd25 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -2988,6 +2989,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 9061af8..405a6d8 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -65,6 +65,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1134,6 +1135,24 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1188,6 +1207,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			gtt_fix_index_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -1312,7 +1332,17 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+			if (newrelnode != InvalidOid &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2254,6 +2284,8 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		gtt_fix_index_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3480,6 +3512,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3587,28 +3623,34 @@ void
 RelationSetNewRelfilenode(Relation relation, char persistence)
 {
 	Oid			newrelfilenode;
-	Relation	pg_class;
-	HeapTuple	tuple;
+	Relation	pg_class = NULL;
+	HeapTuple	tuple = NULL;
 	Form_pg_class classform;
 	MultiXactId minmulti = InvalidMultiXactId;
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
+	bool		modify_pg_class = !RELATION_IS_GLOBAL_TEMP(relation);
 
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
 
-	/*
-	 * Get a writable copy of the pg_class tuple for the given relation.
-	 */
-	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+	if (modify_pg_class)
+	{
+		/*
+		 * Get a writable copy of the pg_class tuple for the given relation.
+		 */
+		pg_class = table_open(RelationRelationId, RowExclusiveLock);
 
-	tuple = SearchSysCacheCopy1(RELOID,
-								ObjectIdGetDatum(RelationGetRelid(relation)));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "could not find tuple for relation %u",
-			 RelationGetRelid(relation));
-	classform = (Form_pg_class) GETSTRUCT(tuple);
+		tuple = SearchSysCacheCopy1(RELOID,
+									ObjectIdGetDatum(RelationGetRelid(relation)));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u",
+				 RelationGetRelid(relation));
+		classform = (Form_pg_class) GETSTRUCT(tuple);
+	}
+	else
+		memset(&classform, 0, sizeof(classform));
 
 	/*
 	 * Schedule unlinking of the old storage at transaction commit.
@@ -3634,7 +3676,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 				/* handle these directly, at least for now */
 				SMgrRelation srel;
 
-				srel = RelationCreateStorage(newrnode, persistence);
+				srel = RelationCreateStorage(newrnode, persistence, relation);
 				smgrclose(srel);
 			}
 			break;
@@ -3654,6 +3696,15 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 			break;
 	}
 
+	if (!modify_pg_class)
+	{
+		Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+		Assert(RELATION_IS_GLOBAL_TEMP(relation));
+		Assert(!RelationIsMapped(relation));
+		relation->rd_node.relNode = relnode;
+		CacheInvalidateRelcache(relation);
+	}
 	/*
 	 * If we're dealing with a mapped index, pg_class.relfilenode doesn't
 	 * change; instead we have to send the update to the relation mapper.
@@ -3663,7 +3714,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	 * possibly-inaccurate values of relpages etc, but those will be fixed up
 	 * later.
 	 */
-	if (RelationIsMapped(relation))
+	else if (RelationIsMapped(relation))
 	{
 		/* This case is only supported for indexes */
 		Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3709,9 +3760,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 		CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 	}
 
-	heap_freetuple(tuple);
+	if (modify_pg_class)
+	{
+		heap_freetuple(tuple);
 
-	table_close(pg_class, RowExclusiveLock);
+		table_close(pg_class, RowExclusiveLock);
+	}
 
 	/*
 	 * Make the pg_class row change or relation map change visible.  This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 73518d9..246a1ee 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -40,6 +40,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "commands/prepare.h"
 #include "commands/trigger.h"
@@ -145,6 +146,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global 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,
@@ -2046,6 +2059,15 @@ static struct config_bool ConfigureNamesBool[] =
 static struct config_int ConfigureNamesInt[] =
 {
 	{
+		{"max_active_global_temporary_table", PGC_POSTMASTER, UNGROUPED,
+			gettext_noop("max active global temporary table."),
+			NULL
+		},
+		&max_active_gtt,
+		DEFAULT_NUM_ACTIVE_GTT, MIN_NUM_ACTIVE_GTT, MAX_NUM_ACTIVE_GTT,
+		NULL, NULL, NULL
+	},
+	{
 		{"archive_timeout", PGC_SIGHUP, WAL_ARCHIVING,
 			gettext_noop("Forces a switch to the next WAL file if a "
 						 "new file has not been started within N seconds."),
@@ -2564,6 +2586,16 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"vacuum_gtt_defer_check_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("The defer check age of GTT, used to check expired data after vacuum."),
+			NULL
+		},
+		&vacuum_gtt_defer_check_age,
+		10000, 0, 1000000,
+		NULL, NULL, NULL
+	},
+
 	/*
 	 * See also CheckRequiredParameterValues() if this parameter changes
 	 */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 784bcea..df462e2 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2419,6 +2419,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
 		dopt->no_unlogged_table_data)
 		return;
 
+	/* Don't dump data in global temp table/sequence */
+	if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		return;
+
 	/* Check that the data is not explicitly excluded */
 	if (simple_oid_list_member(&tabledata_exclude_oids,
 							   tbinfo->dobj.catId.oid))
@@ -15596,6 +15600,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
 		char	   *foreign = "";
+		char	   *table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15649,9 +15654,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);
 
@@ -16045,7 +16056,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
-			 tbinfo->relkind == RELKIND_MATVIEW))
+			 tbinfo->relkind == RELKIND_MATVIEW) &&
+			 tbinfo->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
 		{
 			appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
 			appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
@@ -16950,6 +16962,7 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	PQExpBuffer query = createPQExpBuffer();
 	PQExpBuffer delqry = createPQExpBuffer();
 	char	   *qseqname;
+	bool	   global_temp_seq = false;
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
@@ -16959,9 +16972,11 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
-						  "FROM pg_catalog.pg_sequence "
-						  "WHERE seqrelid = '%u'::oid",
+						  "seqcache, seqcycle, c.relpersistence "
+						  "FROM pg_catalog.pg_sequence s, "
+						  "pg_catalog.pg_class c "
+						  "WHERE seqrelid = '%u'::oid "
+						  "and s.seqrelid = c.oid",
 						  tbinfo->dobj.catId.oid);
 	}
 	else if (fout->remoteVersion >= 80400)
@@ -17006,6 +17021,9 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
 
+	if (fout->remoteVersion >= 130000)
+		global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
 	if (strcmp(seqtype, "smallint") == 0)
@@ -17083,9 +17101,13 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	}
 	else
 	{
-		appendPQExpBuffer(query,
-						  "CREATE SEQUENCE %s\n",
-						  fmtQualifiedDumpable(tbinfo));
+		appendPQExpBuffer(query, "CREATE ");
+
+		if (global_temp_seq)
+			appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+		appendPQExpBuffer(query, "SEQUENCE %s\n",
+								  fmtQualifiedDumpable(tbinfo));
 
 		if (strcmp(seqtype, "bigint") != 0)
 			appendPQExpBuffer(query, "    AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 00aef85..9ad71c8 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -84,7 +84,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -157,7 +157,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 7e524ea..371168f 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -441,22 +443,46 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 	 * output, so we have to copy that system table.  It's easiest to do that
 	 * by treating it as a user table.
 	 */
-	snprintf(query + strlen(query), sizeof(query) - strlen(query),
-			 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
-			 "  SELECT c.oid, 0::oid, 0::oid "
-			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
-			 "         ON c.relnamespace = n.oid "
-			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
-	/* exclude possible orphaned temp tables */
-			 "    ((n.nspname !~ '^pg_temp_' AND "
-			 "      n.nspname !~ '^pg_toast_temp_' AND "
-			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
-			 "                        'binary_upgrade', 'pg_toast') AND "
-			 "      c.oid >= %u::pg_catalog.oid) OR "
-			 "     (n.nspname = 'pg_catalog' AND "
-			 "      relname IN ('pg_largeobject') ))), ",
-			 FirstNormalObjectId);
+	if (skip_gtt)
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude global temp tables */
+				 "    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
+	else
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
 
 	/*
 	 * Add a CTE that collects OIDs of toast tables belonging to the tables
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 70194eb..8dd9e66 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -411,7 +411,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -642,7 +642,9 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -653,7 +655,9 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 8b90cef..d155205 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -389,7 +389,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 0861d74..7bc9e17 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3738,7 +3738,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index f41785f..ed841dd 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1029,6 +1029,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2406,6 +2408,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2614,6 +2619,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index d31141c..44f767a 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -59,7 +59,8 @@ extern Relation heap_create(const char *relname,
 							bool mapped_relation,
 							bool allow_system_table_mods,
 							TransactionId *relfrozenxid,
-							MultiXactId *relminmxid);
+							MultiXactId *relminmxid,
+							bool skip_create_storage);
 
 extern Oid	heap_create_with_catalog(const char *relname,
 									 Oid relnamespace,
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 679eec3..5c57eac 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -168,6 +168,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 687509b..bc7e35e 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5569,6 +5569,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4388',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4389',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4390',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4391',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 30c38e0..7ff2408 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,7 +22,7 @@
 /* GUC variables */
 extern int	wal_skip_threshold;
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationPreTruncate(Relation rel);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..902375d
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern int		vacuum_gtt_defer_check_age;
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Relation relation,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void gtt_force_enable_index(Relation index);
+extern void gtt_fix_index_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+extern void gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index e2638ab..89a5ce4 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 51b8f99..ef86219 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -399,6 +399,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index af9b417..6e16f99 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -218,6 +218,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_SHARED_TIDBITMAP,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_PER_XACT_PREDICATE_LIST,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 9c9a50a..70d4c7d 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -148,6 +148,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 ea8a876..bd732a3 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -92,4 +92,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 2819282..363fc33 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -282,6 +282,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 0b5957b..0720a4c 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	struct SMgrRelationData *rd_smgr;	/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -305,6 +305,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -570,11 +571,13 @@ typedef struct ViewOptions
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -582,6 +585,7 @@ typedef struct ViewOptions
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -594,6 +598,14 @@ typedef struct ViewOptions
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is either local temp relation of this session
+ * 		or global temp relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -637,6 +649,17 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..ca2d135
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,14 @@
+reset search_path;
+select pg_sleep(5);
+ pg_sleep 
+----------
+ 
+(1 row)
+
+drop schema gtt cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
+drop cascades to table gtt.gtt_with_seq
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..d6a675a
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,383 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+ n 
+---
+ 9
+(1 row)
+
+commit;
+-- 0 row
+select * from gtt6;
+ n 
+---
+(0 rows)
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temp table
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  could not create global temporary table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR:  insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count 
+-------
+     1
+(1 row)
+
+select count(*) from orders;
+ count 
+-------
+     0
+(1 row)
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  2 |  3
+(2 rows)
+
+commit;
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+select * from gtt_s_2 order by c1;
+ c1 | c2 
+----+----
+  1 |  2
+  2 |  4
+(2 rows)
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 1)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 300)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 33 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test_new
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.gtt6
+drop cascades to table gtt_function.foo
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to sequence gtt_function.seq_1
+drop cascades to table gtt_function.gtt_s_1
+drop cascades to table gtt_function.gtt_s_2
+drop cascades to table gtt_function.gt1
+drop cascades to table gtt_function.gtt_test1
+drop cascades to table gtt_function.gtt_test2
+drop cascades to table gtt_function.gtt_test3
+drop cascades to table gtt_function.gtt_test4
+drop cascades to table gtt_function.gtt_test5
+drop cascades to table gtt_function.gtt_test6
+drop cascades to table gtt_function.gtt_test7
+drop cascades to table gtt_function.gtt_test8
+drop cascades to table gtt_function.gtt_test9
+drop cascades to table gtt_function.gtt_test10
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..0646aae
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,90 @@
+set search_path=gtt,sys;
+select nextval('gtt_with_seq_c2_seq');
+ nextval 
+---------
+       1
+(1 row)
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..2f084be
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,343 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+rollback;
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+commit;
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          114688 |                 614400
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |            49152 |         49152 |                  49152
+(2 rows)
+
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2000
+(1 row)
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+ c1 | c2 
+----+----
+  1 |  1
+(1 row)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..8c0c376
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,10 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..4420fdb
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,80 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+ gtt_stats  | gtt_pkey  |        1 |         0 |             0
+(2 rows)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+reindex table gtt;
+reindex index gtt_pkey;
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+ gtt_stats  | gtt_pkey  |       30 |     10000 |             0
+(2 rows)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2a18dc4..6ab171c 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1359,6 +1359,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 026ea88..592ba9c 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..2c8e586
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,8 @@
+
+
+reset search_path;
+
+select pg_sleep(5);
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..81f0bfc
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,253 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+commit;
+-- 0 row
+select * from gtt6;
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+commit;
+select * from gtt_s_1 order by c1;
+select * from gtt_s_2 order by c1;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d05745e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,44 @@
+
+
+set search_path=gtt,sys;
+
+select nextval('gtt_with_seq_c2_seq');
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..39cca5e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,154 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+rollback;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+commit;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+select count(*) from gtt_t_kenyon;
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..dbe84d1
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,19 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..d61b0ff
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,46 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+reindex table gtt;
+
+reindex index gtt_pkey;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#291Pavel Stehule
pavel.stehule@gmail.com
In reply to: 曾文旌 (#290)
Re: [Proposal] Global temporary tables

Hi

pá 11. 9. 2020 v 17:00 odesílatel 曾文旌 <wenjing.zwj@alibaba-inc.com> napsal:

I have written the README for the GTT, which contains the GTT requirements
and design.
I found that compared to my first email a year ago, many GTT Limitations
are now gone.
Now, I'm adding comments to some of the necessary functions.

There are problems with patching. Please, can you rebase your patch?

Regards

Pavel

Show quoted text

Wenjing

2020年7月31日 上午4:57,Robert Haas <robertmhaas@gmail.com> 写道:

On Thu, Jul 30, 2020 at 8:09 AM wenjing zeng <wjzeng2012@gmail.com>

wrote:

Please continue to review the code.

This patch is pretty light on comments. Many of the new functions have
no header comments, for example. There are comments here and there in
the body of the new functions that are added, and in places where
existing code is changed there are comments here and there, but
overall it's not a whole lot. There's no documentation and no README,
either. Since this adds a new feature and a bunch of new SQL-callable
functions that interact with that feature, the feature itself should
be documented, along with its limitations and the new SQL-callable
functions that interact with it. I think there should be either a
lengthy comment in some suitable file, or maybe various comments in
various files, or else a README file, that clearly sets out the major
design principles behind the patch, and explaining also what that
means in terms of features and limitations. Without that, it's really
hard for anyone to jump into reviewing this code, and it will be hard
for people who have to maintain it in the future to understand it,
either. Or for users, for that matter.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#292曾文旌
wenjing.zwj@alibaba-inc.com
In reply to: Pavel Stehule (#291)
2 attachment(s)
Re: [Proposal] Global temporary tables

2020年11月21日 02:28,Pavel Stehule <pavel.stehule@gmail.com> 写道:

Hi

pá 11. 9. 2020 v 17:00 odesílatel 曾文旌 <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:
I have written the README for the GTT, which contains the GTT requirements and design.
I found that compared to my first email a year ago, many GTT Limitations are now gone.
Now, I'm adding comments to some of the necessary functions.

There are problems with patching. Please, can you rebase your patch?

Sure.
I'm still working on sort code and comments.
If you have any suggestions, please let me know.

Wenjing

Show quoted text

Regards

Pavel

Wenjing

2020年7月31日 上午4:57,Robert Haas <robertmhaas@gmail.com <mailto:robertmhaas@gmail.com>> 写道:

On Thu, Jul 30, 2020 at 8:09 AM wenjing zeng <wjzeng2012@gmail.com <mailto:wjzeng2012@gmail.com>> wrote:

Please continue to review the code.

This patch is pretty light on comments. Many of the new functions have
no header comments, for example. There are comments here and there in
the body of the new functions that are added, and in places where
existing code is changed there are comments here and there, but
overall it's not a whole lot. There's no documentation and no README,
either. Since this adds a new feature and a bunch of new SQL-callable
functions that interact with that feature, the feature itself should
be documented, along with its limitations and the new SQL-callable
functions that interact with it. I think there should be either a
lengthy comment in some suitable file, or maybe various comments in
various files, or else a README file, that clearly sets out the major
design principles behind the patch, and explaining also what that
means in terms of features and limitations. Without that, it's really
hard for anyone to jump into reviewing this code, and it will be hard
for people who have to maintain it in the future to understand it,
either. Or for users, for that matter.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;
The Enterprise PostgreSQL Company

Attachments:

global_temporary_table_v37-pgsql.patchapplication/octet-stream; name=global_temporary_table_v37-pgsql.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 8ccc228..ef61a13 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -168,6 +168,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * For global temp table only
+	 * use ShareUpdateExclusiveLock for ensure safety
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temp table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},	
 	/* list terminator */
 	{{NULL}}
 };
@@ -1817,6 +1830,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1962,12 +1977,16 @@ bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
 	/*
-	 * There are no options for partitioned tables yet, but this is able to do
-	 * some validation.
+	 * Add option for global temp partitioned tables.
 	 */
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index 615b5ad..7736799 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1026,7 +1026,7 @@ gistproperty(Oid index_oid, int attno,
 XLogRecPtr
 gistGetFakeLSN(Relation rel)
 {
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	if (RELATION_IS_TEMP(rel))
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 7c9ccf4..f4561bc 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -151,7 +151,7 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	if (!RELATION_IS_TEMP(index))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index dcaea71..536aab6 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -589,7 +589,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -642,7 +642,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 25f2d5d..e49b67b 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -62,6 +62,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -445,9 +446,13 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
 	Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
-	/* not every AM requires these to be valid, but heap does */
-	Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
-	Assert(MultiXactIdIsValid(onerel->rd_rel->relminmxid));
+	/*
+	 * not every AM requires these to be valid, but regular heap does.
+	 * Transaction information for the global temp table is stored in
+	 * local hash, not catalog.
+	 */
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ MultiXactIdIsValid(onerel->rd_rel->relminmxid));
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 848123d..2a29e93 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -609,6 +610,14 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		/*
+		 * If a global temp table storage file is not initialized in the
+		 * current session, its index does not have root page. just returns 0.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 13f1d8c..d32aabe 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6877,6 +6877,15 @@ StartupXLOG(void)
 		int			rmid;
 
 		/*
+		 * When pg backend processes crashes, those storage files that belong
+		 * to global temporary tables will not be cleaned up.
+		 * They will remain in the data directory. These data files no longer
+		 * belong to any session and need to be cleaned up during the crash recovery.
+		 */
+		if (max_active_gtt > 0)
+			RemovePgTempFiles();
+
+		/*
 		 * Update pg_control to show that we are recovering and to show the
 		 * selected checkpoint as the place we are starting from. We also mark
 		 * pg_control with any minimum recovery stop point obtained from a
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 6bb0c6e..9ad28c4 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -212,7 +212,8 @@ Boot_CreateStmt:
 												   mapped_relation,
 												   true,
 												   &relfrozenxid,
-												   &relminmxid);
+												   &relminmxid,
+												   false);
 						elog(DEBUG4, "bootstrap relation created");
 					}
 					else
diff --git a/src/backend/catalog/GTT_README b/src/backend/catalog/GTT_README
new file mode 100644
index 0000000..864cdae
--- /dev/null
+++ b/src/backend/catalog/GTT_README
@@ -0,0 +1,170 @@
+Global temporary table(GTT)
+==============
+
+Feature description
+--------------------------------
+
+Previously, temporary tables are defined once and automatically
+created (starting with empty contents) in every session before using them.
+
+The temporary table implementation in PostgreSQL, known as Local temp tables(LTT),
+did not fully comply with the SQL standard. This version added the support of
+Global temporary Table .
+
+The metadata of Global temporary table is persistent and shared among sessions.
+The data stored in the Global temporary table is independent of sessions. This
+means, when a session creates a global temporary table and writes some data.
+Other sessions cannot see those data, but they have an empty Global temporary
+table with same schema.
+
+Like local temporary table, global temporary table supports ON COMMIT PRESERVE ROWS
+or ON COMMIT DELETE ROWS clause, so that data in the temporary table can be
+cleaned up or reserved automatically when a session exits or a transaction COMMITs.
+
+Unlike Local Temporary Table, Global temporary table does not support
+ON COMMIT DROP clauses.
+
+In following paragraphs, we use GTT for global temporary table and LTT for
+local temporary table.
+
+Main design ideas
+-----------------------------------------
+
+STORAGE & BUFFER
+
+In general, GTT and LTT use the same storage and buffer design and
+implementation. The storage files for both types of temporary tables are named
+as t_backendid_relfilenode, and the local buffer is used to cache the data.
+
+The schema of GTTs is shared among sessions while their data are not. We build
+a new mechanisms to manage those non-shared data and their statistics.
+Here is the summary of changes:
+
+1) CATALOG
+GTTs store session-specific data. The storage information of GTTs'data, their
+transaction information, and their statistics are not stored in the catalog.
+
+2) STORAGE INFO & STATISTICS & TRANSACTION
+In order to maintain durability and availability of GTTs'session-specific data,
+their storage information, statistics, and transaction information is managed
+in a local hash table tt_storage_local_hash.
+
+3) DDL
+A shared hash table active_gtt_shared_hash is added to track the state of the
+GTT in a different session. This information is recorded in the hash table
+during the DDL execution of the GTT.
+
+4) LOCK
+The data stored in a GTT can only be modified or accessed by owning session.
+The statements that only modify data in a GTT do not need a high level of table
+locking.
+Changes to the GTT's metadata affect all sessions.
+The operations making those changes include truncate GTT, Vacuum/Cluster GTT,
+and Lock GTT.
+
+Detailed design
+-----------------------------------------
+
+1. CATALOG
+1.1 relpersistence
+define RELPERSISTENCEGLOBALTEMP 'g'
+Mark global temp table in pg_class relpersistence to 'g'. The relpersistence
+of indexes created on the GTT, sequences on GTT and toast tables on GTT are
+also set to 'g'
+
+1.2 on commit clause
+LTT's status associated with on commit DELETE ROWS and on commit PRESERVE ROWS
+is not stored in catalog. Instead, GTTs need a bool value on_commit_delete_rows
+in reloptions which is shared among sessions.
+
+1.3 gram.y
+GTT is already supported in syntax tree. We remove the warning message
+"GLOBAL is deprecated in temporary table creation" and mark
+relpersistence = RELPERSISTENCEGLOBALTEMP.
+
+2. STORAGE INFO & STATISTICS DATA & TRANSACTION INFO
+2.1. gtt_storage_local_hash
+Each backend creates a local hashtable gtt_storage_local_hash to track a GTT's
+storage file information, statistics, and transaction information.
+
+2.2 GTT storage file info track
+1) When one session inserts data into a GTT for the first time, record the
+storage info to gtt_storage_local_hash.
+2) Use beforeshmemexit to ensure that all files ofa session GTT are deleted when
+the session exits.
+3) GTT storage file cleanup during abnormal situations
+When a backend exits abnormally (such as oom kill), the startup process starts
+recovery before accepting client connection. The same startup process checks
+nd removes all GTT files before redo WAL.
+
+2.3 statistics info
+1) relpages reltuples relallvisible
+2) The statistics of each column from pg_statistic
+All the above information is stored in gtt_storage_local_hash.
+When doing vacuum or analyze, GTT's statistic is updated, which is used by
+the SQL planner.
+The statistics summarizes only data in the current session.
+
+2.3 transaction info track
+frozenxid minmulti from pg_class is stored to gtt_storage_local_hash.
+
+4 DDL
+4.1. active_gtt_shared_hash
+This is the hash table created in shared memory to trace the GTT files initialized
+in each session. Each hash entry contains a bitmap that records the backendid of
+the initialized GTT file. With this hash table, we know which backend/session
+is using this GTT. Such information is used during GTT's DDL operations.
+
+4.1 DROP GTT
+One GTT is allowed to be deleted when there is only one session using the table
+and the session is the current session.
+After holding the AccessExclusiveLock lock on GTT, active_gtt_shared_hash
+is checked to ensure that.
+
+4.2 ALTER GTT/DROP INDEX ON GTT
+Same as drop GTT.
+
+4.3 CREATE INDEX ON GTT
+1) create index on GTT statements build index based on local data in a session.
+2) After the index is created, record the index metadata to the catalog.
+3) Other sessions can enable or disable the local GTT index.
+
+5 LOCK
+
+5.1 TRUNCATE GTT
+The truncate GTT command uses RowExclusiveLock, not AccessExclusiveLock, because
+this command only cleans up local data and local buffers in current session.
+
+5.2 CLUSTER GTT/VACUUM FULL GTT
+Same as truncate GTT.
+
+5.3 Lock GTT
+A lock GTT statement does not hold any table locks.
+
+6 MVCC commit log(clog) cleanup
+
+The GTT storage file contains transaction information. Queries for GTT data rely
+on transaction information such as clog. The transaction information required by
+each session may be completely different. We need to ensure that the transaction
+information of the GTT data is not cleaned up during its lifetime and that
+transaction resources are recycled at the instance level.
+
+6.1 The session level GTT oldest frozenxid
+1) To manage all GTT transaction information, add session level oldest frozenxid
+in each session. When one GTT is created or removed, record the session level
+oldest frozenxid and store it in MyProc.
+2) When vacuum advances the database's frozenxid, session level oldest frozenxid
+should be considered. This is acquired by searching all of MyProc. This way,
+we can avoid the clog required by GTTs to be cleaned.
+
+6.2 vacuum GTT
+Users can perform vacuum over a GTT to clean up local data in the GTT.
+
+6.3 autovacuum GTT
+Autovacuum skips all GTTs, because the data in GTTs is only visible in current
+session.
+
+7 OTHERS
+Parallel query
+Planner does not produce parallel query plans for SQL related to GTT. Because 
+GTT private data cannot be accessed across processes.
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 2519771..e31d817 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -43,6 +43,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index f984514..106e22e 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -392,6 +392,7 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 4cd7d76..9554dce 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -304,7 +306,8 @@ heap_create(const char *relname,
 			bool mapped_relation,
 			bool allow_system_table_mods,
 			TransactionId *relfrozenxid,
-			MultiXactId *relminmxid)
+			MultiXactId *relminmxid,
+			bool skip_create_storage)
 {
 	bool		create_storage;
 	Relation	rel;
@@ -369,7 +372,9 @@ heap_create(const char *relname,
 	 * storage is already created, so don't do it here.  Also don't create it
 	 * for relkinds without physical storage.
 	 */
-	if (!RELKIND_HAS_STORAGE(relkind) || OidIsValid(relfilenode))
+	if (!RELKIND_HAS_STORAGE(relkind) ||
+		OidIsValid(relfilenode) ||
+		skip_create_storage)
 		create_storage = false;
 	else
 	{
@@ -427,7 +432,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -988,6 +993,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -1026,8 +1032,21 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/*
+	 * The transaction information of the global temporary table is stored
+	 * in the local hash table, not in catalog.
+	 */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1291,7 +1310,8 @@ heap_create_with_catalog(const char *relname,
 							   mapped_relation,
 							   allow_system_table_mods,
 							   &relfrozenxid,
-							   &relminmxid);
+							   &relminmxid,
+							   false);
 
 	Assert(relid == RelationGetRelid(new_rel_desc));
 
@@ -1397,6 +1417,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1976,6 +1997,19 @@ heap_drop_with_catalog(Oid relid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
 	/*
+	 * Global temporary table is allowed to be dropped only when the
+	 * current session is using it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot drop global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
+	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
 	if (RELKIND_HAS_STORAGE(rel->rd_rel->relkind))
@@ -3238,7 +3272,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3250,7 +3284,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3296,8 +3330,16 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		/*
+		 * Truncate global temporary table only clears local data,
+		 * so only low-level locks need to be held.
+		 */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3330,6 +3372,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3338,23 +3381,47 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	/* For global temporary table only */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		/*
+		 * If this GTT is not initialized in current backend, there is
+		 * no needs to anything.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(rel)))
+			return;
+
+		/*
+		 * Truncate GTT only clears local data, so only low-level locks
+		 * need to be held.
+		 */
+		lockmode = RowExclusiveLock;
+	}
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	/*
+	 * After the data is cleaned up on the GTT, the transaction information
+	 * for the data(stored in local hash table) is also need reset.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(RelationGetRelid(rel), 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 731610c..62c03bf 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -54,6 +54,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "commands/defrem.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -720,6 +721,29 @@ index_create(Relation heapRelation,
 	char		relkind;
 	TransactionId relfrozenxid;
 	MultiXactId relminmxid;
+	bool		skip_create_storage = false;
+
+	/* For global temporary table only */
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temporary table with concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot reindex global temporary tables concurrently")));
+
+		/*
+		 * For the case that some backend is applied relcache message to create
+		 * an index on a global temporary table, if this table in the current
+		 * backend are not initialized, the creation of index storage on the
+		 * table are also skipped.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+		{
+			skip_create_storage = true;
+			flags |= INDEX_CREATE_SKIP_BUILD;
+		}
+	}
 
 	/* constraint flags can only be set when a constraint is requested */
 	Assert((constr_flags == 0) ||
@@ -927,7 +951,8 @@ index_create(Relation heapRelation,
 								mapped_relation,
 								allow_system_table_mods,
 								&relfrozenxid,
-								&relminmxid);
+								&relminmxid,
+								skip_create_storage);
 
 	Assert(relfrozenxid == InvalidTransactionId);
 	Assert(relminmxid == InvalidMultiXactId);
@@ -2200,7 +2225,8 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 * lock (see comments in RemoveRelations), and a non-concurrent DROP is
 	 * more efficient.
 	 */
-	Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+	Assert(!(get_rel_persistence(indexId) == RELPERSISTENCE_TEMP ||
+			 get_rel_persistence(indexId) == RELPERSISTENCE_GLOBAL_TEMP) ||
 		   (!concurrent && !concurrent_lock_mode));
 
 	/*
@@ -2233,6 +2259,20 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
 	/*
+	 * Allow to drop index on global temporary table when only current
+	 * backend use it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation) &&
+		is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+	{
+		ereport(ERROR,
+			(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+			 errmsg("cannot drop index %s or global temporary table %s",
+					RelationGetRelationName(userIndexRelation), RelationGetRelationName(userHeapRelation)),
+			 errhint("Because the index is created on the global temporary table and other backend attached it.")));
+	}
+
+	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
 	 *
@@ -2840,6 +2880,7 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = RELATION_IS_GLOBAL_TEMP(rel);
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2934,20 +2975,37 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
-		{
-			rd_rel->relpages = (int32) relpages;
-			dirty = true;
-		}
-		if (rd_rel->reltuples != (float4) reltuples)
+		/* For global temporary table */
+		if (is_gtt)
 		{
-			rd_rel->reltuples = (float4) reltuples;
-			dirty = true;
+			/* Update GTT'statistics into local relcache */
+			rel->rd_rel->relpages = (int32) relpages;
+			rel->rd_rel->reltuples = (float4) reltuples;
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+
+			/* Update GTT'statistics into local hashtable */
+			up_gtt_relstats(RelationGetRelid(rel), relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+		else
 		{
-			rd_rel->relallvisible = (int32) relallvisible;
-			dirty = true;
+			if (rd_rel->relpages != (int32) relpages)
+			{
+				rd_rel->relpages = (int32) relpages;
+				dirty = true;
+			}
+
+			if (rd_rel->reltuples != (float4) reltuples)
+			{
+				rd_rel->reltuples = (float4) reltuples;
+				dirty = true;
+			}
+
+			if (rd_rel->relallvisible != (int32) relallvisible)
+			{
+				rd_rel->relallvisible = (int32) relallvisible;
+				dirty = true;
+			}
 		}
 	}
 
@@ -3062,6 +3120,26 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, index, val);
 	}
 
+	/* For build index on global temporary table */
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		/*
+		 * If the storage for the index in this session is not initialized,
+		 * it needs to be created.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			/* Before create init storage, fix the local Relcache first */
+			gtt_force_enable_index(indexRelation);
+
+			Assert(gtt_storage_attached(RelationGetRelid(heapRelation)));
+
+			/* Init storage for index */
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3625,6 +3703,20 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	if (!heapRelation)
 		return;
 
+	/*
+	 * For reindex on global temporary table, If the storage for the index
+	 * in current backend is not initialized, nothing is done.
+	 */
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		!gtt_storage_attached(indexId))
+	{
+		/* Suppress use of the target index while rebuilding it */
+		SetReindexProcessing(heapId, indexId);
+		/* Re-allow use of target index */
+		ResetReindexProcessing();
+		return;
+	}
+
 	if (progress)
 	{
 		pgstat_progress_start_command(PROGRESS_COMMAND_CREATE_INDEX,
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 740570c..bcd24d9 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -655,6 +655,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/* Do not allow create global temporary table in temporary schemas */
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp table in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index d538f25..dbc84e8 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,6 +27,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -61,6 +62,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			temprelOid;			/* InvalidOid if not a global temporary rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -115,7 +117,7 @@ AddPendingSync(const RelFileNode *rnode)
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -126,7 +128,12 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/*
+		 * Global temporary table and local temporary table use same
+		 * design on storage module.
+		 */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -154,6 +161,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
@@ -165,6 +173,21 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		AddPendingSync(&rnode);
 	}
 
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		Assert(rel && RELATION_IS_GLOBAL_TEMP(rel));
+
+		/*
+		 * Remember the reloid of global temporary table, which is used for
+		 * transaction commit or rollback.
+		 * see smgrDoPendingDeletes.
+		 */
+		pending->temprelOid = RelationGetRelid(rel);
+
+		/* Remember global temporary table storage info to localhash */
+		remember_gtt_storage_info(rnode, rel);
+	}
+
 	return srel;
 }
 
@@ -201,12 +224,21 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
 	/*
+	 * Remember the reloid of global temporary table, which is used for
+	 * transaction commit or rollback.
+	 * see smgrDoPendingDeletes.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->temprelOid = RelationGetRelid(rel);
+
+	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
 	 * once with atCommit false.  Hence, it will be physically deleted at end
@@ -602,6 +634,7 @@ smgrDoPendingDeletes(bool isCommit)
 	int			nrels = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -631,14 +664,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->temprelOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -648,12 +685,21 @@ smgrDoPendingDeletes(bool isCommit)
 
 	if (nrels > 0)
 	{
+		int	i;
+
 		smgrdounlinkall(srels, nrels, false);
 
-		for (int i = 0; i < nrels; i++)
+		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* Delete global temporary table info in localhash */
+			if (gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..1eb039a
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1499 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  The body implementation of Global Temparary table.
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *	  See src/backend/catalog/GTT_README for Global temparary table's
+ *	  requirements and design.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_relstats_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+int		vacuum_gtt_defer_check_age = 0;
+
+typedef struct gtt_ctl_data
+{
+	LWLock			lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+	Oid			relfilenode;
+	Oid			spcnode;
+
+	/* pg_class stat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+
+	/* pg_statistic */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+
+	Oid			oldrelid;			/* remember the source of relid, before the switch relfilenode. */
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_reset_statistics(gtt_local_hash_entry *entry);
+static void gtt_free_statistics(gtt_local_hash_entry *entry);
+static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok);
+static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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();
+	}
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(gtt_fnode);
+	info.entrysize = action_gtt_shared_hash_entry_size();
+	active_gtt_shared_hash =
+		ShmemInitHash("active gtt shared hash",
+						gtt_shared_ctl->max_entry,
+						gtt_shared_ctl->max_entry,
+						&info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			found;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *)&(fnode), 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_global_temporary_table.")));
+	}
+
+	if (found == false)
+	{
+		int			wordnum;
+
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode				fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	if (!skiplock)
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		if (!skiplock)
+			LWLockRelease(&gtt_shared_ctl->lock);
+
+		if (isCommit)
+			elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+	bms_del_member(entry->map, MyBackendId);
+
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+
+	if (!skiplock)
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+	return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	Bitmapset	*map_copy = NULL;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool		in_use = false;
+	int			num_use = 0;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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;
+	gtt_relfilenode			*new_node = NULL;
+	Oid						relid = RelationGetRelid(rel);
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		/* First time through: initialize the hash table */
+		HASHCTL		ctl;
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_relstats_context =
+			AllocSetContextCreate(CacheMemoryContext,
+							"gtt relstats context",
+							ALLOCSET_DEFAULT_SIZES);
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+	entry = gtt_search_by_relid(relid, true);
+	if (!entry)
+	{
+		bool		found = false;
+		int 		natts = 0;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash,
+						(void *) &relid, HASH_ENTER, &found);
+
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+		entry->natts = 0;
+		entry->attnum = NULL;
+		entry->att_stat_tups = NULL;
+		entry->oldrelid = InvalidOid;
+
+		natts = RelationGetNumberOfAttributes(rel);
+		entry->attnum = palloc0(sizeof(int) * natts);
+		entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+		entry->natts = natts;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			gtt_storage_checkin(relid);
+		}
+	}
+
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* only heap contain transaction information */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	gtt_reset_statistics(entry);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	gtt_relfilenode *d_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+		return;
+	}
+
+	d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+		else if (entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+			gtt_relfilenode *gttnode2 = NULL;
+
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+			Assert(gttnode2->relfilenode == rnode.relNode);
+			Assert(list_length(entry->relfilenode_list) == 1);
+			/* rollback switch relfilenode */
+			gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+									   entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+									   false);
+			/* clean up footprint */
+			entry2->oldrelid = InvalidOid;
+			d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+			Assert(d_rnode);
+		}
+		else
+		{
+			if (entry->relfilenode_list == NIL)
+			{
+				if (entry->relkind == RELKIND_RELATION ||
+					entry->relkind == RELKIND_SEQUENCE)
+					gtt_storage_checkout(relid, false, isCommit);
+
+				gtt_free_statistics(entry);
+				hash_search(gtt_storage_local_hash,
+						(void *) &(relid), HASH_REMOVE, NULL);
+			}
+
+			return;
+		}
+	}
+
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	pfree(d_rnode);
+	if (entry->relfilenode_list == NIL)
+	{
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(relid, false, isCommit);
+
+		if (isCommit && entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			/* clean up footprint */
+			entry2->oldrelid = InvalidOid;
+		}
+
+		gtt_free_statistics(entry);
+		hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_REMOVE, NULL);
+	}
+	else
+		gtt_reset_statistics(entry);
+
+	return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool found = false;
+	gtt_local_hash_entry *entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (!OidIsValid(relid))
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry)
+		found = true;
+
+	return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS				status;
+	gtt_local_hash_entry		*entry;
+	SMgrRelation	*srels = NULL;
+	Oid				*relids = NULL;
+	char			*relkinds = NULL;
+	int			nrels = 0,
+				nfiles = 0,
+				maxrels = 0,
+				maxfiles = 0,
+				i = 0;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel;
+			RelFileNode 	rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel = smgropen(rnode, MyBackendId);
+
+			if (maxfiles == 0)
+			{
+				maxfiles = 32;
+				srels = palloc(sizeof(SMgrRelation) * maxfiles);
+			}
+			else if (maxfiles <= nfiles)
+			{
+				maxfiles *= 2;
+				srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+			}
+
+			srels[nfiles++] = srel;
+		}
+
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			relids  = palloc(sizeof(Oid) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			relids  = repalloc(relids , sizeof(Oid) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		relkinds[nrels] = entry->relkind;
+		relids[nrels] = entry->relid;
+		nrels++;
+	}
+
+	if (nfiles > 0)
+	{
+		smgrdounlinkall(srels, nfiles, false);
+		for (i = 0; i < nfiles; i++)
+			smgrclose(srels[i]);
+
+		pfree(srels);
+	}
+
+	if (nrels)
+	{
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+		for (i = 0; i < nrels; i++)
+		{
+			if (relkinds[i] == RELKIND_RELATION ||
+				relkinds[i] == RELKIND_SEQUENCE)
+				gtt_storage_checkout(relids[i], true, false);
+		}
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+		pfree(relids);
+		pfree(relkinds);
+	}
+
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;
+
+	return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible) 
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Oid relid,
+					BlockNumber num_pages,
+					double num_tuples,
+					BlockNumber num_all_visible_pages,
+					TransactionId relfrozenxid,
+					TransactionId relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (!OidIsValid(relid))
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (num_pages > 0 &&
+		gtt_rnode->relpages != (int32)num_pages)
+		gtt_rnode->relpages = (int32)num_pages;
+
+	if (num_tuples > 0 &&
+		gtt_rnode->reltuples != (float4)num_tuples)
+		gtt_rnode->reltuples = (float4)num_tuples;
+
+	/* only heap contain transaction information and relallvisible */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (num_all_visible_pages > 0 &&
+			gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+		{
+			gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+		}
+
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNewTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible) 
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	MemoryContext			oldcontext;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return;
+
+	if (entry->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	Assert(entry->relid == reloid);
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == 0)
+		{
+			entry->attnum[i] = attnum;
+			break;
+		}
+		else if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < entry->natts);
+	Assert(entry->att_stat_tups[i] == NULL);
+	entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return NULL;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			return entry->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int			i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->backend_gtt_frozenxid != gtt_frozenxid)
+		MyProc->backend_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	HeapTuple	tuple;
+	Relation	rel = NULL;
+	int		attnum = PG_GETARG_INT32(1);
+	Oid		reloid = PG_GETARG_OID(0);
+	char		rel_persistence;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	  tupdesc;
+	MemoryContext oldcontext;
+	Tuplestorestate *tupstore;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+
+	relation_close(rel, NoLock);
+	relation_close(pg_tatistic, AccessShareLock);
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate	*tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Oid			relnode = 0;
+	Relation	rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[6];
+		bool	isnull[6];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(reloid);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_backend_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(RelationGetRelid(index)));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+void
+gtt_fix_index_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid relOid = index->rd_index->indrelid;
+
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	if (gtt_storage_attached(relOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int			i;
+	Oid			toastrelid;
+
+	if (!(operation == CMD_UTILITY || operation == CMD_INSERT))
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+
+		index_build(relation, index, info, true, false);
+	}
+
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid 		indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo 	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+static void
+gtt_reset_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+
+		entry->attnum[i] = 0;
+	}
+
+	return;
+}
+
+static void
+gtt_free_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (entry->attnum)
+		pfree(entry->attnum);
+
+	if (entry->att_stat_tups)
+		pfree(entry->att_stat_tups);
+
+	return;
+}
+
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
+void
+gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint)
+{
+	gtt_local_hash_entry	*entry1;
+	gtt_local_hash_entry	*entry2;
+	gtt_relfilenode			*gtt_rnode1 = NULL;
+	gtt_relfilenode			*gtt_rnode2 = NULL;
+	MemoryContext			oldcontext;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry1 = gtt_search_by_relid(rel1, false);
+	gtt_rnode1 = gtt_search_relfilenode(entry1, relfilenode1, false);
+
+	entry2 = gtt_search_by_relid(rel2, false);
+	gtt_rnode2 = gtt_search_relfilenode(entry2, relfilenode2, false);
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	entry1->relfilenode_list = list_delete_ptr(entry1->relfilenode_list, gtt_rnode1);
+	entry2->relfilenode_list = lappend(entry2->relfilenode_list, gtt_rnode1);
+
+	entry2->relfilenode_list = list_delete_ptr(entry2->relfilenode_list, gtt_rnode2);
+	entry1->relfilenode_list = lappend(entry1->relfilenode_list, gtt_rnode2);
+	MemoryContextSwitchTo(oldcontext);
+
+	if (footprint)
+	{
+		entry1->oldrelid = rel2;
+		entry2->oldrelid = rel1;
+	}
+
+	return;
+}
+
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok)
+{
+	gtt_relfilenode			*rnode = NULL;
+	ListCell				*lc;
+
+	Assert(entry);
+
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == relfilenode)
+		{
+			rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (!missing_ok && rnode == NULL)
+		elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+	return rnode;
+}
+
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry == NULL && !missing_ok)
+		elog(ERROR, "relid %u not found in local hash", relid);
+
+	return entry;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2e4aa1c..f1baaa0 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM 
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 8af12b5..a0b6390 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -185,6 +186,17 @@ analyze_rel(Oid relid, RangeVar *relation,
 	}
 
 	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
+	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
 	if (RelationGetRelid(onerel) == StatisticRelationId)
@@ -575,14 +587,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 		 * pg_statistic for columns we didn't process, we leave them alone.)
 		 */
 		update_attstats(RelationGetRelid(onerel), inh,
-						attr_cnt, vacattrstats);
+						attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
 
 		for (ind = 0; ind < nindexes; ind++)
 		{
 			AnlIndexData *thisdata = &indexdata[ind];
 
 			update_attstats(RelationGetRelid(Irel[ind]), false,
-							thisdata->attr_cnt, thisdata->vacattrstats);
+							thisdata->attr_cnt, thisdata->vacattrstats,
+							RelationGetRelPersistence(Irel[ind]));
 		}
 
 		/*
@@ -1445,7 +1458,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
  *		by taking a self-exclusive lock on the relation in analyze_rel().
  */
 static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
 {
 	Relation	sd;
 	int			attno;
@@ -1547,31 +1560,48 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/*
+		 * For global temporary table,
+		 * Update column statistic to localhash, not catalog.
+		 */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 04d12a7..f83c1d9 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/toasting.h"
 #include "commands/cluster.h"
 #include "commands/progress.h"
@@ -72,6 +73,12 @@ static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
 							bool verbose, bool *pSwapToastByContent,
 							TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
 static List *get_tables_to_cluster(MemoryContext cluster_context);
+static void gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables);
 
 
 /*---------------------------------------------------------------------------
@@ -367,6 +374,18 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
 	}
 
 	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap) &&
+		!gtt_storage_attached(RelationGetRelid(OldHeap)))
+	{
+		relation_close(OldHeap, AccessExclusiveLock);
+		pgstat_progress_end_command();
+		return;
+	}
+
+	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
 	 */
@@ -750,6 +769,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	BlockNumber num_pages;
 	int			elevel = verbose ? INFO : DEBUG2;
 	PGRUsage	ru0;
+	bool		is_gtt = false;
+	uint32		gtt_relfrozenxid = 0;
+	uint32		gtt_relminmxid = 0;
 
 	pg_rusage_init(&ru0);
 
@@ -763,6 +785,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	else
 		OldIndex = NULL;
 
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		is_gtt = true;
+
 	/*
 	 * Their tuple descriptors should be exactly alike, but here we only need
 	 * assume that they have the same number of columns.
@@ -830,20 +855,38 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 						  &OldestXmin, &FreezeXid, NULL, &MultiXactCutoff,
 						  NULL);
 
-	/*
-	 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
-	 * backwards, so take the max.
-	 */
-	if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
-		TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
-		FreezeXid = OldHeap->rd_rel->relfrozenxid;
+	if (is_gtt)
+	{
+		/* Gets transaction information for global temporary table from localhash. */
+		get_gtt_relstats(OIDOldHeap,
+					NULL, NULL, NULL,
+					&gtt_relfrozenxid, &gtt_relminmxid);
+
+		if (TransactionIdIsValid(gtt_relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, gtt_relfrozenxid))
+			FreezeXid = gtt_relfrozenxid;
+
+		if (MultiXactIdIsValid(gtt_relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, gtt_relminmxid))
+			MultiXactCutoff = gtt_relminmxid;
+	}
+	else
+	{
+		/*
+		 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
+		 * backwards, so take the max.
+		 */
+		if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
+			FreezeXid = OldHeap->rd_rel->relfrozenxid;
 
-	/*
-	 * MultiXactCutoff, similarly, shouldn't go backwards either.
-	 */
-	if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
-		MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
-		MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+		/*
+		 * MultiXactCutoff, similarly, shouldn't go backwards either.
+		 */
+		if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
+			MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+	}
 
 	/*
 	 * Decide whether to use an indexscan or seqscan-and-optional-sort to scan
@@ -911,6 +954,15 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	table_close(OldHeap, NoLock);
 	table_close(NewHeap, NoLock);
 
+	/* Update relstats of global temporary table to localhash. */
+	if (is_gtt)
+	{
+		up_gtt_relstats(RelationGetRelid(NewHeap), num_pages, num_tuples, 0,
+						InvalidTransactionId, InvalidMultiXactId);
+		CommandCounterIncrement();
+		return;
+	}
+
 	/* Update pg_class to reflect the correct values of pages and tuples. */
 	relRelation = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1346,10 +1398,22 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 	 * Swap the contents of the heap relations (including any toast tables).
 	 * Also set old heap's relfrozenxid to frozenXid.
 	 */
-	swap_relation_files(OIDOldHeap, OIDNewHeap,
+	if (newrelpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		Assert(!is_system_catalog);
+		/* For global temporary table modify data in localhash, not pg_class */
+		gtt_swap_relation_files(OIDOldHeap, OIDNewHeap,
+								(OIDOldHeap == RelationRelationId),
+								swap_toast_by_content, is_internal,
+								frozenXid, cutoffMulti, mapped_tables);
+	}
+	else
+	{
+		swap_relation_files(OIDOldHeap, OIDNewHeap,
 						(OIDOldHeap == RelationRelationId),
 						swap_toast_by_content, is_internal,
 						frozenXid, cutoffMulti, mapped_tables);
+	}
 
 	/*
 	 * If it's a system catalog, queue a sinval message to flush all catcaches
@@ -1557,3 +1621,146 @@ get_tables_to_cluster(MemoryContext cluster_context)
 
 	return rvs;
 }
+
+/*
+ * For global temporary table, storage information is stored in localhash,
+ * This function like swap_relation_files except that update storage information,
+ * in the localhash, not pg_class.
+ */
+static void
+gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables)
+{
+	Relation	relRelation;
+	Oid			relfilenode1,
+				relfilenode2;
+	Relation	rel1;
+	Relation	rel2;
+
+	relRelation = table_open(RelationRelationId, RowExclusiveLock);
+
+	rel1 = relation_open(r1, AccessExclusiveLock);
+	rel2 = relation_open(r2, AccessExclusiveLock);
+
+	relfilenode1 = gtt_fetch_current_relfilenode(r1);
+	relfilenode2 = gtt_fetch_current_relfilenode(r2);
+
+	Assert(OidIsValid(relfilenode1) && OidIsValid(relfilenode2));
+	gtt_switch_rel_relfilenode(r1, relfilenode1, r2, relfilenode2, true);
+
+	CacheInvalidateRelcache(rel1);
+	CacheInvalidateRelcache(rel2);
+
+	InvokeObjectPostAlterHookArg(RelationRelationId, r1, 0,
+								 InvalidOid, is_internal);
+	InvokeObjectPostAlterHookArg(RelationRelationId, r2, 0,
+								 InvalidOid, true);
+
+	if (rel1->rd_rel->reltoastrelid || rel2->rd_rel->reltoastrelid)
+	{
+		if (swap_toast_by_content)
+		{
+			if (rel1->rd_rel->reltoastrelid && rel2->rd_rel->reltoastrelid)
+			{
+				gtt_swap_relation_files(rel1->rd_rel->reltoastrelid,
+									rel2->rd_rel->reltoastrelid,
+									target_is_pg_class,
+									swap_toast_by_content,
+									is_internal,
+									frozenXid,
+									cutoffMulti,
+									mapped_tables);
+			}
+			else
+				elog(ERROR, "cannot swap toast files by content when there's only one");
+		}
+		else
+		{
+			ObjectAddress baseobject,
+						toastobject;
+			long		count;
+
+			if (IsSystemRelation(rel1))
+				elog(ERROR, "cannot swap toast files by links for system catalogs");
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel1->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel2->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+
+			/* Register new dependencies */
+			baseobject.classId = RelationRelationId;
+			baseobject.objectSubId = 0;
+			toastobject.classId = RelationRelationId;
+			toastobject.objectSubId = 0;
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r1;
+				toastobject.objectId = rel1->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r2;
+				toastobject.objectId = rel2->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+		}
+	}
+
+	if (swap_toast_by_content &&
+		rel1->rd_rel->relkind == RELKIND_TOASTVALUE &&
+		rel2->rd_rel->relkind == RELKIND_TOASTVALUE)
+	{
+		Oid			toastIndex1,
+					toastIndex2;
+
+		/* Get valid index for each relation */
+		toastIndex1 = toast_get_valid_index(r1,
+											AccessExclusiveLock);
+		toastIndex2 = toast_get_valid_index(r2,
+											AccessExclusiveLock);
+
+		gtt_swap_relation_files(toastIndex1,
+							toastIndex2,
+							target_is_pg_class,
+							swap_toast_by_content,
+							is_internal,
+							InvalidTransactionId,
+							InvalidMultiXactId,
+							mapped_tables);
+	}
+
+	relation_close(rel1, NoLock);
+	relation_close(rel2, NoLock);
+
+	table_close(relRelation, RowExclusiveLock);
+
+	RelationCloseSmgrByOid(r1);
+	RelationCloseSmgrByOid(r2);
+
+	CommandCounterIncrement();
+}
+
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 115860a..d03575a 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -27,6 +27,7 @@
 #include "catalog/dependency.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -1113,7 +1114,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP_ON_CURRENT_SESSION(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, stmt->filename, stmt->is_program,
@@ -2837,6 +2838,9 @@ CopyFrom(CopyState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	/* Check and init global temporary table storage in current backend */
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	/*
 	 * Set up a ModifyTableState so we can let FDW(s) init themselves for
 	 * foreign-table result relation(s).
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 02c7a0c..72a27f6 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -554,7 +554,7 @@ DefineIndex(Oid relationId,
 	 * is more efficient.  Do this before any use of the concurrent option is
 	 * done.
 	 */
-	if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+	if (stmt->concurrent && !RelpersistenceTsTemp(get_rel_persistence(relationId)))
 		concurrent = true;
 	else
 		concurrent = false;
@@ -2467,7 +2467,7 @@ ReindexIndex(RangeVar *indexRelation, int options, bool isTopLevel)
 	if (relkind == RELKIND_PARTITIONED_INDEX)
 		ReindexPartitions(indOid, options, isTopLevel);
 	else if ((options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 persistence != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(persistence))
 		ReindexRelationConcurrently(indOid, options);
 	else
 		reindex_index(indOid, false, persistence,
@@ -2572,7 +2572,7 @@ ReindexTable(RangeVar *relation, int options, bool isTopLevel)
 	if (get_rel_relkind(heapOid) == RELKIND_PARTITIONED_TABLE)
 		ReindexPartitions(heapOid, options, isTopLevel);
 	else if ((options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(get_rel_persistence(heapOid)))
 	{
 		result = ReindexRelationConcurrently(heapOid, options);
 
@@ -2931,7 +2931,7 @@ ReindexMultipleInternal(List *relids, int options)
 			   relkind != RELKIND_PARTITIONED_TABLE);
 
 		if ((options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			relpersistence != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(relpersistence))
 		{
 			(void) ReindexRelationConcurrently(relid,
 											   options |
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 0982276..76355de 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -57,7 +57,10 @@ LockTableCommand(LockStmt *lockstmt)
 										  RangeVarCallbackForLockTable,
 										  (void *) &lockstmt->mode);
 
-		if (get_rel_relkind(reloid) == RELKIND_VIEW)
+		/* Lock table command ignores global temporary table. */
+		if (get_rel_persistence(reloid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+		else if (get_rel_relkind(reloid) == RELKIND_VIEW)
 			LockViewRecurse(reloid, lockstmt->mode, lockstmt->nowait, NIL);
 		else if (recurse)
 			LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait);
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 632b34a..2e6c570 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -275,8 +278,6 @@ ResetSequence(Oid seq_relid)
 	Buffer		buf;
 	HeapTupleData seqdatatuple;
 	HeapTuple	tuple;
-	HeapTuple	pgstuple;
-	Form_pg_sequence pgsform;
 	int64		startv;
 
 	/*
@@ -287,12 +288,7 @@ ResetSequence(Oid seq_relid)
 	init_sequence(seq_relid, &elm, &seq_rel);
 	(void) read_seq_tuple(seq_rel, &buf, &seqdatatuple);
 
-	pgstuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seq_relid));
-	if (!HeapTupleIsValid(pgstuple))
-		elog(ERROR, "cache lookup failed for sequence %u", seq_relid);
-	pgsform = (Form_pg_sequence) GETSTRUCT(pgstuple);
-	startv = pgsform->seqstart;
-	ReleaseSysCache(pgstuple);
+	startv = get_seqence_start_value(seq_relid);
 
 	/*
 	 * Copy the existing sequence tuple.
@@ -451,6 +447,15 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 
 	init_sequence(relid, &elm, &seqrel);
 
+	if (RELATION_IS_GLOBAL_TEMP(seqrel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(seqrel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary sequence %s when other backend attached it.",
+						RelationGetRelationName(seqrel))));
+	}
+
 	rel = table_open(SequenceRelationId, RowExclusiveLock);
 	seqtuple = SearchSysCacheCopy1(SEQRELID,
 								   ObjectIdGetDatum(relid));
@@ -611,7 +616,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +941,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1154,6 +1159,14 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	/* Initializes the storage for sequence which the global temporary table belongs. */
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1954,3 +1967,51 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+/*
+ * Get the startValue of the sequence from syscache.
+ */
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+/*
+ * Initialize sequence which global temporary table belongs.
+ */
+void
+gtt_init_seq(Relation rel)
+{
+	Datum		value[SEQ_COL_LASTCOL] = {0};
+	bool		null[SEQ_COL_LASTCOL] = {false};
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 46f1637..025e44a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -46,6 +46,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -560,6 +561,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
 static void ATExecAlterCollationRefreshVersion(Relation rel, List *coll);
+static OnCommitAction gtt_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -605,6 +607,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -616,7 +619,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * Check consistency of arguments
 	 */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !RelpersistenceTsTemp(stmt->relation->relpersistence))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -646,7 +649,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	if (RelpersistenceTsTemp(stmt->relation->relpersistence)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -747,6 +750,56 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* For global temporary table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* Check parent table */
+		if (inheritOids)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			table_close(relation, NoLock);
+		}
+
+		/* Check oncommit clause and save to reloptions */
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temporary table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1368,7 +1421,7 @@ RemoveRelations(DropStmt *drop)
 		 * relation persistence cannot be known without its OID.
 		 */
 		if (drop->concurrent &&
-			get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(get_rel_persistence(relOid)))
 		{
 			Assert(list_length(drop->objects) == 1 &&
 				   drop->removeType == OBJECT_INDEX);
@@ -1574,7 +1627,16 @@ ExecuteTruncate(TruncateStmt *stmt)
 		Relation	rel;
 		bool		recurse = rv->inh;
 		Oid			myrelid;
-		LOCKMODE	lockmode = AccessExclusiveLock;
+		LOCKMODE	lockmode;
+
+		/*
+		 * Truncate global temp table only cleans up the data in current backend,
+		 * only low-level locks are required.
+		 */
+		if (rv->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+		else
+			lockmode = AccessExclusiveLock;
 
 		myrelid = RangeVarGetRelidExtended(rv, lockmode,
 										   0, RangeVarCallbackForTruncate,
@@ -1839,6 +1901,14 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
 			continue;
 
 		/*
+		 * Skip the global temporary table that is not initialized for storage
+		 * in current backend.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			continue;
+
+		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
 		 * a new relfilenode in the current (sub)transaction, then we can just
@@ -3682,6 +3752,16 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temporary table only current backend use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -5005,6 +5085,42 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot rewrite temporary tables of other sessions")));
 
+			if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+			{
+				if (tab->chgPersistence)
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot change global temporary table persistence setting")));
+
+				/*
+				 * The storage for the global temporary table needs to be initialized
+				 * before rewrite table.
+				 */
+				if(!gtt_storage_attached(tab->relid))
+				{
+					ResultRelInfo *resultRelInfo;
+					MemoryContext oldcontext;
+					MemoryContext ctx_alter_gtt;
+
+					ctx_alter_gtt = AllocSetContextCreate(CurrentMemoryContext,
+											"gtt alter table", ALLOCSET_DEFAULT_SIZES);
+					oldcontext = MemoryContextSwitchTo(ctx_alter_gtt);
+
+					resultRelInfo = makeNode(ResultRelInfo);
+					InitResultRelInfo(resultRelInfo, OldHeap,
+									1, NULL, 0);
+					if (resultRelInfo->ri_RelationDesc->rd_rel->relhasindex &&
+						resultRelInfo->ri_IndexRelationDescs == NULL)
+						ExecOpenIndices(resultRelInfo, false);
+
+					init_gtt_storage(CMD_UTILITY, resultRelInfo);
+					ExecCloseIndices(resultRelInfo);
+
+					MemoryContextSwitchTo(oldcontext);
+					MemoryContextDelete(ctx_alter_gtt);
+				}
+			}
+
 			/*
 			 * Select destination tablespace (same as original unless user
 			 * requested a change)
@@ -8469,6 +8585,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -12972,6 +13094,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -13174,6 +13299,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temporary table");
+
 	/*
 	 * No work if no change in tablespace.
 	 */
@@ -13548,7 +13676,7 @@ index_copy_data(Relation rel, RelFileNode newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
@@ -14956,6 +15084,7 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
 	switch (rel->rd_rel->relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -17632,3 +17761,40 @@ ATExecAlterCollationRefreshVersion(Relation rel, List *coll)
 	index_update_collation_versions(rel->rd_id, get_collation_oid(coll, false));
 	CacheInvalidateRelcache(rel);
 }
+
+/*
+ * Parse the on commit clause for the temporary table 
+ */
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			bool	res = false;
+			char	*sval = defGetString(def);
+
+			/* It has to be a Boolean value */
+			if (!parse_bool(sval, &res))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
+
+			if (res)
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 395e75f..693f801 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/vacuum.h"
@@ -1233,6 +1234,22 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = RELATION_IS_GLOBAL_TEMP(relation);
+
+	 /* For global temporary table */
+	if (is_gtt)
+	{
+		/* Store relation statistics and transaction information to the localhash */
+		up_gtt_relstats(RelationGetRelid(relation),
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+
+		/* Update relation statistics to local relcache */
+		relation->rd_rel->relpages = (int32) num_pages;
+		relation->rd_rel->reltuples = (float4) num_tuples;
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1246,17 +1263,23 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (!is_gtt &&
+		pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (!is_gtt &&
+		pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (!is_gtt &&
+		pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1301,7 +1324,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1312,7 +1336,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1420,6 +1445,13 @@ vac_update_datfrozenxid(void)
 		}
 
 		/*
+		 * The relfrozenxid for a global temporary talble is stored in localhash,
+		 * not pg_class, See list_all_session_gtt_frozenxids()
+		 */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
+		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
 		 * to not be set (i.e. set to their respective Invalid*Id)
@@ -1476,6 +1508,43 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/* If enable global temporary table */
+	if (max_active_gtt > 0)
+	{
+		TransactionId	safe_age;
+		/*  */
+		TransactionId	oldest_gtt_frozenxid =
+			list_all_backend_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid))
+		{
+			safe_age = oldest_gtt_frozenxid + vacuum_gtt_defer_check_age;
+			if (safe_age < FirstNormalTransactionId)
+				safe_age += FirstNormalTransactionId;
+
+			/*
+			 * We tolerate that the minimum age of gtt is less than
+			 * the minimum age of conventional tables, otherwise it will
+			 * throw warning message.
+			 */
+			if (TransactionIdIsNormal(safe_age) &&
+				TransactionIdPrecedes(safe_age, newFrozenXid))
+			{
+				ereport(WARNING,
+					(errmsg("global temp table oldest relfrozenxid %u is the oldest in the entire db",
+							oldest_gtt_frozenxid),
+					 errdetail("The oldest relfrozenxid in pg_class is %u", newFrozenXid),
+					 errhint("If they differ greatly, please consider cleaning up the data in global temp table.")));
+			}
+
+			/*
+			 * We need to ensure that the clog required by gtt is not cleand.
+			 */
+			if (TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+				newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1811,6 +1880,19 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 	}
 
 	/*
+	 * Skip those global temporary table that are not initialized in
+	 * current backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
+	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
 	 * contents are probably not up-to-date on disk.  (We don't throw a
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6e65103..2f46140 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -530,6 +530,12 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	/* Global temporary table are not sensible. */
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 7179f58..c53db5b 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,10 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		/* This is one kind of temp table */
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 86594bd..3e4bb7e 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
@@ -606,6 +607,9 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	/* Init storage for partitioned global temporary table in current backend */
+	init_gtt_storage(mtstate->operation, leaf_part_rri);
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 29e07b7..f1ae8ad 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,6 +42,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -2289,6 +2290,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			ExecOpenIndices(resultRelInfo,
 							node->onConflictAction != ONCONFLICT_NONE);
 
+		/* Init storage for global temporary table in current backend */
+		init_gtt_storage(operation, resultRelInfo);
+
 		/*
 		 * If this is an UPDATE and a BEFORE UPDATE trigger is present, the
 		 * trigger itself might modify the partition-key values. So arrange
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 84a69b0..e6b4b0a 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -48,7 +48,7 @@
 #include "partitioning/partprune.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/lsyscache.h"
-
+#include "utils/rel.h"
 
 /* results of subquery_is_pushdown_safe */
 typedef struct pushdown_safety_info
@@ -623,7 +623,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			if (RelpersistenceTsTemp(get_rel_persistence(rte->relid)))
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 986d7a5..06b2ed3 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6435,7 +6435,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
-	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+	if (RELATION_IS_TEMP(heap) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 52c01eb..e9f28cc 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -29,6 +29,7 @@
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
 #include "catalog/index.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
@@ -230,6 +231,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temporary table in current backend */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 084e00f..606ce15 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2546,6 +2546,11 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("materialized views must not use temporary tables or views")));
 
+		if (is_query_using_gtt(query))
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("materialized views must not use global temporary tables or views")));
+
 		/*
 		 * A materialized view would either need to save parameters for use in
 		 * maintaining/loading the data or prohibit them entirely.  The latter
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index efc9c99..10f2d97 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3319,17 +3319,11 @@ OptTemp:	TEMPORARY					{ $$ = RELPERSISTENCE_TEMP; }
 			| LOCAL TEMP				{ $$ = RELPERSISTENCE_TEMP; }
 			| GLOBAL TEMPORARY
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
-					$$ = RELPERSISTENCE_TEMP;
+					$$ = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
-					$$ = RELPERSISTENCE_TEMP;
+					$$ = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED					{ $$ = RELPERSISTENCE_UNLOGGED; }
 			| /*EMPTY*/					{ $$ = RELPERSISTENCE_PERMANENT; }
@@ -11437,19 +11431,13 @@ OptTempTableName:
 				}
 			| GLOBAL TEMPORARY opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED opt_table qualified_name
 				{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index a56bd86..5319729 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3609,3 +3610,48 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* Check if the query uses global temporary table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index c709aba..26b723e 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -457,6 +457,13 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->options = seqoptions;
 
 	/*
+	 * If a sequence is bound to a global temporary table, then the sequence
+	 * must been "global temporary"
+	 */
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
+	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
 	 * clause, the "redundant options" error will point to their occurrence,
@@ -3214,6 +3221,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		cxt.isforeign = false;
 	}
 	cxt.relation = stmt->relation;
+	/* Sets the table persistence to the context */
+	cxt.relation->relpersistence = RelationGetRelPersistence(rel);
 	cxt.rel = rel;
 	cxt.inhRelations = NIL;
 	cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index aa5b97f..a8e41d2 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2108,6 +2108,14 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/*
+			 * Aotuvacuum cannot vacuum the private data stored in each backend
+			 * that belongs to global temporary table, so skip them.
+			 */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2174,7 +2182,7 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		if (RelpersistenceTsTemp(classForm->relpersistence))
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index ad0d1a9..c9fa4a4 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -2849,6 +2850,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * Returns 0 if this global temporary table is not initialized in current
+	 * backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 96c2aaa..432211d 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -22,6 +22,7 @@
 #include "access/subtrans.h"
 #include "access/syncscan.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -149,6 +150,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, active_gtt_shared_hash_size());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -221,6 +223,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* For global temporary table shared hashtable */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 94edb24..0797d32 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -65,6 +65,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -5039,3 +5040,78 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temporary table.
+ */
+int
+list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct *arrayP = procArray;
+	TransactionId result = InvalidTransactionId;
+	int			index;
+	uint8			flags = 0;
+	int			i = 0;
+
+	/* return 0 if feature is disabled */
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+		*n = 0;
+	}
+
+	/* Disable in standby node */
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		uint8           statusFlags = ProcGlobal->statusFlags[index];
+
+		if (statusFlags & flags)
+			continue;
+
+		/* Fetch all backend that is belonging to MyDatabaseId */
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->backend_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->backend_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->backend_gtt_frozenxid, result))
+				result = proc->backend_gtt_frozenxid;
+
+			/* save backend pid and backend level oldest relfrozenxid */
+			if (max_size > 0)
+			{
+				pids[i] = proc->pid;
+				xids[i] = proc->backend_gtt_frozenxid;
+				i++;
+			}
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (max_size > 0)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 2fa90cc..22f0f81 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -177,7 +177,9 @@ static const char *const BuiltinTrancheNames[] = {
 	/* LWTRANCHE_PARALLEL_APPEND: */
 	"ParallelAppend",
 	/* LWTRANCHE_PER_XACT_PREDICATE_LIST: */
-	"PerXactPredicateList"
+	"PerXactPredicateList",
+	/* LWTRANCHE_GTT_CTL */
+	"GlobalTempTableControl"
 };
 
 StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index d1738c6..d2bb05d 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -391,6 +391,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;	/* init backend level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
@@ -572,6 +573,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;	/* init backend level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 3319e97..fd3953b 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -982,6 +982,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index bec357f..a8e6f70 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -108,6 +108,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -4887,12 +4888,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))
 							{
@@ -5017,15 +5031,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))
 		{
@@ -6448,6 +6474,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;
@@ -6465,6 +6492,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,
@@ -6476,6 +6510,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;
@@ -6491,6 +6527,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,
@@ -7409,6 +7452,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))
@@ -7421,6 +7466,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 =
@@ -7433,6 +7486,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.
@@ -7452,6 +7507,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 ae23299..dcf38b5 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -2993,6 +2994,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 66393be..629f05f 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -66,6 +66,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1135,6 +1136,24 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1189,6 +1208,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			gtt_fix_index_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -1313,7 +1333,17 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+			if (newrelnode != InvalidOid &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2255,6 +2285,8 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		gtt_fix_index_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3481,6 +3513,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3588,28 +3624,34 @@ void
 RelationSetNewRelfilenode(Relation relation, char persistence)
 {
 	Oid			newrelfilenode;
-	Relation	pg_class;
-	HeapTuple	tuple;
+	Relation	pg_class = NULL;
+	HeapTuple	tuple = NULL;
 	Form_pg_class classform;
 	MultiXactId minmulti = InvalidMultiXactId;
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
+	bool		modify_pg_class = !RELATION_IS_GLOBAL_TEMP(relation);
 
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
 
-	/*
-	 * Get a writable copy of the pg_class tuple for the given relation.
-	 */
-	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+	if (modify_pg_class)
+	{
+		/*
+		 * Get a writable copy of the pg_class tuple for the given relation.
+		 */
+		pg_class = table_open(RelationRelationId, RowExclusiveLock);
 
-	tuple = SearchSysCacheCopy1(RELOID,
-								ObjectIdGetDatum(RelationGetRelid(relation)));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "could not find tuple for relation %u",
-			 RelationGetRelid(relation));
-	classform = (Form_pg_class) GETSTRUCT(tuple);
+		tuple = SearchSysCacheCopy1(RELOID,
+									ObjectIdGetDatum(RelationGetRelid(relation)));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u",
+				 RelationGetRelid(relation));
+		classform = (Form_pg_class) GETSTRUCT(tuple);
+	}
+	else
+		memset(&classform, 0, sizeof(classform));
 
 	/*
 	 * Schedule unlinking of the old storage at transaction commit.
@@ -3635,7 +3677,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 				/* handle these directly, at least for now */
 				SMgrRelation srel;
 
-				srel = RelationCreateStorage(newrnode, persistence);
+				srel = RelationCreateStorage(newrnode, persistence, relation);
 				smgrclose(srel);
 			}
 			break;
@@ -3655,6 +3697,15 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 			break;
 	}
 
+	if (!modify_pg_class)
+	{
+		Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+		Assert(RELATION_IS_GLOBAL_TEMP(relation));
+		Assert(!RelationIsMapped(relation));
+		relation->rd_node.relNode = relnode;
+		CacheInvalidateRelcache(relation);
+	}
 	/*
 	 * If we're dealing with a mapped index, pg_class.relfilenode doesn't
 	 * change; instead we have to send the update to the relation mapper.
@@ -3664,7 +3715,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	 * possibly-inaccurate values of relpages etc, but those will be fixed up
 	 * later.
 	 */
-	if (RelationIsMapped(relation))
+	else if (RelationIsMapped(relation))
 	{
 		/* This case is only supported for indexes */
 		Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3710,9 +3761,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 		CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 	}
 
-	heap_freetuple(tuple);
+	if (modify_pg_class)
+	{
+		heap_freetuple(tuple);
 
-	table_close(pg_class, RowExclusiveLock);
+		table_close(pg_class, RowExclusiveLock);
+	}
 
 	/*
 	 * Make the pg_class row change or relation map change visible.  This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index bb34630..5f53882 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -40,6 +40,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "commands/prepare.h"
 #include "commands/trigger.h"
@@ -145,6 +146,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global 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,
@@ -2046,6 +2059,15 @@ static struct config_bool ConfigureNamesBool[] =
 static struct config_int ConfigureNamesInt[] =
 {
 	{
+		{"max_active_global_temporary_table", PGC_POSTMASTER, UNGROUPED,
+			gettext_noop("max active global temporary table."),
+			NULL
+		},
+		&max_active_gtt,
+		DEFAULT_NUM_ACTIVE_GTT, MIN_NUM_ACTIVE_GTT, MAX_NUM_ACTIVE_GTT,
+		NULL, NULL, NULL
+	},
+	{
 		{"archive_timeout", PGC_SIGHUP, WAL_ARCHIVING,
 			gettext_noop("Forces a switch to the next WAL file if a "
 						 "new file has not been started within N seconds."),
@@ -2564,6 +2586,16 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"vacuum_gtt_defer_check_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("The defer check age of GTT, used to check expired data after vacuum."),
+			NULL
+		},
+		&vacuum_gtt_defer_check_age,
+		10000, 0, 1000000,
+		NULL, NULL, NULL
+	},
+
 	/*
 	 * See also CheckRequiredParameterValues() if this parameter changes
 	 */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index dc1d41d..7916a7a 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2432,6 +2432,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
 		dopt->no_unlogged_table_data)
 		return;
 
+	/* Don't dump data in global temp table/sequence */
+	if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		return;
+
 	/* Check that the data is not explicitly excluded */
 	if (simple_oid_list_member(&tabledata_exclude_oids,
 							   tbinfo->dobj.catId.oid))
@@ -15650,6 +15654,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
 		char	   *foreign = "";
+		char	   *table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15703,9 +15708,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 			binary_upgrade_set_pg_class_oids(fout, q,
 											 tbinfo->dobj.catId.oid, false);
 
+		if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED)
+			table_type = "UNLOGGED ";
+		else if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			table_type = "GLOBAL TEMPORARY ";
+		else
+			table_type = "";
+
 		appendPQExpBuffer(q, "CREATE %s%s %s",
-						  tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ?
-						  "UNLOGGED " : "",
+						  table_type,
 						  reltypename,
 						  qualrelname);
 
@@ -16099,7 +16110,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
-			 tbinfo->relkind == RELKIND_MATVIEW))
+			 tbinfo->relkind == RELKIND_MATVIEW) &&
+			 tbinfo->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
 		{
 			appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
 			appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
@@ -17029,6 +17041,7 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	PQExpBuffer query = createPQExpBuffer();
 	PQExpBuffer delqry = createPQExpBuffer();
 	char	   *qseqname;
+	bool	   global_temp_seq = false;
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
@@ -17038,9 +17051,11 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
-						  "FROM pg_catalog.pg_sequence "
-						  "WHERE seqrelid = '%u'::oid",
+						  "seqcache, seqcycle, c.relpersistence "
+						  "FROM pg_catalog.pg_sequence s, "
+						  "pg_catalog.pg_class c "
+						  "WHERE seqrelid = '%u'::oid "
+						  "and s.seqrelid = c.oid",
 						  tbinfo->dobj.catId.oid);
 	}
 	else if (fout->remoteVersion >= 80400)
@@ -17085,6 +17100,9 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
 
+	if (fout->remoteVersion >= 130000)
+		global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
 	if (strcmp(seqtype, "smallint") == 0)
@@ -17162,9 +17180,13 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	}
 	else
 	{
-		appendPQExpBuffer(query,
-						  "CREATE SEQUENCE %s\n",
-						  fmtQualifiedDumpable(tbinfo));
+		appendPQExpBuffer(query, "CREATE ");
+
+		if (global_temp_seq)
+			appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+		appendPQExpBuffer(query, "SEQUENCE %s\n",
+								  fmtQualifiedDumpable(tbinfo));
 
 		if (strcmp(seqtype, "bigint") != 0)
 			appendPQExpBuffer(query, "    AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 6685d51..e37d9bf 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -86,7 +86,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -166,7 +166,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 7e524ea..371168f 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -441,22 +443,46 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 	 * output, so we have to copy that system table.  It's easiest to do that
 	 * by treating it as a user table.
 	 */
-	snprintf(query + strlen(query), sizeof(query) - strlen(query),
-			 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
-			 "  SELECT c.oid, 0::oid, 0::oid "
-			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
-			 "         ON c.relnamespace = n.oid "
-			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
-	/* exclude possible orphaned temp tables */
-			 "    ((n.nspname !~ '^pg_temp_' AND "
-			 "      n.nspname !~ '^pg_toast_temp_' AND "
-			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
-			 "                        'binary_upgrade', 'pg_toast') AND "
-			 "      c.oid >= %u::pg_catalog.oid) OR "
-			 "     (n.nspname = 'pg_catalog' AND "
-			 "      relname IN ('pg_largeobject') ))), ",
-			 FirstNormalObjectId);
+	if (skip_gtt)
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude global temp tables */
+				 "    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
+	else
+	{
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+				 "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+				 "  SELECT c.oid, 0::oid, 0::oid "
+				 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+				 "         ON c.relnamespace = n.oid "
+				 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+				 CppAsString2(RELKIND_MATVIEW) ") AND "
+		/* exclude possible orphaned temp tables */
+				 "    ((n.nspname !~ '^pg_temp_' AND "
+				 "      n.nspname !~ '^pg_toast_temp_' AND "
+				 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
+				 "                        'binary_upgrade', 'pg_toast') AND "
+				 "      c.oid >= %u::pg_catalog.oid) OR "
+				 "     (n.nspname = 'pg_catalog' AND "
+				 "      relname IN ('pg_largeobject') ))), ",
+				 FirstNormalObjectId);
+	}
 
 	/*
 	 * Add a CTE that collects OIDs of toast tables belonging to the tables
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index e2253ec..2a07b24 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -407,7 +407,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -638,7 +638,9 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -649,7 +651,9 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index ee70243..a69089c 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -388,7 +388,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 07d6400..334251f 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3752,7 +3752,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 8afc780..761e6dd 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1044,6 +1044,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2434,6 +2436,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2642,6 +2647,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index f94deff..056fe64 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -59,7 +59,8 @@ extern Relation heap_create(const char *relname,
 							bool mapped_relation,
 							bool allow_system_table_mods,
 							TransactionId *relfrozenxid,
-							MultiXactId *relminmxid);
+							MultiXactId *relminmxid,
+							bool skip_create_storage);
 
 extern Oid	heap_create_with_catalog(const char *relname,
 									 Oid relnamespace,
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index bb5e72c..bab9599 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -175,6 +175,7 @@ DECLARE_INDEX(pg_class_tblspc_relfilenode_index, 3455, on pg_class using btree(r
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 33dacfd..2b38045 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5592,6 +5592,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4388',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4389',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4390',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4391',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 30c38e0..7ff2408 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,7 +22,7 @@
 /* GUC variables */
 extern int	wal_skip_threshold;
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationPreTruncate(Relation rel);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..35a46b1
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern int		vacuum_gtt_defer_check_age;
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Oid relid,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void gtt_force_enable_index(Relation index);
+extern void gtt_fix_index_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+extern void gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index e2638ab..89a5ce4 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index d0a52f8..8fa493f 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -399,6 +399,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index af9b417..6e16f99 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -218,6 +218,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_SHARED_TIDBITMAP,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_PER_XACT_PREDICATE_LIST,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 00bb244..7d78518 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -153,6 +153,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId backend_gtt_frozenxid;	/* backend level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index ea8a876..ed87772 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -92,4 +92,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 073c8f3..cd8cad8 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -283,6 +283,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index c5ffea4..a980ed5 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	struct SMgrRelationData *rd_smgr;	/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -306,6 +306,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -571,11 +572,13 @@ typedef struct ViewOptions
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -583,6 +586,7 @@ typedef struct ViewOptions
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -595,6 +599,30 @@ typedef struct ViewOptions
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP_ON_CURRENT_SESSION
+ *		Test a rel is either local temp relation of this session
+ * 		or global temp relation.
+ */
+#define RELATION_IS_TEMP_ON_CURRENT_SESSION(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is local temp relation or global temporary relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RelpersistenceTsTemp
+ *		Test a relpersistence is local temp relation or global temporary relation.
+ */
+#define RelpersistenceTsTemp(relpersistence) \
+	(relpersistence == RELPERSISTENCE_TEMP || \
+	 relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -638,6 +666,19 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* For global temporary table */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/* Get on commit clause value only for global temporary table */
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+/* Get relpersistence for relation */
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..ca2d135
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,14 @@
+reset search_path;
+select pg_sleep(5);
+ pg_sleep 
+----------
+ 
+(1 row)
+
+drop schema gtt cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
+drop cascades to table gtt.gtt_with_seq
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..85976ff
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,383 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+ n 
+---
+ 9
+(1 row)
+
+commit;
+-- 0 row
+select * from gtt6;
+ n 
+---
+(0 rows)
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temporary table
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temporary table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  could not create global temporary table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR:  insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count 
+-------
+     1
+(1 row)
+
+select count(*) from orders;
+ count 
+-------
+     0
+(1 row)
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  2 |  3
+(2 rows)
+
+commit;
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+select * from gtt_s_2 order by c1;
+ c1 | c2 
+----+----
+  1 |  2
+  2 |  4
+(2 rows)
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 1)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 300)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 33 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test_new
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.gtt6
+drop cascades to table gtt_function.foo
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to sequence gtt_function.seq_1
+drop cascades to table gtt_function.gtt_s_1
+drop cascades to table gtt_function.gtt_s_2
+drop cascades to table gtt_function.gt1
+drop cascades to table gtt_function.gtt_test1
+drop cascades to table gtt_function.gtt_test2
+drop cascades to table gtt_function.gtt_test3
+drop cascades to table gtt_function.gtt_test4
+drop cascades to table gtt_function.gtt_test5
+drop cascades to table gtt_function.gtt_test6
+drop cascades to table gtt_function.gtt_test7
+drop cascades to table gtt_function.gtt_test8
+drop cascades to table gtt_function.gtt_test9
+drop cascades to table gtt_function.gtt_test10
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..0646aae
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,90 @@
+set search_path=gtt,sys;
+select nextval('gtt_with_seq_c2_seq');
+ nextval 
+---------
+       1
+(1 row)
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..2f084be
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,343 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+rollback;
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+commit;
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          114688 |                 614400
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |            49152 |         49152 |                  49152
+(2 rows)
+
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2000
+(1 row)
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+ c1 | c2 
+----+----
+  1 |  1
+(1 row)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..8c0c376
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,10 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..4420fdb
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,80 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+ gtt_stats  | gtt_pkey  |        1 |         0 |             0
+(2 rows)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+reindex table gtt;
+reindex index gtt_pkey;
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+ gtt_stats  | gtt_pkey  |       30 |     10000 |             0
+(2 rows)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 097ff5d..27803d6 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1359,6 +1359,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index ae89ed7..c498e39 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..2c8e586
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,8 @@
+
+
+reset search_path;
+
+select pg_sleep(5);
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..81f0bfc
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,253 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+commit;
+-- 0 row
+select * from gtt6;
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+ 
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+commit;
+select * from gtt_s_1 order by c1;
+select * from gtt_s_2 order by c1;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d05745e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,44 @@
+
+
+set search_path=gtt,sys;
+
+select nextval('gtt_with_seq_c2_seq');
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..39cca5e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,154 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+rollback;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+commit;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from 
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+select count(*) from gtt_t_kenyon;
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..dbe84d1
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,19 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..d61b0ff
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,46 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+reindex table gtt;
+
+reindex index gtt_pkey;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#293Pavel Stehule
pavel.stehule@gmail.com
In reply to: 曾文旌 (#292)
Re: [Proposal] Global temporary tables

po 23. 11. 2020 v 10:27 odesílatel 曾文旌 <wenjing.zwj@alibaba-inc.com> napsal:

2020年11月21日 02:28,Pavel Stehule <pavel.stehule@gmail.com> 写道:

Hi

pá 11. 9. 2020 v 17:00 odesílatel 曾文旌 <wenjing.zwj@alibaba-inc.com>
napsal:

I have written the README for the GTT, which contains the GTT
requirements and design.
I found that compared to my first email a year ago, many GTT Limitations
are now gone.
Now, I'm adding comments to some of the necessary functions.

There are problems with patching. Please, can you rebase your patch?

Sure.
I'm still working on sort code and comments.
If you have any suggestions, please let me know.

It is broken again

There is bad white space

+   /*
+    * For global temp table only
+    * use ShareUpdateExclusiveLock for ensure safety
+    */
+   {
+       {
+           "on_commit_delete_rows",
+           "global temp table on commit options",
+           RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+           ShareUpdateExclusiveLock
+       },
+       true
+   },  <=================
    /* list terminator */
    {{NULL}}
+7 OTHERS
+Parallel query
+Planner does not produce parallel query plans for SQL related to GTT.
Because <=================
+GTT private data cannot be accessed across processes.
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible)
<========================
+ * to local hashtable
+ */
+void
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible)
<==============
+ * from lo

and there are lot of more places ...

I found other issue

postgres=# create global temp table foo(a int);
CREATE TABLE
postgres=# create index on foo(a);
CREATE INDEX

close session and in new session

postgres=# reindex index foo_a_idx ;
WARNING: relcache reference leak: relation "foo" not closed
REINDEX

Regards

Pavel

Show quoted text

Wenjing

Regards

Pavel

Wenjing

2020年7月31日 上午4:57,Robert Haas <robertmhaas@gmail.com> 写道:

On Thu, Jul 30, 2020 at 8:09 AM wenjing zeng <wjzeng2012@gmail.com>

wrote:

Please continue to review the code.

This patch is pretty light on comments. Many of the new functions have
no header comments, for example. There are comments here and there in
the body of the new functions that are added, and in places where
existing code is changed there are comments here and there, but
overall it's not a whole lot. There's no documentation and no README,
either. Since this adds a new feature and a bunch of new SQL-callable
functions that interact with that feature, the feature itself should
be documented, along with its limitations and the new SQL-callable
functions that interact with it. I think there should be either a
lengthy comment in some suitable file, or maybe various comments in
various files, or else a README file, that clearly sets out the major
design principles behind the patch, and explaining also what that
means in terms of features and limitations. Without that, it's really
hard for anyone to jump into reviewing this code, and it will be hard
for people who have to maintain it in the future to understand it,
either. Or for users, for that matter.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#294曾文旌
wenjing.zwj@alibaba-inc.com
In reply to: Pavel Stehule (#293)
2 attachment(s)
Re: [Proposal] Global temporary tables

2020年11月25日 14:19,Pavel Stehule <pavel.stehule@gmail.com> 写道:

po 23. 11. 2020 v 10:27 odesílatel 曾文旌 <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:

2020年11月21日 02:28,Pavel Stehule <pavel.stehule@gmail.com <mailto:pavel.stehule@gmail.com>> 写道:

Hi

pá 11. 9. 2020 v 17:00 odesílatel 曾文旌 <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:
I have written the README for the GTT, which contains the GTT requirements and design.
I found that compared to my first email a year ago, many GTT Limitations are now gone.
Now, I'm adding comments to some of the necessary functions.

There are problems with patching. Please, can you rebase your patch?

Sure.
I'm still working on sort code and comments.
If you have any suggestions, please let me know.

It is broken again

There is bad white space

+   /*
+    * For global temp table only
+    * use ShareUpdateExclusiveLock for ensure safety
+    */
+   {
+       {
+           "on_commit_delete_rows",
+           "global temp table on commit options",
+           RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+           ShareUpdateExclusiveLock
+       },
+       true
+   },  <=================
/* list terminator */
{{NULL}}
+7 OTHERS
+Parallel query
+Planner does not produce parallel query plans for SQL related to GTT. Because <=================
+GTT private data cannot be accessed across processes.
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible) <========================
+ * to local hashtable
+ */
+void
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible) <==============
+ * from lo

and there are lot of more places ...

I found other issue

postgres=# create global temp table foo(a int);
CREATE TABLE
postgres=# create index on foo(a);
CREATE INDEX

close session and in new session

postgres=# reindex index foo_a_idx ;
WARNING: relcache reference leak: relation "foo" not closed
REINDEX

I fixed all the above issues and rebase code.
Please review the new version code again.

Wenjing

Show quoted text

Regards

Pavel

Wenjing

Regards

Pavel

Wenjing

2020年7月31日 上午4:57,Robert Haas <robertmhaas@gmail.com <mailto:robertmhaas@gmail.com>> 写道:

On Thu, Jul 30, 2020 at 8:09 AM wenjing zeng <wjzeng2012@gmail.com <mailto:wjzeng2012@gmail.com>> wrote:

Please continue to review the code.

This patch is pretty light on comments. Many of the new functions have
no header comments, for example. There are comments here and there in
the body of the new functions that are added, and in places where
existing code is changed there are comments here and there, but
overall it's not a whole lot. There's no documentation and no README,
either. Since this adds a new feature and a bunch of new SQL-callable
functions that interact with that feature, the feature itself should
be documented, along with its limitations and the new SQL-callable
functions that interact with it. I think there should be either a
lengthy comment in some suitable file, or maybe various comments in
various files, or else a README file, that clearly sets out the major
design principles behind the patch, and explaining also what that
means in terms of features and limitations. Without that, it's really
hard for anyone to jump into reviewing this code, and it will be hard
for people who have to maintain it in the future to understand it,
either. Or for users, for that matter.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;
The Enterprise PostgreSQL Company

Attachments:

global_temporary_table_v38-pg14.patchapplication/octet-stream; name=global_temporary_table_v38-pg14.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 8ccc228..9ea9a49 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -168,6 +168,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * For global temporary table only
+	 * use ShareUpdateExclusiveLock for ensure safety
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temporary table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},
 	/* list terminator */
 	{{NULL}}
 };
@@ -1817,6 +1830,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1962,12 +1977,16 @@ bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
 	/*
-	 * There are no options for partitioned tables yet, but this is able to do
-	 * some validation.
+	 * Add option for global temp partitioned tables.
 	 */
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index 615b5ad..7736799 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1026,7 +1026,7 @@ gistproperty(Oid index_oid, int attno,
 XLogRecPtr
 gistGetFakeLSN(Relation rel)
 {
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	if (RELATION_IS_TEMP(rel))
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 7c9ccf4..f4561bc 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -151,7 +151,7 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	if (!RELATION_IS_TEMP(index))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index dcaea71..536aab6 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -589,7 +589,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -642,7 +642,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 25f2d5d..e49b67b 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -62,6 +62,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -445,9 +446,13 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
 	Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
-	/* not every AM requires these to be valid, but heap does */
-	Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
-	Assert(MultiXactIdIsValid(onerel->rd_rel->relminmxid));
+	/*
+	 * not every AM requires these to be valid, but regular heap does.
+	 * Transaction information for the global temp table is stored in
+	 * local hash, not catalog.
+	 */
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ MultiXactIdIsValid(onerel->rd_rel->relminmxid));
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 848123d..e9f9230 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -609,6 +610,14 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		/*
+		 * If a global temporary table storage file is not initialized in the
+		 * current session, its index does not have root page. just returns 0.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 13f1d8c..d32aabe 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6877,6 +6877,15 @@ StartupXLOG(void)
 		int			rmid;
 
 		/*
+		 * When pg backend processes crashes, those storage files that belong
+		 * to global temporary tables will not be cleaned up.
+		 * They will remain in the data directory. These data files no longer
+		 * belong to any session and need to be cleaned up during the crash recovery.
+		 */
+		if (max_active_gtt > 0)
+			RemovePgTempFiles();
+
+		/*
 		 * Update pg_control to show that we are recovering and to show the
 		 * selected checkpoint as the place we are starting from. We also mark
 		 * pg_control with any minimum recovery stop point obtained from a
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 6bb0c6e..9ad28c4 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -212,7 +212,8 @@ Boot_CreateStmt:
 												   mapped_relation,
 												   true,
 												   &relfrozenxid,
-												   &relminmxid);
+												   &relminmxid,
+												   false);
 						elog(DEBUG4, "bootstrap relation created");
 					}
 					else
diff --git a/src/backend/catalog/GTT_README b/src/backend/catalog/GTT_README
new file mode 100644
index 0000000..1d23b07
--- /dev/null
+++ b/src/backend/catalog/GTT_README
@@ -0,0 +1,170 @@
+Global temporary table(GTT)
+==============
+
+Feature description
+--------------------------------
+
+Previously, temporary tables are defined once and automatically
+created (starting with empty contents) in every session before using them.
+
+The temporary table implementation in PostgreSQL, known as Local temp tables(LTT),
+did not fully comply with the SQL standard. This version added the support of
+Global temporary Table .
+
+The metadata of Global temporary table is persistent and shared among sessions.
+The data stored in the Global temporary table is independent of sessions. This
+means, when a session creates a global temporary table and writes some data.
+Other sessions cannot see those data, but they have an empty Global temporary
+table with same schema.
+
+Like local temporary table, global temporary table supports ON COMMIT PRESERVE ROWS
+or ON COMMIT DELETE ROWS clause, so that data in the temporary table can be
+cleaned up or reserved automatically when a session exits or a transaction COMMITs.
+
+Unlike Local Temporary Table, Global temporary table does not support
+ON COMMIT DROP clauses.
+
+In following paragraphs, we use GTT for global temporary table and LTT for
+local temporary table.
+
+Main design ideas
+-----------------------------------------
+
+STORAGE & BUFFER
+
+In general, GTT and LTT use the same storage and buffer design and
+implementation. The storage files for both types of temporary tables are named
+as t_backendid_relfilenode, and the local buffer is used to cache the data.
+
+The schema of GTTs is shared among sessions while their data are not. We build
+a new mechanisms to manage those non-shared data and their statistics.
+Here is the summary of changes:
+
+1) CATALOG
+GTTs store session-specific data. The storage information of GTTs'data, their
+transaction information, and their statistics are not stored in the catalog.
+
+2) STORAGE INFO & STATISTICS & TRANSACTION
+In order to maintain durability and availability of GTTs'session-specific data,
+their storage information, statistics, and transaction information is managed
+in a local hash table tt_storage_local_hash.
+
+3) DDL
+A shared hash table active_gtt_shared_hash is added to track the state of the
+GTT in a different session. This information is recorded in the hash table
+during the DDL execution of the GTT.
+
+4) LOCK
+The data stored in a GTT can only be modified or accessed by owning session.
+The statements that only modify data in a GTT do not need a high level of table
+locking.
+Changes to the GTT's metadata affect all sessions.
+The operations making those changes include truncate GTT, Vacuum/Cluster GTT,
+and Lock GTT.
+
+Detailed design
+-----------------------------------------
+
+1. CATALOG
+1.1 relpersistence
+define RELPERSISTENCEGLOBALTEMP 'g'
+Mark global temp table in pg_class relpersistence to 'g'. The relpersistence
+of indexes created on the GTT, sequences on GTT and toast tables on GTT are
+also set to 'g'
+
+1.2 on commit clause
+LTT's status associated with on commit DELETE ROWS and on commit PRESERVE ROWS
+is not stored in catalog. Instead, GTTs need a bool value on_commit_delete_rows
+in reloptions which is shared among sessions.
+
+1.3 gram.y
+GTT is already supported in syntax tree. We remove the warning message
+"GLOBAL is deprecated in temporary table creation" and mark
+relpersistence = RELPERSISTENCEGLOBALTEMP.
+
+2. STORAGE INFO & STATISTICS DATA & TRANSACTION INFO
+2.1. gtt_storage_local_hash
+Each backend creates a local hashtable gtt_storage_local_hash to track a GTT's
+storage file information, statistics, and transaction information.
+
+2.2 GTT storage file info track
+1) When one session inserts data into a GTT for the first time, record the
+storage info to gtt_storage_local_hash.
+2) Use beforeshmemexit to ensure that all files ofa session GTT are deleted when
+the session exits.
+3) GTT storage file cleanup during abnormal situations
+When a backend exits abnormally (such as oom kill), the startup process starts
+recovery before accepting client connection. The same startup process checks
+nd removes all GTT files before redo WAL.
+
+2.3 statistics info
+1) relpages reltuples relallvisible
+2) The statistics of each column from pg_statistic
+All the above information is stored in gtt_storage_local_hash.
+When doing vacuum or analyze, GTT's statistic is updated, which is used by
+the SQL planner.
+The statistics summarizes only data in the current session.
+
+2.3 transaction info track
+frozenxid minmulti from pg_class is stored to gtt_storage_local_hash.
+
+4 DDL
+4.1. active_gtt_shared_hash
+This is the hash table created in shared memory to trace the GTT files initialized
+in each session. Each hash entry contains a bitmap that records the backendid of
+the initialized GTT file. With this hash table, we know which backend/session
+is using this GTT. Such information is used during GTT's DDL operations.
+
+4.1 DROP GTT
+One GTT is allowed to be deleted when there is only one session using the table
+and the session is the current session.
+After holding the AccessExclusiveLock lock on GTT, active_gtt_shared_hash
+is checked to ensure that.
+
+4.2 ALTER GTT/DROP INDEX ON GTT
+Same as drop GTT.
+
+4.3 CREATE INDEX ON GTT
+1) create index on GTT statements build index based on local data in a session.
+2) After the index is created, record the index metadata to the catalog.
+3) Other sessions can enable or disable the local GTT index.
+
+5 LOCK
+
+5.1 TRUNCATE GTT
+The truncate GTT command uses RowExclusiveLock, not AccessExclusiveLock, because
+this command only cleans up local data and local buffers in current session.
+
+5.2 CLUSTER GTT/VACUUM FULL GTT
+Same as truncate GTT.
+
+5.3 Lock GTT
+A lock GTT statement does not hold any table locks.
+
+6 MVCC commit log(clog) cleanup
+
+The GTT storage file contains transaction information. Queries for GTT data rely
+on transaction information such as clog. The transaction information required by
+each session may be completely different. We need to ensure that the transaction
+information of the GTT data is not cleaned up during its lifetime and that
+transaction resources are recycled at the instance level.
+
+6.1 The session level GTT oldest frozenxid
+1) To manage all GTT transaction information, add session level oldest frozenxid
+in each session. When one GTT is created or removed, record the session level
+oldest frozenxid and store it in MyProc.
+2) When vacuum advances the database's frozenxid, session level oldest frozenxid
+should be considered. This is acquired by searching all of MyProc. This way,
+we can avoid the clog required by GTTs to be cleaned.
+
+6.2 vacuum GTT
+Users can perform vacuum over a GTT to clean up local data in the GTT.
+
+6.3 autovacuum GTT
+Autovacuum skips all GTTs, because the data in GTTs is only visible in current
+session.
+
+7 OTHERS
+Parallel query
+Planner does not produce parallel query plans for SQL related to GTT. Because
+GTT private data cannot be accessed across processes.
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 2519771..e31d817 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -43,6 +43,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index f984514..106e22e 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -392,6 +392,7 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 4cd7d76..9554dce 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -304,7 +306,8 @@ heap_create(const char *relname,
 			bool mapped_relation,
 			bool allow_system_table_mods,
 			TransactionId *relfrozenxid,
-			MultiXactId *relminmxid)
+			MultiXactId *relminmxid,
+			bool skip_create_storage)
 {
 	bool		create_storage;
 	Relation	rel;
@@ -369,7 +372,9 @@ heap_create(const char *relname,
 	 * storage is already created, so don't do it here.  Also don't create it
 	 * for relkinds without physical storage.
 	 */
-	if (!RELKIND_HAS_STORAGE(relkind) || OidIsValid(relfilenode))
+	if (!RELKIND_HAS_STORAGE(relkind) ||
+		OidIsValid(relfilenode) ||
+		skip_create_storage)
 		create_storage = false;
 	else
 	{
@@ -427,7 +432,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -988,6 +993,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -1026,8 +1032,21 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/*
+	 * The transaction information of the global temporary table is stored
+	 * in the local hash table, not in catalog.
+	 */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1291,7 +1310,8 @@ heap_create_with_catalog(const char *relname,
 							   mapped_relation,
 							   allow_system_table_mods,
 							   &relfrozenxid,
-							   &relminmxid);
+							   &relminmxid,
+							   false);
 
 	Assert(relid == RelationGetRelid(new_rel_desc));
 
@@ -1397,6 +1417,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1976,6 +1997,19 @@ heap_drop_with_catalog(Oid relid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
 	/*
+	 * Global temporary table is allowed to be dropped only when the
+	 * current session is using it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot drop global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
+	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
 	if (RELKIND_HAS_STORAGE(rel->rd_rel->relkind))
@@ -3238,7 +3272,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3250,7 +3284,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3296,8 +3330,16 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		/*
+		 * Truncate global temporary table only clears local data,
+		 * so only low-level locks need to be held.
+		 */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3330,6 +3372,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3338,23 +3381,47 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	/* For global temporary table only */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		/*
+		 * If this GTT is not initialized in current backend, there is
+		 * no needs to anything.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(rel)))
+			return;
+
+		/*
+		 * Truncate GTT only clears local data, so only low-level locks
+		 * need to be held.
+		 */
+		lockmode = RowExclusiveLock;
+	}
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	/*
+	 * After the data is cleaned up on the GTT, the transaction information
+	 * for the data(stored in local hash table) is also need reset.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(RelationGetRelid(rel), 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 731610c..463f5a3 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -54,6 +54,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "commands/defrem.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -720,6 +721,29 @@ index_create(Relation heapRelation,
 	char		relkind;
 	TransactionId relfrozenxid;
 	MultiXactId relminmxid;
+	bool		skip_create_storage = false;
+
+	/* For global temporary table only */
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temporary table with concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot reindex global temporary tables concurrently")));
+
+		/*
+		 * For the case that some backend is applied relcache message to create
+		 * an index on a global temporary table, if this table in the current
+		 * backend are not initialized, the creation of index storage on the
+		 * table are also skipped.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+		{
+			skip_create_storage = true;
+			flags |= INDEX_CREATE_SKIP_BUILD;
+		}
+	}
 
 	/* constraint flags can only be set when a constraint is requested */
 	Assert((constr_flags == 0) ||
@@ -927,7 +951,8 @@ index_create(Relation heapRelation,
 								mapped_relation,
 								allow_system_table_mods,
 								&relfrozenxid,
-								&relminmxid);
+								&relminmxid,
+								skip_create_storage);
 
 	Assert(relfrozenxid == InvalidTransactionId);
 	Assert(relminmxid == InvalidMultiXactId);
@@ -2200,7 +2225,7 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 * lock (see comments in RemoveRelations), and a non-concurrent DROP is
 	 * more efficient.
 	 */
-	Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+	Assert(!RelpersistenceTsTemp(get_rel_persistence(indexId)) ||
 		   (!concurrent && !concurrent_lock_mode));
 
 	/*
@@ -2233,6 +2258,20 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
 	/*
+	 * Allow to drop index on global temporary table when only current
+	 * backend use it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation) &&
+		is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+	{
+		ereport(ERROR,
+			(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+			 errmsg("cannot drop index %s or global temporary table %s",
+					RelationGetRelationName(userIndexRelation), RelationGetRelationName(userHeapRelation)),
+			 errhint("Because the index is created on the global temporary table and other backend attached it.")));
+	}
+
+	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
 	 *
@@ -2840,6 +2879,7 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = RELATION_IS_GLOBAL_TEMP(rel);
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2934,20 +2974,37 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
-		{
-			rd_rel->relpages = (int32) relpages;
-			dirty = true;
-		}
-		if (rd_rel->reltuples != (float4) reltuples)
+		/* For global temporary table */
+		if (is_gtt)
 		{
-			rd_rel->reltuples = (float4) reltuples;
-			dirty = true;
+			/* Update GTT'statistics into local relcache */
+			rel->rd_rel->relpages = (int32) relpages;
+			rel->rd_rel->reltuples = (float4) reltuples;
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+
+			/* Update GTT'statistics into local hashtable */
+			up_gtt_relstats(RelationGetRelid(rel), relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+		else
 		{
-			rd_rel->relallvisible = (int32) relallvisible;
-			dirty = true;
+			if (rd_rel->relpages != (int32) relpages)
+			{
+				rd_rel->relpages = (int32) relpages;
+				dirty = true;
+			}
+
+			if (rd_rel->reltuples != (float4) reltuples)
+			{
+				rd_rel->reltuples = (float4) reltuples;
+				dirty = true;
+			}
+
+			if (rd_rel->relallvisible != (int32) relallvisible)
+			{
+				rd_rel->relallvisible = (int32) relallvisible;
+				dirty = true;
+			}
 		}
 	}
 
@@ -3062,6 +3119,26 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, index, val);
 	}
 
+	/* For build index on global temporary table */
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		/*
+		 * If the storage for the index in this session is not initialized,
+		 * it needs to be created.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			/* Before create init storage, fix the local Relcache first */
+			gtt_force_enable_index(indexRelation);
+
+			Assert(gtt_storage_attached(RelationGetRelid(heapRelation)));
+
+			/* Init storage for index */
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3616,6 +3693,20 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	if (!OidIsValid(heapId))
 		return;
 
+	/*
+	 * For reindex on global temporary table, If the storage for the index
+	 * in current backend is not initialized, nothing is done.
+	 */
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		!gtt_storage_attached(indexId))
+	{
+		/* Suppress use of the target index while rebuilding it */
+		SetReindexProcessing(heapId, indexId);
+		/* Re-allow use of target index */
+		ResetReindexProcessing();
+		return;
+	}
+
 	if ((options & REINDEXOPT_MISSING_OK) != 0)
 		heapRelation = try_table_open(heapId, ShareLock);
 	else
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 740570c..bcd24d9 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -655,6 +655,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/* Do not allow create global temporary table in temporary schemas */
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp table in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index d538f25..dbc84e8 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,6 +27,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -61,6 +62,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			temprelOid;			/* InvalidOid if not a global temporary rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -115,7 +117,7 @@ AddPendingSync(const RelFileNode *rnode)
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -126,7 +128,12 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/*
+		 * Global temporary table and local temporary table use same
+		 * design on storage module.
+		 */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -154,6 +161,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
@@ -165,6 +173,21 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		AddPendingSync(&rnode);
 	}
 
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		Assert(rel && RELATION_IS_GLOBAL_TEMP(rel));
+
+		/*
+		 * Remember the reloid of global temporary table, which is used for
+		 * transaction commit or rollback.
+		 * see smgrDoPendingDeletes.
+		 */
+		pending->temprelOid = RelationGetRelid(rel);
+
+		/* Remember global temporary table storage info to localhash */
+		remember_gtt_storage_info(rnode, rel);
+	}
+
 	return srel;
 }
 
@@ -201,12 +224,21 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
 	/*
+	 * Remember the reloid of global temporary table, which is used for
+	 * transaction commit or rollback.
+	 * see smgrDoPendingDeletes.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->temprelOid = RelationGetRelid(rel);
+
+	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
 	 * once with atCommit false.  Hence, it will be physically deleted at end
@@ -602,6 +634,7 @@ smgrDoPendingDeletes(bool isCommit)
 	int			nrels = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -631,14 +664,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->temprelOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -648,12 +685,21 @@ smgrDoPendingDeletes(bool isCommit)
 
 	if (nrels > 0)
 	{
+		int	i;
+
 		smgrdounlinkall(srels, nrels, false);
 
-		for (int i = 0; i < nrels; i++)
+		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* Delete global temporary table info in localhash */
+			if (gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..0e6563d
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1507 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  The body implementation of Global Temparary table.
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *	  See src/backend/catalog/GTT_README for Global temparary table's
+ *	  requirements and design.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_relstats_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+int		vacuum_gtt_defer_check_age = 0;
+
+typedef struct gtt_ctl_data
+{
+	LWLock			lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+	Oid			relfilenode;
+	Oid			spcnode;
+
+	/* pg_class stat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+
+	/* pg_statistic */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+
+	Oid			oldrelid;			/* remember the source of relid, before the switch relfilenode. */
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_reset_statistics(gtt_local_hash_entry *entry);
+static void gtt_free_statistics(gtt_local_hash_entry *entry);
+static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok);
+static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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();
+	}
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(gtt_fnode);
+	info.entrysize = action_gtt_shared_hash_entry_size();
+	active_gtt_shared_hash =
+		ShmemInitHash("active gtt shared hash",
+						gtt_shared_ctl->max_entry,
+						gtt_shared_ctl->max_entry,
+						&info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			found;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *)&(fnode), 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_global_temporary_table.")));
+	}
+
+	if (found == false)
+	{
+		int			wordnum;
+
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode				fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	if (!skiplock)
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		if (!skiplock)
+			LWLockRelease(&gtt_shared_ctl->lock);
+
+		if (isCommit)
+			elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+	bms_del_member(entry->map, MyBackendId);
+
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+
+	if (!skiplock)
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+	return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	Bitmapset	*map_copy = NULL;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool		in_use = false;
+	int			num_use = 0;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), 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;
+	gtt_relfilenode			*new_node = NULL;
+	Oid						relid = RelationGetRelid(rel);
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		/* First time through: initialize the hash table */
+		HASHCTL		ctl;
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_relstats_context =
+			AllocSetContextCreate(CacheMemoryContext,
+							"gtt relstats context",
+							ALLOCSET_DEFAULT_SIZES);
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+	entry = gtt_search_by_relid(relid, true);
+	if (!entry)
+	{
+		bool		found = false;
+		int			natts = 0;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash,
+						(void *) &relid, HASH_ENTER, &found);
+
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+		entry->natts = 0;
+		entry->attnum = NULL;
+		entry->att_stat_tups = NULL;
+		entry->oldrelid = InvalidOid;
+
+		natts = RelationGetNumberOfAttributes(rel);
+		entry->attnum = palloc0(sizeof(int) * natts);
+		entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+		entry->natts = natts;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			gtt_storage_checkin(relid);
+		}
+	}
+
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* only heap contain transaction information */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	gtt_reset_statistics(entry);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	gtt_relfilenode *d_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+		return;
+	}
+
+	d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+		else if (entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+			gtt_relfilenode *gttnode2 = NULL;
+
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+			Assert(gttnode2->relfilenode == rnode.relNode);
+			Assert(list_length(entry->relfilenode_list) == 1);
+			/* rollback switch relfilenode */
+			gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+									   entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+									   false);
+			/* clean up footprint */
+			entry2->oldrelid = InvalidOid;
+			d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+			Assert(d_rnode);
+		}
+		else
+		{
+			if (entry->relfilenode_list == NIL)
+			{
+				if (entry->relkind == RELKIND_RELATION ||
+					entry->relkind == RELKIND_SEQUENCE)
+					gtt_storage_checkout(relid, false, isCommit);
+
+				gtt_free_statistics(entry);
+				hash_search(gtt_storage_local_hash,
+						(void *) &(relid), HASH_REMOVE, NULL);
+			}
+
+			return;
+		}
+	}
+
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	pfree(d_rnode);
+	if (entry->relfilenode_list == NIL)
+	{
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(relid, false, isCommit);
+
+		if (isCommit && entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			/* clean up footprint */
+			entry2->oldrelid = InvalidOid;
+		}
+
+		gtt_free_statistics(entry);
+		hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_REMOVE, NULL);
+	}
+	else
+		gtt_reset_statistics(entry);
+
+	return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool found = false;
+	gtt_local_hash_entry *entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (!OidIsValid(relid))
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry)
+		found = true;
+
+	return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS				status;
+	gtt_local_hash_entry		*entry;
+	SMgrRelation	*srels = NULL;
+	Oid				*relids = NULL;
+	char			*relkinds = NULL;
+	int			nrels = 0,
+				nfiles = 0,
+				maxrels = 0,
+				maxfiles = 0,
+				i = 0;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel;
+			RelFileNode		rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel = smgropen(rnode, MyBackendId);
+
+			if (maxfiles == 0)
+			{
+				maxfiles = 32;
+				srels = palloc(sizeof(SMgrRelation) * maxfiles);
+			}
+			else if (maxfiles <= nfiles)
+			{
+				maxfiles *= 2;
+				srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+			}
+
+			srels[nfiles++] = srel;
+		}
+
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			relids  = palloc(sizeof(Oid) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			relids  = repalloc(relids , sizeof(Oid) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		relkinds[nrels] = entry->relkind;
+		relids[nrels] = entry->relid;
+		nrels++;
+	}
+
+	if (nfiles > 0)
+	{
+		smgrdounlinkall(srels, nfiles, false);
+		for (i = 0; i < nfiles; i++)
+			smgrclose(srels[i]);
+
+		pfree(srels);
+	}
+
+	if (nrels)
+	{
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+		for (i = 0; i < nrels; i++)
+		{
+			if (relkinds[i] == RELKIND_RELATION ||
+				relkinds[i] == RELKIND_SEQUENCE)
+				gtt_storage_checkout(relids[i], true, false);
+		}
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+		pfree(relids);
+		pfree(relkinds);
+	}
+
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;
+
+	return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible)
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Oid relid,
+					BlockNumber num_pages,
+					double num_tuples,
+					BlockNumber num_all_visible_pages,
+					TransactionId relfrozenxid,
+					TransactionId relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (!OidIsValid(relid))
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (num_pages > 0 &&
+		gtt_rnode->relpages != (int32)num_pages)
+		gtt_rnode->relpages = (int32)num_pages;
+
+	if (num_tuples > 0 &&
+		gtt_rnode->reltuples != (float4)num_tuples)
+		gtt_rnode->reltuples = (float4)num_tuples;
+
+	/* only heap contain transaction information and relallvisible */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (num_all_visible_pages > 0 &&
+			gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+		{
+			gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+		}
+
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNewTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible)
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	MemoryContext			oldcontext;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return;
+
+	if (entry->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	Assert(entry->relid == reloid);
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == 0)
+		{
+			entry->attnum[i] = attnum;
+			break;
+		}
+		else if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < entry->natts);
+	Assert(entry->att_stat_tups[i] == NULL);
+	entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return NULL;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			return entry->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int			i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->backend_gtt_frozenxid != gtt_frozenxid)
+		MyProc->backend_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	HeapTuple	tuple;
+	Relation	rel = NULL;
+	int		attnum = PG_GETARG_INT32(1);
+	Oid		reloid = PG_GETARG_OID(0);
+	char		rel_persistence;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	  tupdesc;
+	MemoryContext oldcontext;
+	Tuplestorestate *tupstore;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+
+	relation_close(rel, NoLock);
+	relation_close(pg_tatistic, AccessShareLock);
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate	*tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Oid			relnode = 0;
+	Relation	rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[6];
+		bool	isnull[6];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid		reloid = PG_GETARG_OID(0);
+	char	rel_persistence;
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(reloid);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_backend_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+void
+gtt_force_enable_index(Relation index)
+{
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(RelationGetRelid(index)));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+/*
+ * Fix the state of the global temporary table's index.
+ */
+void
+gtt_fix_index_backend_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid heapOid = index->rd_index->indrelid;
+
+	/* Must be global temporary table */
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	/*
+	 * If this global temporary table is not initialized in the current Backend,
+	 * its index status is temporarily set to invalid(local relcache).
+	 */
+	if (gtt_storage_attached(heapOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int			i;
+	Oid			toastrelid;
+
+	if (!(operation == CMD_UTILITY || operation == CMD_INSERT))
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+
+		index_build(relation, index, info, true, false);
+	}
+
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid			indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+static void
+gtt_reset_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+
+		entry->attnum[i] = 0;
+	}
+
+	return;
+}
+
+static void
+gtt_free_statistics(gtt_local_hash_entry *entry)
+{
+	int i;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->att_stat_tups[i])
+		{
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (entry->attnum)
+		pfree(entry->attnum);
+
+	if (entry->att_stat_tups)
+		pfree(entry->att_stat_tups);
+
+	return;
+}
+
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
+void
+gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint)
+{
+	gtt_local_hash_entry	*entry1;
+	gtt_local_hash_entry	*entry2;
+	gtt_relfilenode			*gtt_rnode1 = NULL;
+	gtt_relfilenode			*gtt_rnode2 = NULL;
+	MemoryContext			oldcontext;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry1 = gtt_search_by_relid(rel1, false);
+	gtt_rnode1 = gtt_search_relfilenode(entry1, relfilenode1, false);
+
+	entry2 = gtt_search_by_relid(rel2, false);
+	gtt_rnode2 = gtt_search_relfilenode(entry2, relfilenode2, false);
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	entry1->relfilenode_list = list_delete_ptr(entry1->relfilenode_list, gtt_rnode1);
+	entry2->relfilenode_list = lappend(entry2->relfilenode_list, gtt_rnode1);
+
+	entry2->relfilenode_list = list_delete_ptr(entry2->relfilenode_list, gtt_rnode2);
+	entry1->relfilenode_list = lappend(entry1->relfilenode_list, gtt_rnode2);
+	MemoryContextSwitchTo(oldcontext);
+
+	if (footprint)
+	{
+		entry1->oldrelid = rel2;
+		entry2->oldrelid = rel1;
+	}
+
+	return;
+}
+
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok)
+{
+	gtt_relfilenode			*rnode = NULL;
+	ListCell				*lc;
+
+	Assert(entry);
+
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == relfilenode)
+		{
+			rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (!missing_ok && rnode == NULL)
+		elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+	return rnode;
+}
+
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry == NULL && !missing_ok)
+		elog(ERROR, "relid %u not found in local hash", relid);
+
+	return entry;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2e4aa1c..6018192 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 8af12b5..a0b6390 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -185,6 +186,17 @@ analyze_rel(Oid relid, RangeVar *relation,
 	}
 
 	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
+	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
 	if (RelationGetRelid(onerel) == StatisticRelationId)
@@ -575,14 +587,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 		 * pg_statistic for columns we didn't process, we leave them alone.)
 		 */
 		update_attstats(RelationGetRelid(onerel), inh,
-						attr_cnt, vacattrstats);
+						attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
 
 		for (ind = 0; ind < nindexes; ind++)
 		{
 			AnlIndexData *thisdata = &indexdata[ind];
 
 			update_attstats(RelationGetRelid(Irel[ind]), false,
-							thisdata->attr_cnt, thisdata->vacattrstats);
+							thisdata->attr_cnt, thisdata->vacattrstats,
+							RelationGetRelPersistence(Irel[ind]));
 		}
 
 		/*
@@ -1445,7 +1458,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
  *		by taking a self-exclusive lock on the relation in analyze_rel().
  */
 static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
 {
 	Relation	sd;
 	int			attno;
@@ -1547,31 +1560,48 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/*
+		 * For global temporary table,
+		 * Update column statistic to localhash, not catalog.
+		 */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 04d12a7..f83c1d9 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/toasting.h"
 #include "commands/cluster.h"
 #include "commands/progress.h"
@@ -72,6 +73,12 @@ static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
 							bool verbose, bool *pSwapToastByContent,
 							TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
 static List *get_tables_to_cluster(MemoryContext cluster_context);
+static void gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables);
 
 
 /*---------------------------------------------------------------------------
@@ -367,6 +374,18 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
 	}
 
 	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap) &&
+		!gtt_storage_attached(RelationGetRelid(OldHeap)))
+	{
+		relation_close(OldHeap, AccessExclusiveLock);
+		pgstat_progress_end_command();
+		return;
+	}
+
+	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
 	 */
@@ -750,6 +769,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	BlockNumber num_pages;
 	int			elevel = verbose ? INFO : DEBUG2;
 	PGRUsage	ru0;
+	bool		is_gtt = false;
+	uint32		gtt_relfrozenxid = 0;
+	uint32		gtt_relminmxid = 0;
 
 	pg_rusage_init(&ru0);
 
@@ -763,6 +785,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	else
 		OldIndex = NULL;
 
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		is_gtt = true;
+
 	/*
 	 * Their tuple descriptors should be exactly alike, but here we only need
 	 * assume that they have the same number of columns.
@@ -830,20 +855,38 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 						  &OldestXmin, &FreezeXid, NULL, &MultiXactCutoff,
 						  NULL);
 
-	/*
-	 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
-	 * backwards, so take the max.
-	 */
-	if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
-		TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
-		FreezeXid = OldHeap->rd_rel->relfrozenxid;
+	if (is_gtt)
+	{
+		/* Gets transaction information for global temporary table from localhash. */
+		get_gtt_relstats(OIDOldHeap,
+					NULL, NULL, NULL,
+					&gtt_relfrozenxid, &gtt_relminmxid);
+
+		if (TransactionIdIsValid(gtt_relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, gtt_relfrozenxid))
+			FreezeXid = gtt_relfrozenxid;
+
+		if (MultiXactIdIsValid(gtt_relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, gtt_relminmxid))
+			MultiXactCutoff = gtt_relminmxid;
+	}
+	else
+	{
+		/*
+		 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
+		 * backwards, so take the max.
+		 */
+		if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
+			FreezeXid = OldHeap->rd_rel->relfrozenxid;
 
-	/*
-	 * MultiXactCutoff, similarly, shouldn't go backwards either.
-	 */
-	if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
-		MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
-		MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+		/*
+		 * MultiXactCutoff, similarly, shouldn't go backwards either.
+		 */
+		if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
+			MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+	}
 
 	/*
 	 * Decide whether to use an indexscan or seqscan-and-optional-sort to scan
@@ -911,6 +954,15 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	table_close(OldHeap, NoLock);
 	table_close(NewHeap, NoLock);
 
+	/* Update relstats of global temporary table to localhash. */
+	if (is_gtt)
+	{
+		up_gtt_relstats(RelationGetRelid(NewHeap), num_pages, num_tuples, 0,
+						InvalidTransactionId, InvalidMultiXactId);
+		CommandCounterIncrement();
+		return;
+	}
+
 	/* Update pg_class to reflect the correct values of pages and tuples. */
 	relRelation = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1346,10 +1398,22 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 	 * Swap the contents of the heap relations (including any toast tables).
 	 * Also set old heap's relfrozenxid to frozenXid.
 	 */
-	swap_relation_files(OIDOldHeap, OIDNewHeap,
+	if (newrelpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		Assert(!is_system_catalog);
+		/* For global temporary table modify data in localhash, not pg_class */
+		gtt_swap_relation_files(OIDOldHeap, OIDNewHeap,
+								(OIDOldHeap == RelationRelationId),
+								swap_toast_by_content, is_internal,
+								frozenXid, cutoffMulti, mapped_tables);
+	}
+	else
+	{
+		swap_relation_files(OIDOldHeap, OIDNewHeap,
 						(OIDOldHeap == RelationRelationId),
 						swap_toast_by_content, is_internal,
 						frozenXid, cutoffMulti, mapped_tables);
+	}
 
 	/*
 	 * If it's a system catalog, queue a sinval message to flush all catcaches
@@ -1557,3 +1621,146 @@ get_tables_to_cluster(MemoryContext cluster_context)
 
 	return rvs;
 }
+
+/*
+ * For global temporary table, storage information is stored in localhash,
+ * This function like swap_relation_files except that update storage information,
+ * in the localhash, not pg_class.
+ */
+static void
+gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables)
+{
+	Relation	relRelation;
+	Oid			relfilenode1,
+				relfilenode2;
+	Relation	rel1;
+	Relation	rel2;
+
+	relRelation = table_open(RelationRelationId, RowExclusiveLock);
+
+	rel1 = relation_open(r1, AccessExclusiveLock);
+	rel2 = relation_open(r2, AccessExclusiveLock);
+
+	relfilenode1 = gtt_fetch_current_relfilenode(r1);
+	relfilenode2 = gtt_fetch_current_relfilenode(r2);
+
+	Assert(OidIsValid(relfilenode1) && OidIsValid(relfilenode2));
+	gtt_switch_rel_relfilenode(r1, relfilenode1, r2, relfilenode2, true);
+
+	CacheInvalidateRelcache(rel1);
+	CacheInvalidateRelcache(rel2);
+
+	InvokeObjectPostAlterHookArg(RelationRelationId, r1, 0,
+								 InvalidOid, is_internal);
+	InvokeObjectPostAlterHookArg(RelationRelationId, r2, 0,
+								 InvalidOid, true);
+
+	if (rel1->rd_rel->reltoastrelid || rel2->rd_rel->reltoastrelid)
+	{
+		if (swap_toast_by_content)
+		{
+			if (rel1->rd_rel->reltoastrelid && rel2->rd_rel->reltoastrelid)
+			{
+				gtt_swap_relation_files(rel1->rd_rel->reltoastrelid,
+									rel2->rd_rel->reltoastrelid,
+									target_is_pg_class,
+									swap_toast_by_content,
+									is_internal,
+									frozenXid,
+									cutoffMulti,
+									mapped_tables);
+			}
+			else
+				elog(ERROR, "cannot swap toast files by content when there's only one");
+		}
+		else
+		{
+			ObjectAddress baseobject,
+						toastobject;
+			long		count;
+
+			if (IsSystemRelation(rel1))
+				elog(ERROR, "cannot swap toast files by links for system catalogs");
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel1->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel2->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+
+			/* Register new dependencies */
+			baseobject.classId = RelationRelationId;
+			baseobject.objectSubId = 0;
+			toastobject.classId = RelationRelationId;
+			toastobject.objectSubId = 0;
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r1;
+				toastobject.objectId = rel1->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r2;
+				toastobject.objectId = rel2->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+		}
+	}
+
+	if (swap_toast_by_content &&
+		rel1->rd_rel->relkind == RELKIND_TOASTVALUE &&
+		rel2->rd_rel->relkind == RELKIND_TOASTVALUE)
+	{
+		Oid			toastIndex1,
+					toastIndex2;
+
+		/* Get valid index for each relation */
+		toastIndex1 = toast_get_valid_index(r1,
+											AccessExclusiveLock);
+		toastIndex2 = toast_get_valid_index(r2,
+											AccessExclusiveLock);
+
+		gtt_swap_relation_files(toastIndex1,
+							toastIndex2,
+							target_is_pg_class,
+							swap_toast_by_content,
+							is_internal,
+							InvalidTransactionId,
+							InvalidMultiXactId,
+							mapped_tables);
+	}
+
+	relation_close(rel1, NoLock);
+	relation_close(rel2, NoLock);
+
+	table_close(relRelation, RowExclusiveLock);
+
+	RelationCloseSmgrByOid(r1);
+	RelationCloseSmgrByOid(r2);
+
+	CommandCounterIncrement();
+}
+
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index b6143b8..e8da86c 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -290,7 +290,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP_ON_CURRENT_SESSION(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, whereClause,
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 1b14e9a..5e918f2 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -23,6 +23,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "access/xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/copyfrom_internal.h"
 #include "commands/trigger.h"
@@ -655,6 +656,9 @@ CopyFrom(CopyFromState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	/* Check and init global temporary table storage in current backend */
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	/*
 	 * Set up a ModifyTableState so we can let FDW(s) init themselves for
 	 * foreign-table result relation(s).
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 02c7a0c..72a27f6 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -554,7 +554,7 @@ DefineIndex(Oid relationId,
 	 * is more efficient.  Do this before any use of the concurrent option is
 	 * done.
 	 */
-	if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+	if (stmt->concurrent && !RelpersistenceTsTemp(get_rel_persistence(relationId)))
 		concurrent = true;
 	else
 		concurrent = false;
@@ -2467,7 +2467,7 @@ ReindexIndex(RangeVar *indexRelation, int options, bool isTopLevel)
 	if (relkind == RELKIND_PARTITIONED_INDEX)
 		ReindexPartitions(indOid, options, isTopLevel);
 	else if ((options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 persistence != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(persistence))
 		ReindexRelationConcurrently(indOid, options);
 	else
 		reindex_index(indOid, false, persistence,
@@ -2572,7 +2572,7 @@ ReindexTable(RangeVar *relation, int options, bool isTopLevel)
 	if (get_rel_relkind(heapOid) == RELKIND_PARTITIONED_TABLE)
 		ReindexPartitions(heapOid, options, isTopLevel);
 	else if ((options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(get_rel_persistence(heapOid)))
 	{
 		result = ReindexRelationConcurrently(heapOid, options);
 
@@ -2931,7 +2931,7 @@ ReindexMultipleInternal(List *relids, int options)
 			   relkind != RELKIND_PARTITIONED_TABLE);
 
 		if ((options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			relpersistence != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(relpersistence))
 		{
 			(void) ReindexRelationConcurrently(relid,
 											   options |
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 0982276..76355de 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -57,7 +57,10 @@ LockTableCommand(LockStmt *lockstmt)
 										  RangeVarCallbackForLockTable,
 										  (void *) &lockstmt->mode);
 
-		if (get_rel_relkind(reloid) == RELKIND_VIEW)
+		/* Lock table command ignores global temporary table. */
+		if (get_rel_persistence(reloid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+		else if (get_rel_relkind(reloid) == RELKIND_VIEW)
 			LockViewRecurse(reloid, lockstmt->mode, lockstmt->nowait, NIL);
 		else if (recurse)
 			LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait);
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 632b34a..2e6c570 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -275,8 +278,6 @@ ResetSequence(Oid seq_relid)
 	Buffer		buf;
 	HeapTupleData seqdatatuple;
 	HeapTuple	tuple;
-	HeapTuple	pgstuple;
-	Form_pg_sequence pgsform;
 	int64		startv;
 
 	/*
@@ -287,12 +288,7 @@ ResetSequence(Oid seq_relid)
 	init_sequence(seq_relid, &elm, &seq_rel);
 	(void) read_seq_tuple(seq_rel, &buf, &seqdatatuple);
 
-	pgstuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seq_relid));
-	if (!HeapTupleIsValid(pgstuple))
-		elog(ERROR, "cache lookup failed for sequence %u", seq_relid);
-	pgsform = (Form_pg_sequence) GETSTRUCT(pgstuple);
-	startv = pgsform->seqstart;
-	ReleaseSysCache(pgstuple);
+	startv = get_seqence_start_value(seq_relid);
 
 	/*
 	 * Copy the existing sequence tuple.
@@ -451,6 +447,15 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 
 	init_sequence(relid, &elm, &seqrel);
 
+	if (RELATION_IS_GLOBAL_TEMP(seqrel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(seqrel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary sequence %s when other backend attached it.",
+						RelationGetRelationName(seqrel))));
+	}
+
 	rel = table_open(SequenceRelationId, RowExclusiveLock);
 	seqtuple = SearchSysCacheCopy1(SEQRELID,
 								   ObjectIdGetDatum(relid));
@@ -611,7 +616,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +941,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1154,6 +1159,14 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	/* Initializes the storage for sequence which the global temporary table belongs. */
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1954,3 +1967,51 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+/*
+ * Get the startValue of the sequence from syscache.
+ */
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+/*
+ * Initialize sequence which global temporary table belongs.
+ */
+void
+gtt_init_seq(Relation rel)
+{
+	Datum		value[SEQ_COL_LASTCOL] = {0};
+	bool		null[SEQ_COL_LASTCOL] = {false};
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 46f1637..1e14939 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -46,6 +46,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -560,6 +561,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
 static void ATExecAlterCollationRefreshVersion(Relation rel, List *coll);
+static OnCommitAction gtt_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -605,6 +607,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -616,7 +619,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * Check consistency of arguments
 	 */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !RelpersistenceTsTemp(stmt->relation->relpersistence))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -646,7 +649,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	if (RelpersistenceTsTemp(stmt->relation->relpersistence)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -747,6 +750,56 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* For global temporary table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* Check parent table */
+		if (inheritOids)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			table_close(relation, NoLock);
+		}
+
+		/* Check oncommit clause and save to reloptions */
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temporary table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1368,7 +1421,7 @@ RemoveRelations(DropStmt *drop)
 		 * relation persistence cannot be known without its OID.
 		 */
 		if (drop->concurrent &&
-			get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(get_rel_persistence(relOid)))
 		{
 			Assert(list_length(drop->objects) == 1 &&
 				   drop->removeType == OBJECT_INDEX);
@@ -1574,7 +1627,16 @@ ExecuteTruncate(TruncateStmt *stmt)
 		Relation	rel;
 		bool		recurse = rv->inh;
 		Oid			myrelid;
-		LOCKMODE	lockmode = AccessExclusiveLock;
+		LOCKMODE	lockmode;
+
+		/*
+		 * Truncate global temp table only cleans up the data in current backend,
+		 * only low-level locks are required.
+		 */
+		if (rv->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+		else
+			lockmode = AccessExclusiveLock;
 
 		myrelid = RangeVarGetRelidExtended(rv, lockmode,
 										   0, RangeVarCallbackForTruncate,
@@ -1839,6 +1901,14 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
 			continue;
 
 		/*
+		 * Skip the global temporary table that is not initialized for storage
+		 * in current backend.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			continue;
+
+		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
 		 * a new relfilenode in the current (sub)transaction, then we can just
@@ -3682,6 +3752,16 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temporary table only current backend use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -5005,6 +5085,42 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot rewrite temporary tables of other sessions")));
 
+			if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+			{
+				if (tab->chgPersistence)
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot change global temporary table persistence setting")));
+
+				/*
+				 * The storage for the global temporary table needs to be initialized
+				 * before rewrite table.
+				 */
+				if(!gtt_storage_attached(tab->relid))
+				{
+					ResultRelInfo *resultRelInfo;
+					MemoryContext oldcontext;
+					MemoryContext ctx_alter_gtt;
+
+					ctx_alter_gtt = AllocSetContextCreate(CurrentMemoryContext,
+											"gtt alter table", ALLOCSET_DEFAULT_SIZES);
+					oldcontext = MemoryContextSwitchTo(ctx_alter_gtt);
+
+					resultRelInfo = makeNode(ResultRelInfo);
+					InitResultRelInfo(resultRelInfo, OldHeap,
+									1, NULL, 0);
+					if (resultRelInfo->ri_RelationDesc->rd_rel->relhasindex &&
+						resultRelInfo->ri_IndexRelationDescs == NULL)
+						ExecOpenIndices(resultRelInfo, false);
+
+					init_gtt_storage(CMD_UTILITY, resultRelInfo);
+					ExecCloseIndices(resultRelInfo);
+
+					MemoryContextSwitchTo(oldcontext);
+					MemoryContextDelete(ctx_alter_gtt);
+				}
+			}
+
 			/*
 			 * Select destination tablespace (same as original unless user
 			 * requested a change)
@@ -8469,6 +8585,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -12972,6 +13094,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -13174,6 +13299,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temporary table");
+
 	/*
 	 * No work if no change in tablespace.
 	 */
@@ -13548,7 +13676,7 @@ index_copy_data(Relation rel, RelFileNode newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
@@ -14956,6 +15084,7 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
 	switch (rel->rd_rel->relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -17632,3 +17761,40 @@ ATExecAlterCollationRefreshVersion(Relation rel, List *coll)
 	index_update_collation_versions(rel->rd_id, get_collation_oid(coll, false));
 	CacheInvalidateRelcache(rel);
 }
+
+/*
+ * Parse the on commit clause for the temporary table
+ */
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			bool	res = false;
+			char	*sval = defGetString(def);
+
+			/* It has to be a Boolean value */
+			if (!parse_bool(sval, &res))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
+
+			if (res)
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 395e75f..693f801 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/vacuum.h"
@@ -1233,6 +1234,22 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = RELATION_IS_GLOBAL_TEMP(relation);
+
+	 /* For global temporary table */
+	if (is_gtt)
+	{
+		/* Store relation statistics and transaction information to the localhash */
+		up_gtt_relstats(RelationGetRelid(relation),
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+
+		/* Update relation statistics to local relcache */
+		relation->rd_rel->relpages = (int32) num_pages;
+		relation->rd_rel->reltuples = (float4) num_tuples;
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1246,17 +1263,23 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (!is_gtt &&
+		pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (!is_gtt &&
+		pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (!is_gtt &&
+		pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1301,7 +1324,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1312,7 +1336,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1420,6 +1445,13 @@ vac_update_datfrozenxid(void)
 		}
 
 		/*
+		 * The relfrozenxid for a global temporary talble is stored in localhash,
+		 * not pg_class, See list_all_session_gtt_frozenxids()
+		 */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
+		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
 		 * to not be set (i.e. set to their respective Invalid*Id)
@@ -1476,6 +1508,43 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/* If enable global temporary table */
+	if (max_active_gtt > 0)
+	{
+		TransactionId	safe_age;
+		/*  */
+		TransactionId	oldest_gtt_frozenxid =
+			list_all_backend_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid))
+		{
+			safe_age = oldest_gtt_frozenxid + vacuum_gtt_defer_check_age;
+			if (safe_age < FirstNormalTransactionId)
+				safe_age += FirstNormalTransactionId;
+
+			/*
+			 * We tolerate that the minimum age of gtt is less than
+			 * the minimum age of conventional tables, otherwise it will
+			 * throw warning message.
+			 */
+			if (TransactionIdIsNormal(safe_age) &&
+				TransactionIdPrecedes(safe_age, newFrozenXid))
+			{
+				ereport(WARNING,
+					(errmsg("global temp table oldest relfrozenxid %u is the oldest in the entire db",
+							oldest_gtt_frozenxid),
+					 errdetail("The oldest relfrozenxid in pg_class is %u", newFrozenXid),
+					 errhint("If they differ greatly, please consider cleaning up the data in global temp table.")));
+			}
+
+			/*
+			 * We need to ensure that the clog required by gtt is not cleand.
+			 */
+			if (TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+				newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1811,6 +1880,19 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 	}
 
 	/*
+	 * Skip those global temporary table that are not initialized in
+	 * current backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
+	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
 	 * contents are probably not up-to-date on disk.  (We don't throw a
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6e65103..2f46140 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -530,6 +530,12 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	/* Global temporary table are not sensible. */
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 7179f58..c53db5b 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,10 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		/* This is one kind of temp table */
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 86594bd..3e4bb7e 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
@@ -606,6 +607,9 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	/* Init storage for partitioned global temporary table in current backend */
+	init_gtt_storage(mtstate->operation, leaf_part_rri);
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index e0f2428..d998df5 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,6 +42,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -2286,6 +2287,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			ExecOpenIndices(resultRelInfo,
 							node->onConflictAction != ONCONFLICT_NONE);
 
+		/* Init storage for global temporary table in current backend */
+		init_gtt_storage(operation, resultRelInfo);
+
 		/*
 		 * If this is an UPDATE and a BEFORE UPDATE trigger is present, the
 		 * trigger itself might modify the partition-key values. So arrange
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 84a69b0..e6b4b0a 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -48,7 +48,7 @@
 #include "partitioning/partprune.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/lsyscache.h"
-
+#include "utils/rel.h"
 
 /* results of subquery_is_pushdown_safe */
 typedef struct pushdown_safety_info
@@ -623,7 +623,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			if (RelpersistenceTsTemp(get_rel_persistence(rte->relid)))
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 247f7d4..57a473d 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6427,7 +6427,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
-	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+	if (RELATION_IS_TEMP(heap) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 3e94256..a32ab49 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -29,6 +29,7 @@
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
 #include "catalog/index.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
@@ -230,6 +231,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temporary table in current backend */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 084e00f..606ce15 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2546,6 +2546,11 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("materialized views must not use temporary tables or views")));
 
+		if (is_query_using_gtt(query))
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("materialized views must not use global temporary tables or views")));
+
 		/*
 		 * A materialized view would either need to save parameters for use in
 		 * maintaining/loading the data or prohibit them entirely.  The latter
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index efc9c99..10f2d97 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3319,17 +3319,11 @@ OptTemp:	TEMPORARY					{ $$ = RELPERSISTENCE_TEMP; }
 			| LOCAL TEMP				{ $$ = RELPERSISTENCE_TEMP; }
 			| GLOBAL TEMPORARY
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
-					$$ = RELPERSISTENCE_TEMP;
+					$$ = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
-					$$ = RELPERSISTENCE_TEMP;
+					$$ = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED					{ $$ = RELPERSISTENCE_UNLOGGED; }
 			| /*EMPTY*/					{ $$ = RELPERSISTENCE_PERMANENT; }
@@ -11437,19 +11431,13 @@ OptTempTableName:
 				}
 			| GLOBAL TEMPORARY opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| GLOBAL TEMP opt_table qualified_name
 				{
-					ereport(WARNING,
-							(errmsg("GLOBAL is deprecated in temporary table creation"),
-							 parser_errposition(@1)));
 					$$ = $4;
-					$$->relpersistence = RELPERSISTENCE_TEMP;
+					$$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP;
 				}
 			| UNLOGGED opt_table qualified_name
 				{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index a56bd86..702bb67 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3609,3 +3610,53 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * Like function isQueryUsingTempRelation_walker
+ * return true if any relation underlying
+ * the query is a global temporary table.
+ */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* Check if the query uses global temporary table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index c709aba..26b723e 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -457,6 +457,13 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->options = seqoptions;
 
 	/*
+	 * If a sequence is bound to a global temporary table, then the sequence
+	 * must been "global temporary"
+	 */
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
+	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
 	 * clause, the "redundant options" error will point to their occurrence,
@@ -3214,6 +3221,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		cxt.isforeign = false;
 	}
 	cxt.relation = stmt->relation;
+	/* Sets the table persistence to the context */
+	cxt.relation->relpersistence = RelationGetRelPersistence(rel);
 	cxt.rel = rel;
 	cxt.inhRelations = NIL;
 	cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index aa5b97f..a8e41d2 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2108,6 +2108,14 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/*
+			 * Aotuvacuum cannot vacuum the private data stored in each backend
+			 * that belongs to global temporary table, so skip them.
+			 */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2174,7 +2182,7 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		if (RelpersistenceTsTemp(classForm->relpersistence))
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index ad0d1a9..c9fa4a4 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -2849,6 +2850,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * Returns 0 if this global temporary table is not initialized in current
+	 * backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 96c2aaa..432211d 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -22,6 +22,7 @@
 #include "access/subtrans.h"
 #include "access/syncscan.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -149,6 +150,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, active_gtt_shared_hash_size());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -221,6 +223,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* For global temporary table shared hashtable */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 94edb24..0797d32 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -65,6 +65,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -5039,3 +5040,78 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temporary table.
+ */
+int
+list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct *arrayP = procArray;
+	TransactionId result = InvalidTransactionId;
+	int			index;
+	uint8			flags = 0;
+	int			i = 0;
+
+	/* return 0 if feature is disabled */
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+		*n = 0;
+	}
+
+	/* Disable in standby node */
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		uint8           statusFlags = ProcGlobal->statusFlags[index];
+
+		if (statusFlags & flags)
+			continue;
+
+		/* Fetch all backend that is belonging to MyDatabaseId */
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->backend_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->backend_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->backend_gtt_frozenxid, result))
+				result = proc->backend_gtt_frozenxid;
+
+			/* save backend pid and backend level oldest relfrozenxid */
+			if (max_size > 0)
+			{
+				pids[i] = proc->pid;
+				xids[i] = proc->backend_gtt_frozenxid;
+				i++;
+			}
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (max_size > 0)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 108e652..8ef5b8e 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -177,7 +177,9 @@ static const char *const BuiltinTrancheNames[] = {
 	/* LWTRANCHE_PARALLEL_APPEND: */
 	"ParallelAppend",
 	/* LWTRANCHE_PER_XACT_PREDICATE_LIST: */
-	"PerXactPredicateList"
+	"PerXactPredicateList",
+	/* LWTRANCHE_GTT_CTL */
+	"GlobalTempTableControl"
 };
 
 StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 7dc3911..def8956 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -391,6 +391,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;	/* init backend level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
@@ -572,6 +573,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;	/* init backend level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 3319e97..db1eb07 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -982,6 +982,13 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/*
+			 * For global temporary table ,each backend has its own storage,
+			 * also only sees its own storage. Use Backendid to identify them.
+			 */
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 80bd60f..98b6196 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -108,6 +108,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -4889,12 +4890,26 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								/* For global temporary table, get statistic data from localhash */
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -5019,15 +5034,28 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6450,6 +6478,7 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	{
 		/* Simple variable --- look to stats for the underlying table */
 		RangeTblEntry *rte = planner_rt_fetch(index->rel->relid, root);
+		char	rel_persistence = get_rel_persistence(rte->relid);
 
 		Assert(rte->rtekind == RTE_RELATION);
 		relid = rte->relid;
@@ -6467,6 +6496,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6478,6 +6515,8 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	}
 	else
 	{
+		char	rel_persistence = get_rel_persistence(index->indexoid);
+
 		/* Expression --- maybe there are stats for the index itself */
 		relid = index->indexoid;
 		colnum = 1;
@@ -6493,6 +6532,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7411,6 +7458,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		/* attempt to lookup stats in relation for this index column */
 		if (attnum != 0)
 		{
+			char	rel_persistence = get_rel_persistence(rte->relid);
+
 			/* Simple variable -- look to stats for the underlying table */
 			if (get_relation_stats_hook &&
 				(*get_relation_stats_hook) (root, rte, attnum, &vardata))
@@ -7423,6 +7472,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7435,6 +7493,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		}
 		else
 		{
+			char	rel_persistence = get_rel_persistence(index->indexoid);
+
 			/*
 			 * Looks like we've found an expression column in the index. Let's
 			 * see if there's any stats for it.
@@ -7454,6 +7514,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index ae23299..07201a0 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -2993,6 +2994,19 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		/* For global temporary table, get statistic data from localhash */
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 66393be..a2814e4 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -66,6 +66,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1135,6 +1136,28 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+
+				/* For global temporary table, get relstat data from localhash */
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+
+				/* And put them to local relcache */
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1189,6 +1212,8 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			/* The state of the global temporary table's index may need to be set */
+			gtt_fix_index_backend_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -1313,7 +1338,22 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+			/*
+			 * For global temporary table, get the latest relfilenode
+			 * from localhash and put it in relcache.
+			 */
+			if (OidIsValid(newrelnode) &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2255,6 +2295,9 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		/* The state of the global temporary table's index may need to be set */
+		gtt_fix_index_backend_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3481,6 +3524,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3588,28 +3635,38 @@ void
 RelationSetNewRelfilenode(Relation relation, char persistence)
 {
 	Oid			newrelfilenode;
-	Relation	pg_class;
-	HeapTuple	tuple;
+	Relation	pg_class = NULL;
+	HeapTuple	tuple = NULL;
 	Form_pg_class classform;
 	MultiXactId minmulti = InvalidMultiXactId;
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
+	/*
+	 * For global temporary table, storage information for the table is
+	 * maintained locally, not in catalog.
+	 */
+	bool		update_catalog = !RELATION_IS_GLOBAL_TEMP(relation);
 
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
 
-	/*
-	 * Get a writable copy of the pg_class tuple for the given relation.
-	 */
-	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+	memset(&classform, 0, sizeof(classform));
 
-	tuple = SearchSysCacheCopy1(RELOID,
-								ObjectIdGetDatum(RelationGetRelid(relation)));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "could not find tuple for relation %u",
-			 RelationGetRelid(relation));
-	classform = (Form_pg_class) GETSTRUCT(tuple);
+	if (update_catalog)
+	{
+		/*
+		 * Get a writable copy of the pg_class tuple for the given relation.
+		 */
+		pg_class = table_open(RelationRelationId, RowExclusiveLock);
+
+		tuple = SearchSysCacheCopy1(RELOID,
+									ObjectIdGetDatum(RelationGetRelid(relation)));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u",
+				 RelationGetRelid(relation));
+		classform = (Form_pg_class) GETSTRUCT(tuple);
+	}
 
 	/*
 	 * Schedule unlinking of the old storage at transaction commit.
@@ -3635,7 +3692,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 				/* handle these directly, at least for now */
 				SMgrRelation srel;
 
-				srel = RelationCreateStorage(newrnode, persistence);
+				srel = RelationCreateStorage(newrnode, persistence, relation);
 				smgrclose(srel);
 			}
 			break;
@@ -3655,6 +3712,18 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 			break;
 	}
 
+	/* For global temporary table */
+	if (!update_catalog)
+	{
+		Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+		Assert(RELATION_IS_GLOBAL_TEMP(relation));
+		Assert(!RelationIsMapped(relation));
+
+		/* Make cache invalid and set new relnode to local cache. */
+		CacheInvalidateRelcache(relation);
+		relation->rd_node.relNode = relnode;
+	}
 	/*
 	 * If we're dealing with a mapped index, pg_class.relfilenode doesn't
 	 * change; instead we have to send the update to the relation mapper.
@@ -3664,7 +3733,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	 * possibly-inaccurate values of relpages etc, but those will be fixed up
 	 * later.
 	 */
-	if (RelationIsMapped(relation))
+	else if (RelationIsMapped(relation))
 	{
 		/* This case is only supported for indexes */
 		Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3710,9 +3779,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 		CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 	}
 
-	heap_freetuple(tuple);
+	if (update_catalog)
+	{
+		heap_freetuple(tuple);
 
-	table_close(pg_class, RowExclusiveLock);
+		table_close(pg_class, RowExclusiveLock);
+	}
 
 	/*
 	 * Make the pg_class row change or relation map change visible.  This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index bb34630..f7c1432 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -40,6 +40,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "commands/prepare.h"
 #include "commands/trigger.h"
@@ -145,6 +146,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temporary table feature.
+ * table schema are still saved in catalog.
+ *
+ * num > 0 means allows the database to manage multiple active tables at the same time.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2046,6 +2059,15 @@ static struct config_bool ConfigureNamesBool[] =
 static struct config_int ConfigureNamesInt[] =
 {
 	{
+		{"max_active_global_temporary_table", PGC_POSTMASTER, UNGROUPED,
+			gettext_noop("max active global temporary table."),
+			NULL
+		},
+		&max_active_gtt,
+		DEFAULT_NUM_ACTIVE_GTT, MIN_NUM_ACTIVE_GTT, MAX_NUM_ACTIVE_GTT,
+		NULL, NULL, NULL
+	},
+	{
 		{"archive_timeout", PGC_SIGHUP, WAL_ARCHIVING,
 			gettext_noop("Forces a switch to the next WAL file if a "
 						 "new file has not been started within N seconds."),
@@ -2564,6 +2586,16 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"vacuum_gtt_defer_check_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("The defer check age of GTT, used to check expired data after vacuum."),
+			NULL
+		},
+		&vacuum_gtt_defer_check_age,
+		10000, 0, 1000000,
+		NULL, NULL, NULL
+	},
+
 	/*
 	 * See also CheckRequiredParameterValues() if this parameter changes
 	 */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index dc1d41d..8a03dac 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2432,6 +2432,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
 		dopt->no_unlogged_table_data)
 		return;
 
+	/* Don't dump data in global temporary table/sequence */
+	if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		return;
+
 	/* Check that the data is not explicitly excluded */
 	if (simple_oid_list_member(&tabledata_exclude_oids,
 							   tbinfo->dobj.catId.oid))
@@ -15650,6 +15654,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
 		char	   *foreign = "";
+		char	   *table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15703,9 +15708,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 			binary_upgrade_set_pg_class_oids(fout, q,
 											 tbinfo->dobj.catId.oid, false);
 
+		if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED)
+			table_type = "UNLOGGED ";
+		else if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			table_type = "GLOBAL TEMPORARY ";
+		else
+			table_type = "";
+
 		appendPQExpBuffer(q, "CREATE %s%s %s",
-						  tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ?
-						  "UNLOGGED " : "",
+						  table_type,
 						  reltypename,
 						  qualrelname);
 
@@ -16091,13 +16102,22 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		}
 
 		/*
+		 * Transaction information for the global temporary table is not stored
+		 * in the pg_class.
+		 */
+		if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			Assert(tbinfo->frozenxid == 0);
+			Assert(tbinfo->minmxid == 0);
+		}
+		/*
 		 * In binary_upgrade mode, arrange to restore the old relfrozenxid and
 		 * relminmxid of all vacuumable relations.  (While vacuum.c processes
 		 * TOAST tables semi-independently, here we see them only as children
 		 * of other relations; so this "if" lacks RELKIND_TOASTVALUE, and the
 		 * child toast table is handled below.)
 		 */
-		if (dopt->binary_upgrade &&
+		else if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
 			 tbinfo->relkind == RELKIND_MATVIEW))
 		{
@@ -17029,6 +17049,7 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	PQExpBuffer query = createPQExpBuffer();
 	PQExpBuffer delqry = createPQExpBuffer();
 	char	   *qseqname;
+	bool	   global_temp_seq = false;
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
@@ -17038,9 +17059,12 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
-						  "FROM pg_catalog.pg_sequence "
-						  "WHERE seqrelid = '%u'::oid",
+						  "seqcache, seqcycle, "
+						  "c.relpersistence "
+						  "FROM pg_catalog.pg_sequence s, "
+						  "pg_catalog.pg_class c "
+						  "WHERE seqrelid = '%u'::oid "
+						  "and s.seqrelid = c.oid",
 						  tbinfo->dobj.catId.oid);
 	}
 	else if (fout->remoteVersion >= 80400)
@@ -17085,6 +17109,9 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
 
+	if (fout->remoteVersion >= 140000)
+		global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
 	if (strcmp(seqtype, "smallint") == 0)
@@ -17162,9 +17189,13 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	}
 	else
 	{
-		appendPQExpBuffer(query,
-						  "CREATE SEQUENCE %s\n",
-						  fmtQualifiedDumpable(tbinfo));
+		appendPQExpBuffer(query, "CREATE ");
+
+		if (global_temp_seq)
+			appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+		appendPQExpBuffer(query, "SEQUENCE %s\n",
+								  fmtQualifiedDumpable(tbinfo));
 
 		if (strcmp(seqtype, "bigint") != 0)
 			appendPQExpBuffer(query, "    AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 6685d51..e37d9bf 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -86,7 +86,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -166,7 +166,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 7e524ea..5725cbe 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -447,8 +449,17 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
 			 "         ON c.relnamespace = n.oid "
 			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
+			 CppAsString2(RELKIND_MATVIEW) ") AND ");
+
+	if (skip_gtt)
+	{
+		/* exclude global temp tables */
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+			"    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND ");
+	}
+
 	/* exclude possible orphaned temp tables */
+	snprintf(query + strlen(query), sizeof(query) - strlen(query),
 			 "    ((n.nspname !~ '^pg_temp_' AND "
 			 "      n.nspname !~ '^pg_toast_temp_' AND "
 			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index e2253ec..32eccd4 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -407,7 +407,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -638,7 +638,10 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+			/* exclude global temp tables */
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -649,7 +652,10 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+		/* exclude global temp tables */
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index ee70243..a69089c 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -388,7 +388,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 07d6400..334251f 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3752,7 +3752,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 8afc780..761e6dd 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1044,6 +1044,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2434,6 +2436,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2642,6 +2647,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index f94deff..056fe64 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -59,7 +59,8 @@ extern Relation heap_create(const char *relname,
 							bool mapped_relation,
 							bool allow_system_table_mods,
 							TransactionId *relfrozenxid,
-							MultiXactId *relminmxid);
+							MultiXactId *relminmxid,
+							bool skip_create_storage);
 
 extern Oid	heap_create_with_catalog(const char *relname,
 									 Oid relnamespace,
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index bb5e72c..bab9599 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -175,6 +175,7 @@ DECLARE_INDEX(pg_class_tblspc_relfilenode_index, 3455, on pg_class using btree(r
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index e7fbda9..ac04e2a 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5589,6 +5589,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4388',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4389',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4390',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4391',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 30c38e0..7ff2408 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,7 +22,7 @@
 /* GUC variables */
 extern int	wal_skip_threshold;
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationPreTruncate(Relation rel);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..a2fadb0
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern int		vacuum_gtt_defer_check_age;
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Oid relid,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void gtt_force_enable_index(Relation index);
+extern void gtt_fix_index_backend_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+extern void gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index e2638ab..89a5ce4 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index d0a52f8..8fa493f 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -399,6 +399,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index af9b417..6e16f99 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -218,6 +218,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_SHARED_TIDBITMAP,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_PER_XACT_PREDICATE_LIST,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 00bb244..7d78518 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -153,6 +153,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId backend_gtt_frozenxid;	/* backend level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index ea8a876..ed87772 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -92,4 +92,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 073c8f3..cd8cad8 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -283,6 +283,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index c5ffea4..6dda0b6 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	struct SMgrRelationData *rd_smgr;	/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -306,6 +306,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -571,11 +572,13 @@ typedef struct ViewOptions
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -583,6 +586,7 @@ typedef struct ViewOptions
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -595,6 +599,30 @@ typedef struct ViewOptions
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP_ON_CURRENT_SESSION
+ *		Test a rel is either local temp relation of this session
+ *		or global temp relation.
+ */
+#define RELATION_IS_TEMP_ON_CURRENT_SESSION(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is local temp relation or global temporary relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RelpersistenceTsTemp
+ *		Test a relpersistence is local temp relation or global temporary relation.
+ */
+#define RelpersistenceTsTemp(relpersistence) \
+	(relpersistence == RELPERSISTENCE_TEMP || \
+	 relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -638,6 +666,19 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* For global temporary table */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/* Get on commit clause value only for global temporary table */
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+/* Get relpersistence for relation */
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..ca2d135
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,14 @@
+reset search_path;
+select pg_sleep(5);
+ pg_sleep 
+----------
+ 
+(1 row)
+
+drop schema gtt cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
+drop cascades to table gtt.gtt_with_seq
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..0ac9e22
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,381 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+ n 
+---
+ 9
+(1 row)
+
+commit;
+-- 0 row
+select * from gtt6;
+ n 
+---
+(0 rows)
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temporary table
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temporary table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  could not create global temporary table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR:  insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count 
+-------
+     1
+(1 row)
+
+select count(*) from orders;
+ count 
+-------
+     0
+(1 row)
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  2 |  3
+(2 rows)
+
+commit;
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+select * from gtt_s_2 order by c1;
+ c1 | c2 
+----+----
+  1 |  2
+  2 |  4
+(2 rows)
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 1)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 300)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 33 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test_new
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.gtt6
+drop cascades to table gtt_function.foo
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to sequence gtt_function.seq_1
+drop cascades to table gtt_function.gtt_s_1
+drop cascades to table gtt_function.gtt_s_2
+drop cascades to table gtt_function.gt1
+drop cascades to table gtt_function.gtt_test1
+drop cascades to table gtt_function.gtt_test2
+drop cascades to table gtt_function.gtt_test3
+drop cascades to table gtt_function.gtt_test4
+drop cascades to table gtt_function.gtt_test5
+drop cascades to table gtt_function.gtt_test6
+drop cascades to table gtt_function.gtt_test7
+drop cascades to table gtt_function.gtt_test8
+drop cascades to table gtt_function.gtt_test9
+drop cascades to table gtt_function.gtt_test10
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..0646aae
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,90 @@
+set search_path=gtt,sys;
+select nextval('gtt_with_seq_c2_seq');
+ nextval 
+---------
+       1
+(1 row)
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..0fccf6b
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,343 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+rollback;
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+commit;
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          114688 |                 614400
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |            49152 |         49152 |                  49152
+(2 rows)
+
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2000
+(1 row)
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+ c1 | c2 
+----+----
+  1 |  1
+(1 row)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..8c0c376
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,10 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..4420fdb
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,80 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+ gtt_stats  | gtt_pkey  |        1 |         0 |             0
+(2 rows)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+reindex table gtt;
+reindex index gtt_pkey;
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+ gtt_stats  | gtt_pkey  |       30 |     10000 |             0
+(2 rows)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 097ff5d..27803d6 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1359,6 +1359,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index ae89ed7..c498e39 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..2c8e586
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,8 @@
+
+
+reset search_path;
+
+select pg_sleep(5);
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..fd8b4d3
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,253 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+commit;
+-- 0 row
+select * from gtt6;
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+commit;
+select * from gtt_s_1 order by c1;
+select * from gtt_s_2 order by c1;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d05745e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,44 @@
+
+
+set search_path=gtt,sys;
+
+select nextval('gtt_with_seq_c2_seq');
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..81a6039
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,154 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+rollback;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+commit;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+select count(*) from gtt_t_kenyon;
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..dbe84d1
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,19 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..d61b0ff
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,46 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+reindex table gtt;
+
+reindex index gtt_pkey;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#295曾文旌
wenjing.zwj@alibaba-inc.com
In reply to: Pavel Stehule (#293)
1 attachment(s)
Re: [Proposal] Global temporary tables

I found that the new Patch mail failed to register to Commitfest
https://commitfest.postgresql.org/28/2349/# <https://commitfest.postgresql.org/28/2349/#&gt;
I don't know what's wrong and how to check it?
Could you help me figure it out?

Show quoted text

2020年11月25日 14:19,Pavel Stehule <pavel.stehule@gmail.com> 写道:

po 23. 11. 2020 v 10:27 odesílatel 曾文旌 <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:

2020年11月21日 02:28,Pavel Stehule <pavel.stehule@gmail.com <mailto:pavel.stehule@gmail.com>> 写道:

Hi

pá 11. 9. 2020 v 17:00 odesílatel 曾文旌 <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:
I have written the README for the GTT, which contains the GTT requirements and design.
I found that compared to my first email a year ago, many GTT Limitations are now gone.
Now, I'm adding comments to some of the necessary functions.

There are problems with patching. Please, can you rebase your patch?

Sure.
I'm still working on sort code and comments.
If you have any suggestions, please let me know.

It is broken again

There is bad white space

+   /*
+    * For global temp table only
+    * use ShareUpdateExclusiveLock for ensure safety
+    */
+   {
+       {
+           "on_commit_delete_rows",
+           "global temp table on commit options",
+           RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+           ShareUpdateExclusiveLock
+       },
+       true
+   },  <=================
/* list terminator */
{{NULL}}
+7 OTHERS
+Parallel query
+Planner does not produce parallel query plans for SQL related to GTT. Because <=================
+GTT private data cannot be accessed across processes.
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible) <========================
+ * to local hashtable
+ */
+void
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible) <==============
+ * from lo

and there are lot of more places ...

I found other issue

postgres=# create global temp table foo(a int);
CREATE TABLE
postgres=# create index on foo(a);
CREATE INDEX

close session and in new session

postgres=# reindex index foo_a_idx ;
WARNING: relcache reference leak: relation "foo" not closed
REINDEX

Regards

Pavel

Wenjing

Regards

Pavel

Wenjing

2020年7月31日 上午4:57,Robert Haas <robertmhaas@gmail.com <mailto:robertmhaas@gmail.com>> 写道:

On Thu, Jul 30, 2020 at 8:09 AM wenjing zeng <wjzeng2012@gmail.com <mailto:wjzeng2012@gmail.com>> wrote:

Please continue to review the code.

This patch is pretty light on comments. Many of the new functions have
no header comments, for example. There are comments here and there in
the body of the new functions that are added, and in places where
existing code is changed there are comments here and there, but
overall it's not a whole lot. There's no documentation and no README,
either. Since this adds a new feature and a bunch of new SQL-callable
functions that interact with that feature, the feature itself should
be documented, along with its limitations and the new SQL-callable
functions that interact with it. I think there should be either a
lengthy comment in some suitable file, or maybe various comments in
various files, or else a README file, that clearly sets out the major
design principles behind the patch, and explaining also what that
means in terms of features and limitations. Without that, it's really
hard for anyone to jump into reviewing this code, and it will be hard
for people who have to maintain it in the future to understand it,
either. Or for users, for that matter.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com <http://www.enterprisedb.com/&gt;
The Enterprise PostgreSQL Company

Attachments:

smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#296Julien Rouhaud
rjuju123@gmail.com
In reply to: 曾文旌 (#295)
Re: [Proposal] Global temporary tables

On Thu, Nov 26, 2020 at 4:05 PM 曾文旌 <wenjing.zwj@alibaba-inc.com> wrote:

I found that the new Patch mail failed to register to Commitfest
https://commitfest.postgresql.org/28/2349/#
I don't know what's wrong and how to check it?
Could you help me figure it out?

Apparently the attachment in
/messages/by-id/A3F1EBD9-E694-4384-8049-37B09308491B@alibaba-inc.com
wasn't detected. I have no idea why, maybe Magnus will know.
Otherwise you could try to ask on -www.

#297Magnus Hagander
magnus@hagander.net
In reply to: Julien Rouhaud (#296)
Re: [Proposal] Global temporary tables

On Thu, Nov 26, 2020 at 11:16 AM Julien Rouhaud <rjuju123@gmail.com> wrote:

On Thu, Nov 26, 2020 at 4:05 PM 曾文旌 <wenjing.zwj@alibaba-inc.com> wrote:

I found that the new Patch mail failed to register to Commitfest
https://commitfest.postgresql.org/28/2349/#
I don't know what's wrong and how to check it?
Could you help me figure it out?

Apparently the attachment in
/messages/by-id/A3F1EBD9-E694-4384-8049-37B09308491B@alibaba-inc.com
wasn't detected. I have no idea why, maybe Magnus will know.
Otherwise you could try to ask on -www.

Not offhand. The email appeas to have a fairly complex nested mime
structure, so something in the python library that parses the MIME
decides that it's not there. For some reason the email is 7 parts. 1
is the signature, the rest seems complexly nested. And the attachment
seems to be squeezed in between two different HTML parts.

Basically, at the top it's multipart/alternative, which says there are
two choices. One is text/plain, which is what the archives uses. The
other is a combination of text/html followed by
application/octetstream (the patch) followed by another text/html.

The archives picks the first alternative, which is text/plain, which
does not contain the attachment. The attachment only exists in the
HTML view.

I think the easiest solution is to re-send as plain text email with
the attachment, which would then put the attachment on the email
itself instead of embedded in the HTML, I would guess.

--
Magnus Hagander
Me: https://www.hagander.net/
Work: https://www.redpill-linpro.com/

#298Pavel Stehule
pavel.stehule@gmail.com
In reply to: 曾文旌 (#294)
Re: [Proposal] Global temporary tables

Hi

this patch is broken now. Please, can you check it?

Regards

Pavel

st 25. 11. 2020 v 14:08 odesílatel 曾文旌 <wenjing.zwj@alibaba-inc.com> napsal:

Show quoted text

2020年11月25日 14:19,Pavel Stehule <pavel.stehule@gmail.com> 写道:

po 23. 11. 2020 v 10:27 odesílatel 曾文旌 <wenjing.zwj@alibaba-inc.com>
napsal:

2020年11月21日 02:28,Pavel Stehule <pavel.stehule@gmail.com> 写道:

Hi

pá 11. 9. 2020 v 17:00 odesílatel 曾文旌 <wenjing.zwj@alibaba-inc.com>
napsal:

I have written the README for the GTT, which contains the GTT
requirements and design.
I found that compared to my first email a year ago, many GTT Limitations
are now gone.
Now, I'm adding comments to some of the necessary functions.

There are problems with patching. Please, can you rebase your patch?

Sure.
I'm still working on sort code and comments.
If you have any suggestions, please let me know.

It is broken again

There is bad white space

+   /*
+    * For global temp table only
+    * use ShareUpdateExclusiveLock for ensure safety
+    */
+   {
+       {
+           "on_commit_delete_rows",
+           "global temp table on commit options",
+           RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+           ShareUpdateExclusiveLock
+       },
+       true
+   },  <=================
/* list terminator */
{{NULL}}
+7 OTHERS
+Parallel query
+Planner does not produce parallel query plans for SQL related to GTT.
Because <=================
+GTT private data cannot be accessed across processes.
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible)
<========================
+ * to local hashtable
+ */
+void
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible)
<==============
+ * from lo

and there are lot of more places ...

I found other issue

postgres=# create global temp table foo(a int);
CREATE TABLE
postgres=# create index on foo(a);
CREATE INDEX

close session and in new session

postgres=# reindex index foo_a_idx ;
WARNING: relcache reference leak: relation "foo" not closed
REINDEX

I fixed all the above issues and rebase code.
Please review the new version code again.

Wenjing

Regards

Pavel

Wenjing

Regards

Pavel

Wenjing

2020年7月31日 上午4:57,Robert Haas <robertmhaas@gmail.com> 写道:

On Thu, Jul 30, 2020 at 8:09 AM wenjing zeng <wjzeng2012@gmail.com>

wrote:

Please continue to review the code.

This patch is pretty light on comments. Many of the new functions have
no header comments, for example. There are comments here and there in
the body of the new functions that are added, and in places where
existing code is changed there are comments here and there, but
overall it's not a whole lot. There's no documentation and no README,
either. Since this adds a new feature and a bunch of new SQL-callable
functions that interact with that feature, the feature itself should
be documented, along with its limitations and the new SQL-callable
functions that interact with it. I think there should be either a
lengthy comment in some suitable file, or maybe various comments in
various files, or else a README file, that clearly sets out the major
design principles behind the patch, and explaining also what that
means in terms of features and limitations. Without that, it's really
hard for anyone to jump into reviewing this code, and it will be hard
for people who have to maintain it in the future to understand it,
either. Or for users, for that matter.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#299wenjing
wjzeng2012@gmail.com
In reply to: Pavel Stehule (#298)
1 attachment(s)
Re: [Proposal] Global temporary tables

ok

The cause of the problem is that the name of the dependent function
(readNextTransactionID) has changed. I fixed it.

This patch(V43) is base on 9fd2952cf4920d563e9cea51634c5b364d57f71a

Wenjing

Attachments:

global_temporary_table_v43-pg14.patchapplication/octet-stream; name=global_temporary_table_v43-pg14.patchDownload
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index d897bbe..aef7996 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -168,6 +168,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * In order to avoid consistency problems, the global temporary table
+	 * uses ShareUpdateExclusiveLock.
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temporary table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},
 	/* list terminator */
 	{{NULL}}
 };
@@ -1817,6 +1830,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1962,12 +1977,16 @@ bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
 	/*
-	 * There are no options for partitioned tables yet, but this is able to do
-	 * some validation.
+	 * Add option for global temp partitioned tables.
 	 */
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index a3ec9f2..7315987 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1026,7 +1026,7 @@ gistproperty(Oid index_oid, int attno,
 XLogRecPtr
 gistGetFakeLSN(Relation rel)
 {
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	if (RELATION_IS_TEMP(rel))
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 0752fb3..5c85d77 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -151,7 +151,7 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	if (!RELATION_IS_TEMP(index))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index bd5faf0..2b6d240 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -589,7 +589,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -642,7 +642,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode)
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
-	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence);
+	RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel);
 
 	/* copy main fork */
 	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 8341879..08ff7c0 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -63,6 +63,7 @@
 #include "access/xlog.h"
 #include "catalog/index.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -448,9 +449,13 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
 	Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
-	/* not every AM requires these to be valid, but heap does */
-	Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
-	Assert(MultiXactIdIsValid(onerel->rd_rel->relminmxid));
+	/*
+	 * not every AM requires these to be valid, but regular heap does.
+	 * Transaction information for the global temp table will be stored
+	 * in the local hash table, not the catalog.
+	 */
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ MultiXactIdIsValid(onerel->rd_rel->relminmxid));
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index fc744cf..c198abf 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -677,6 +678,14 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		/*
+		 * If a global temporary table storage file is not initialized in the
+		 * current backend, its index does not have a root page, just returns 0.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index f4d1ce5..3457527 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -7026,6 +7026,16 @@ StartupXLOG(void)
 		int			rmid;
 
 		/*
+		 * When some backend processes crash, those storage files that belong to
+		 * global temporary tables will not be cleaned up and remain in the data
+		 * directory.
+		 * These data files no longer belong to any session and will be cleaned up
+		 * during the recovery.
+		 */
+		if (max_active_gtt > 0)
+			RemovePgTempFiles();
+
+		/*
 		 * Update pg_control to show that we are recovering and to show the
 		 * selected checkpoint as the place we are starting from. We also mark
 		 * pg_control with any minimum recovery stop point obtained from a
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 5fcd004..58b994c 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -212,7 +212,8 @@ Boot_CreateStmt:
 												   mapped_relation,
 												   true,
 												   &relfrozenxid,
-												   &relminmxid);
+												   &relminmxid,
+												   false);
 						elog(DEBUG4, "bootstrap relation created");
 					}
 					else
diff --git a/src/backend/catalog/GTT_README b/src/backend/catalog/GTT_README
new file mode 100644
index 0000000..3a0ec50
--- /dev/null
+++ b/src/backend/catalog/GTT_README
@@ -0,0 +1,170 @@
+Global Temporary Table(GTT)
+==============
+
+Feature description
+--------------------------------
+
+Previously, temporary tables are defined once and automatically
+created (starting with empty contents) in every session before using them.
+
+The temporary table implementation in PostgreSQL, known as Local temp tables(LTT),
+did not fully comply with the SQL standard. This version added the support of
+Global Temporary Table .
+
+The metadata of Global Temporary Table is persistent and shared among sessions.
+The data stored in the Global temporary table is independent of sessions. This
+means, when a session creates a Global Temporary Table and writes some data.
+Other sessions cannot see those data, but they have an empty Global Temporary
+Table with same schema.
+
+Like local temporary table, Global Temporary Table supports ON COMMIT PRESERVE ROWS
+or ON COMMIT DELETE ROWS clause, so that data in the temporary table can be
+cleaned up or reserved automatically when a session exits or a transaction COMMITs.
+
+Unlike Local Temporary Table, Global Temporary Table does not support
+ON COMMIT DROP clauses.
+
+In following paragraphs, we use GTT for Global Temporary Table and LTT for
+local temporary table.
+
+Main design ideas
+-----------------------------------------
+
+STORAGE & BUFFER
+
+In general, GTT and LTT use the same storage and buffer design and
+implementation. The storage files for both types of temporary tables are named
+as t_backendid_relfilenode, and the local buffer is used to cache the data.
+
+The schema of GTTs is shared among sessions while their data are not. We build
+a new mechanisms to manage those non-shared data and their statistics.
+Here is the summary of changes:
+
+1) CATALOG
+GTTs store session-specific data. The storage information of GTTs'data, their
+transaction information, and their statistics are not stored in the catalog.
+
+2) STORAGE INFO & STATISTICS & TRANSACTION
+In order to maintain durability and availability of GTTs'session-specific data,
+their storage information, statistics, and transaction information is managed
+in a local hash table tt_storage_local_hash.
+
+3) DDL
+A shared hash table active_gtt_shared_hash is added to track the state of the
+GTT in a different session. This information is recorded in the hash table
+during the DDL execution of the GTT.
+
+4) LOCK
+The data stored in a GTT can only be modified or accessed by owning session.
+The statements that only modify data in a GTT do not need a high level of table
+locking.
+Changes to the GTT's metadata affect all sessions.
+The operations making those changes include truncate GTT, Vacuum/Cluster GTT,
+and Lock GTT.
+
+Detailed design
+-----------------------------------------
+
+1. CATALOG
+1.1 relpersistence
+define RELPERSISTENCEGLOBALTEMP 'g'
+Mark Global Temporary Table in pg_class relpersistence to 'g'. The relpersistence
+of indexes created on the GTT, sequences on GTT and toast tables on GTT are
+also set to 'g'
+
+1.2 on commit clause
+LTT's status associated with on commit DELETE ROWS and on commit PRESERVE ROWS
+is not stored in catalog. Instead, GTTs need a bool value on_commit_delete_rows
+in reloptions which is shared among sessions.
+
+1.3 gram.y
+GTT is already supported in syntax tree. We remove the warning message
+"GLOBAL is deprecated in temporary table creation" and mark
+relpersistence = RELPERSISTENCEGLOBALTEMP.
+
+2. STORAGE INFO & STATISTICS DATA & TRANSACTION INFO
+2.1. gtt_storage_local_hash
+Each backend creates a local hashtable gtt_storage_local_hash to track a GTT's
+storage file information, statistics, and transaction information.
+
+2.2 GTT storage file info track
+1) When one session inserts data into a GTT for the first time, record the
+storage info to gtt_storage_local_hash.
+2) Use beforeshmemexit to ensure that all files ofa session GTT are deleted when
+the session exits.
+3) GTT storage file cleanup during abnormal situations
+When a backend exits abnormally (such as oom kill), the startup process starts
+recovery before accepting client connection. The same startup process checks
+nd removes all GTT files before redo WAL.
+
+2.3 statistics info
+1) relpages reltuples relallvisible
+2) The statistics of each column from pg_statistic
+All the above information is stored in gtt_storage_local_hash.
+When doing vacuum or analyze, GTT's statistic is updated, which is used by
+the SQL planner.
+The statistics summarizes only data in the current session.
+
+2.3 transaction info track
+frozenxid minmulti from pg_class is stored to gtt_storage_local_hash.
+
+4 DDL
+4.1. active_gtt_shared_hash
+This is the hash table created in shared memory to trace the GTT files initialized
+in each session. Each hash entry contains a bitmap that records the backendid of
+the initialized GTT file. With this hash table, we know which backend/session
+is using this GTT. Such information is used during GTT's DDL operations.
+
+4.1 DROP GTT
+One GTT is allowed to be deleted when there is only one session using the table
+and the session is the current session.
+After holding the AccessExclusiveLock lock on GTT, active_gtt_shared_hash
+is checked to ensure that.
+
+4.2 ALTER GTT/DROP INDEX ON GTT
+Same as drop GTT.
+
+4.3 CREATE INDEX ON GTT
+1) create index on GTT statements build index based on local data in a session.
+2) After the index is created, record the index metadata to the catalog.
+3) Other sessions can enable or disable the local GTT index.
+
+5 LOCK
+
+5.1 TRUNCATE GTT
+The truncate GTT command uses RowExclusiveLock, not AccessExclusiveLock, because
+this command only cleans up local data and local buffers in current session.
+
+5.2 CLUSTER GTT/VACUUM FULL GTT
+Same as truncate GTT.
+
+5.3 Lock GTT
+A lock GTT statement does not hold any table locks.
+
+6 MVCC commit log(clog) cleanup
+
+The GTT storage file contains transaction information. Queries for GTT data rely
+on transaction information such as clog. The transaction information required by
+each session may be completely different. We need to ensure that the transaction
+information of the GTT data is not cleaned up during its lifetime and that
+transaction resources are recycled at the instance level.
+
+6.1 The session level GTT oldest frozenxid
+1) To manage all GTT transaction information, add session level oldest frozenxid
+in each session. When one GTT is created or removed, record the session level
+oldest frozenxid and store it in MyProc.
+2) When vacuum advances the database's frozenxid, session level oldest frozenxid
+should be considered. This is acquired by searching all of MyProc. This way,
+we can avoid the clog required by GTTs to be cleaned.
+
+6.2 vacuum GTT
+Users can perform vacuum over a GTT to clean up local data in the GTT.
+
+6.3 autovacuum GTT
+Autovacuum skips all GTTs, because the data in GTTs is only visible in current
+session.
+
+7 OTHERS
+Parallel query
+Planner does not produce parallel query plans for SQL related to GTT. Because
+GTT private data cannot be accessed across processes.
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 70bc212..04f8b2a 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -43,6 +43,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index e2ed80a..81d19c5 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -392,6 +392,7 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 9abc4a1..ea05776 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
 #include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -304,7 +306,8 @@ heap_create(const char *relname,
 			bool mapped_relation,
 			bool allow_system_table_mods,
 			TransactionId *relfrozenxid,
-			MultiXactId *relminmxid)
+			MultiXactId *relminmxid,
+			bool skip_create_storage)
 {
 	bool		create_storage;
 	Relation	rel;
@@ -369,7 +372,9 @@ heap_create(const char *relname,
 	 * storage is already created, so don't do it here.  Also don't create it
 	 * for relkinds without physical storage.
 	 */
-	if (!RELKIND_HAS_STORAGE(relkind) || OidIsValid(relfilenode))
+	if (!RELKIND_HAS_STORAGE(relkind) ||
+		OidIsValid(relfilenode) ||
+		skip_create_storage)
 		create_storage = false;
 	else
 	{
@@ -427,7 +432,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -997,6 +1002,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -1035,8 +1041,21 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/*
+	 * The transaction information of the global temporary table is stored
+	 * in the local hash table, not in catalog.
+	 */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1301,7 +1320,8 @@ heap_create_with_catalog(const char *relname,
 							   mapped_relation,
 							   allow_system_table_mods,
 							   &relfrozenxid,
-							   &relminmxid);
+							   &relminmxid,
+							   false);
 
 	Assert(relid == RelationGetRelid(new_rel_desc));
 
@@ -1408,6 +1428,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1987,6 +2008,19 @@ heap_drop_with_catalog(Oid relid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
 	/*
+	 * Global temporary table is allowed to be dropped only when the
+	 * current session is using it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot drop global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
+	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
 	if (RELKIND_HAS_STORAGE(rel->rd_rel->relkind))
@@ -3249,7 +3283,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3261,7 +3295,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3307,8 +3341,16 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		/*
+		 * Truncate global temporary table only clears local data,
+		 * so only low-level locks need to be held.
+		 */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3341,6 +3383,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3349,23 +3392,47 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	/* For global temporary table only */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		/*
+		 * If this GTT is not initialized in current backend, there is
+		 * no needs to anything.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(rel)))
+			return;
+
+		/*
+		 * Truncate GTT only clears local data, so only low-level locks
+		 * need to be held.
+		 */
+		lockmode = RowExclusiveLock;
+	}
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	/*
+	 * After the data is cleaned up on the GTT, the transaction information
+	 * for the data(stored in local hash table) is also need reset.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(RelationGetRelid(rel), 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 4ef61b5..a0e7dbb 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -54,6 +54,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "commands/defrem.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -721,6 +722,29 @@ index_create(Relation heapRelation,
 	char		relkind;
 	TransactionId relfrozenxid;
 	MultiXactId relminmxid;
+	bool		skip_create_storage = false;
+
+	/* For global temporary table only */
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temporary table with concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot reindex global temporary tables concurrently")));
+
+		/*
+		 * For the case that some backend is applied relcache message to create
+		 * an index on a global temporary table, if this table in the current
+		 * backend are not initialized, the creation of index storage on the
+		 * table are also skipped.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+		{
+			skip_create_storage = true;
+			flags |= INDEX_CREATE_SKIP_BUILD;
+		}
+	}
 
 	/* constraint flags can only be set when a constraint is requested */
 	Assert((constr_flags == 0) ||
@@ -928,7 +952,8 @@ index_create(Relation heapRelation,
 								mapped_relation,
 								allow_system_table_mods,
 								&relfrozenxid,
-								&relminmxid);
+								&relminmxid,
+								skip_create_storage);
 
 	Assert(relfrozenxid == InvalidTransactionId);
 	Assert(relminmxid == InvalidMultiXactId);
@@ -2261,7 +2286,7 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 * lock (see comments in RemoveRelations), and a non-concurrent DROP is
 	 * more efficient.
 	 */
-	Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+	Assert(!RelpersistenceTsTemp(get_rel_persistence(indexId)) ||
 		   (!concurrent && !concurrent_lock_mode));
 
 	/*
@@ -2294,6 +2319,20 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
 	/*
+	 * Allow to drop index on global temporary table when only current
+	 * backend use it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation) &&
+		is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+	{
+		ereport(ERROR,
+			(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+			 errmsg("cannot drop index %s or global temporary table %s",
+					RelationGetRelationName(userIndexRelation), RelationGetRelationName(userHeapRelation)),
+			 errhint("Because the index is created on the global temporary table and other backend attached it.")));
+	}
+
+	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
 	 *
@@ -2901,6 +2940,7 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = RELATION_IS_GLOBAL_TEMP(rel);
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2995,20 +3035,37 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
-		{
-			rd_rel->relpages = (int32) relpages;
-			dirty = true;
-		}
-		if (rd_rel->reltuples != (float4) reltuples)
+		/* For global temporary table */
+		if (is_gtt)
 		{
-			rd_rel->reltuples = (float4) reltuples;
-			dirty = true;
+			/* Update GTT'statistics into local relcache */
+			rel->rd_rel->relpages = (int32) relpages;
+			rel->rd_rel->reltuples = (float4) reltuples;
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+
+			/* Update GTT'statistics into local hashtable */
+			up_gtt_relstats(RelationGetRelid(rel), relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+		else
 		{
-			rd_rel->relallvisible = (int32) relallvisible;
-			dirty = true;
+			if (rd_rel->relpages != (int32) relpages)
+			{
+				rd_rel->relpages = (int32) relpages;
+				dirty = true;
+			}
+
+			if (rd_rel->reltuples != (float4) reltuples)
+			{
+				rd_rel->reltuples = (float4) reltuples;
+				dirty = true;
+			}
+
+			if (rd_rel->relallvisible != (int32) relallvisible)
+			{
+				rd_rel->relallvisible = (int32) relallvisible;
+				dirty = true;
+			}
 		}
 	}
 
@@ -3121,6 +3178,26 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, progress_index, progress_vals);
 	}
 
+	/* For build index on global temporary table */
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		/*
+		 * If the storage for the index in this session is not initialized,
+		 * it needs to be created.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			/* Before create init storage, fix the local Relcache first */
+			force_enable_gtt_index(indexRelation);
+
+			Assert(gtt_storage_attached(RelationGetRelid(heapRelation)));
+
+			/* Init storage for index */
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3676,6 +3753,20 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	if (!OidIsValid(heapId))
 		return;
 
+	/*
+	 * For reindex on global temporary table, If the storage for the index
+	 * in current backend is not initialized, nothing is done.
+	 */
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		!gtt_storage_attached(indexId))
+	{
+		/* Suppress use of the target index while rebuilding it */
+		SetReindexProcessing(heapId, indexId);
+		/* Re-allow use of target index */
+		ResetReindexProcessing();
+		return;
+	}
+
 	if ((params->options & REINDEXOPT_MISSING_OK) != 0)
 		heapRelation = try_table_open(heapId, ShareLock);
 	else
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 005e029..5e59b47 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -655,6 +655,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/* Do not allow create global temporary table in temporary schemas */
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp table in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index cba7a9a..9cf6802 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,6 +27,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -61,6 +62,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			temprelOid;			/* InvalidOid if not a global temporary rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -115,7 +117,7 @@ AddPendingSync(const RelFileNode *rnode)
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -126,7 +128,12 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/*
+		 * Global temporary table and local temporary table use same
+		 * design on storage module.
+		 */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -154,6 +161,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
@@ -165,6 +173,21 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		AddPendingSync(&rnode);
 	}
 
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		Assert(rel && RELATION_IS_GLOBAL_TEMP(rel));
+
+		/*
+		 * Remember the reloid of global temporary table, which is used for
+		 * transaction commit or rollback.
+		 * see smgrDoPendingDeletes.
+		 */
+		pending->temprelOid = RelationGetRelid(rel);
+
+		/* Remember global temporary table storage info to localhash */
+		remember_gtt_storage_info(rnode, rel);
+	}
+
 	return srel;
 }
 
@@ -201,12 +224,21 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
 	/*
+	 * Remember the reloid of global temporary table, which is used for
+	 * transaction commit or rollback.
+	 * see smgrDoPendingDeletes.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->temprelOid = RelationGetRelid(rel);
+
+	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
 	 * once with atCommit false.  Hence, it will be physically deleted at end
@@ -602,6 +634,7 @@ smgrDoPendingDeletes(bool isCommit)
 	int			nrels = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -631,14 +664,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->temprelOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -648,12 +685,21 @@ smgrDoPendingDeletes(bool isCommit)
 
 	if (nrels > 0)
 	{
+		int	i;
+
 		smgrdounlinkall(srels, nrels, false);
 
-		for (int i = 0; i < nrels; i++)
+		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* Delete global temporary table info in localhash */
+			if (gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..eaae0d0
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1648 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  The body implementation of Global Temparary table.
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *	  See src/backend/catalog/GTT_README for Global temparary table's
+ *	  requirements and design.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_info_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+int		vacuum_gtt_defer_check_age = 0;
+
+/*
+ * The Global temporary table's shared hash table data structure
+ */
+typedef struct gtt_ctl_data
+{
+	LWLock		lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+/* record this global temporary table in which backends are being used */
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+/*
+ * The Global temporary table's local hash table data structure
+ */
+/* Record the storage information and statistical information of the global temporary table */
+typedef struct
+{
+	Oid			relfilenode;
+	Oid			spcnode;
+
+	/* pg_class relstat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+
+	/* pg_statistic column stat */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+
+	Oid			oldrelid;			/* remember the source of relid, before the switch relfilenode. */
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_free_statistics(gtt_relfilenode *rnode);
+static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok);
+static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok);
+static Bitmapset *copy_active_gtt_bitmap(Oid relid);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+/*
+ * Calculate shared hash table entry size for GTT.
+ */
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	/* hash entry header size */
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	/*
+	 * hash entry data size
+	 * this is a bitmap in shared memory, each backend have a bit.
+	 */
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+/*
+ * Calculate shared hash table max size for GTT.
+ */
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	/* shared hash header size */
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	/* hash entry size */
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	/* max size */
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+/*
+ * Initialization shared hash table for GTT.
+ */
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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();
+	}
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(gtt_fnode);
+	info.entrysize = action_gtt_shared_hash_entry_size();
+	active_gtt_shared_hash =
+		ShmemInitHash("active gtt shared hash",
+						gtt_shared_ctl->max_entry,
+						gtt_shared_ctl->max_entry,
+						&info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+/*
+ * Record GTT relid to shared hash table, which means that current backend is using this GTT.
+ */
+static void
+gtt_storage_checkin(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			found;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *)&(fnode), 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_global_temporary_table.")));
+	}
+
+	if (!found)
+	{
+		int			wordnum;
+
+		/* init bitmap */
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	/* record itself in bitmap */
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+/*
+ * Remove the GTT relid record from the shared hash table which means that current backend is
+ * not use this GTT.
+ */
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode				fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	if (!skiplock)
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		if (!skiplock)
+			LWLockRelease(&gtt_shared_ctl->lock);
+
+		if (isCommit)
+			elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+	/* remove itself from bitmap */
+	bms_del_member(entry->map, MyBackendId);
+
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+
+	if (!skiplock)
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+	return;
+}
+
+/*
+ * Gets usage information for a GTT from shared hash table.
+ * The information is in the form of bitmap.
+ * Quickly copy the entire bitmap from shared memory and return it.
+ * that to avoid holding locks for a long time.
+ */
+static Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	Bitmapset	*map_copy = NULL;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		return NULL;
+	}
+
+	Assert(entry->map);
+
+	/* copy the entire bitmap */
+	if (!bms_is_empty(entry->map))
+		map_copy = bms_copy(entry->map);
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return map_copy;
+}
+
+/*
+ * Check if there are other backends using this GTT besides the current backand.
+ */
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool		in_use = false;
+	int			num_use = 0;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		return false;
+	}
+
+	Assert(entry->map);
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+	/* how many backend are using this GTT */
+	num_use = bms_num_members(entry->map);
+	if (num_use == 0)
+		in_use = false;
+	else if (num_use == 1)
+	{
+		/* check if this is itself */
+		if(bms_is_member(MyBackendId, entry->map))
+			in_use = false;
+		else
+			in_use = true;
+	}
+	else
+		in_use = true;
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return in_use;
+}
+
+/*
+ * Record GTT information to local hash.
+ * They include GTT storage info, transaction info and statistical info.
+ */
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+	gtt_local_hash_entry	*entry;
+	MemoryContext			oldcontext;
+	gtt_relfilenode			*new_node = NULL;
+	Oid						relid = RelationGetRelid(rel);
+	int 					natts = 0;
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	/* First time through: initialize the hash table */
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		HASHCTL		ctl;
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_info_context =
+			AllocSetContextCreate(CacheMemoryContext,
+								"gtt info context",
+								ALLOCSET_DEFAULT_SIZES);
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		ctl.hcxt = gtt_info_context;
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+	}
+
+	Assert(CacheMemoryContext);
+	Assert(gtt_info_context);
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	entry = gtt_search_by_relid(relid, true);
+	if (!entry)
+	{
+		bool		found = false;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash,
+						(void *) &relid, HASH_ENTER, &found);
+
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+		entry->oldrelid = InvalidOid;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			/* record the on commit clause */
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			gtt_storage_checkin(relid);
+		}
+	}
+
+	/* record storage info relstat columnstats and transaction info to relfilenode list */
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	new_node->natts = 0;
+	new_node->attnum = NULL;
+	new_node->att_stat_tups = NULL;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* init column stats structure */
+	natts = RelationGetNumberOfAttributes(rel);
+	new_node->attnum = palloc0(sizeof(int) * natts);
+	new_node->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+	new_node->natts = natts;
+
+	/* only heap have transaction info */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+
+		/**/
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+
+	/* Registration callbacks are used to trigger cleanup during process exit */
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+/*
+ * Remove GTT information from local hash when transaction commit/rollback.
+ */
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	gtt_relfilenode *d_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+		return;
+	}
+
+	d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+		else if (entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+			gtt_relfilenode *gttnode2 = NULL;
+
+			/*
+			 * For cluster GTT rollback.
+			 * We need to roll back the exchange relfilenode operation.
+			 */
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+			Assert(gttnode2->relfilenode == rnode.relNode);
+			Assert(list_length(entry->relfilenode_list) == 1);
+			/* rollback switch relfilenode */
+			gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+									   entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+									   false);
+			/* clean up footprint */
+			entry2->oldrelid = InvalidOid;
+
+			/* temp relfilenode need free */
+			d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+			Assert(d_rnode);
+		}
+		else
+		{
+			/* rollback transaction */
+			if (entry->relfilenode_list == NIL)
+			{
+				if (entry->relkind == RELKIND_RELATION ||
+					entry->relkind == RELKIND_SEQUENCE)
+					gtt_storage_checkout(relid, false, isCommit);
+
+				hash_search(gtt_storage_local_hash,
+						(void *) &(relid), HASH_REMOVE, NULL);
+			}
+
+			return;
+		}
+	}
+
+	/* Clean up transaction info from Local order list and MyProc */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+
+		/* this is valid relfrozenxid */
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	/* delete relfilenode from rel entry */
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	gtt_free_statistics(d_rnode);
+
+	if (entry->relfilenode_list == NIL)
+	{
+		/* this means we truncate this GTT at current backend */
+
+		/* tell shared hash that current backend will no longer use this GTT */
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(relid, false, isCommit);
+
+		if (isCommit && entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+
+			/* commit transaction at cluster GTT, need clean up footprint */
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			entry2->oldrelid = InvalidOid;
+		}
+
+		hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_REMOVE, NULL);
+	}
+
+	return;
+}
+
+/*
+ * Check if current backend is using this GTT.
+ */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool found = false;
+	gtt_local_hash_entry *entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (!OidIsValid(relid))
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry)
+		found = true;
+
+	return found;
+}
+
+/*
+ * When backend exit, bulk cleaning all GTT storage and local buffer of this backend.
+ */
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS				status;
+	gtt_local_hash_entry		*entry;
+	SMgrRelation	*srels = NULL;
+	Oid				*relids = NULL;
+	char			*relkinds = NULL;
+	int			nrels = 0,
+				nfiles = 0,
+				maxrels = 0,
+				maxfiles = 0,
+				i = 0;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	/* Search all relfilenode for GTT in current backend */
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel;
+			RelFileNode		rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel = smgropen(rnode, MyBackendId);
+
+			if (maxfiles == 0)
+			{
+				maxfiles = 32;
+				srels = palloc(sizeof(SMgrRelation) * maxfiles);
+			}
+			else if (maxfiles <= nfiles)
+			{
+				maxfiles *= 2;
+				srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+			}
+
+			srels[nfiles++] = srel;
+		}
+
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			relids  = palloc(sizeof(Oid) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			relids  = repalloc(relids , sizeof(Oid) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		relkinds[nrels] = entry->relkind;
+		relids[nrels] = entry->relid;
+		nrels++;
+	}
+
+	/* drop local buffer and storage */
+	if (nfiles > 0)
+	{
+		smgrdounlinkall(srels, nfiles, false);
+		for (i = 0; i < nfiles; i++)
+			smgrclose(srels[i]);
+
+		pfree(srels);
+	}
+
+	if (nrels)
+	{
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+		for (i = 0; i < nrels; i++)
+		{
+			/* tell shared hash */
+			if (relkinds[i] == RELKIND_RELATION ||
+				relkinds[i] == RELKIND_SEQUENCE)
+				gtt_storage_checkout(relids[i], true, false);
+		}
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+		pfree(relids);
+		pfree(relkinds);
+	}
+
+	/* set to global area */
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;
+
+	return;
+}
+
+/*
+ * Update GTT relstats(relpage/reltuple/relallvisible)
+ * to local hash.
+ */
+void
+up_gtt_relstats(Oid relid,
+					BlockNumber num_pages,
+					double num_tuples,
+					BlockNumber num_all_visible_pages,
+					TransactionId relfrozenxid,
+					TransactionId relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (!OidIsValid(relid))
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (num_pages > 0 &&
+		gtt_rnode->relpages != (int32)num_pages)
+		gtt_rnode->relpages = (int32)num_pages;
+
+	if (num_tuples > 0 &&
+		gtt_rnode->reltuples != (float4)num_tuples)
+		gtt_rnode->reltuples = (float4)num_tuples;
+
+	/* only heap contain transaction information and relallvisible */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (num_all_visible_pages > 0 &&
+			gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+		{
+			gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+		}
+
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNextTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			/* set to local order list */
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			/* set to global area */
+			set_gtt_session_relfrozenxid();
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search GTT relstats(relpage/reltuple/relallvisible)
+ * from local has.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update GTT info(definition is same as pg_statistic)
+ * to local hash.
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	MemoryContext			oldcontext;
+	int						i = 0;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return;
+
+	Assert(entry->relid == reloid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (gtt_rnode->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	/* switch context to gtt_info_context for store tuple at heap_form_tuple */
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == 0)
+		{
+			gtt_rnode->attnum[i] = attnum;
+			break;
+		}
+		else if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			heap_freetuple(gtt_rnode->att_stat_tups[i]);
+			gtt_rnode->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < gtt_rnode->natts);
+	Assert(gtt_rnode->att_stat_tups[i] == NULL);
+	gtt_rnode->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search GTT statistic info(definition is same as pg_statistic)
+ * from local hash.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	int						i = 0;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return NULL;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return NULL;
+
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			return gtt_rnode->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Insert a RelfrozenXID into the list and keep the list in order.
+ */
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell		*cell;
+	int				i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Remove a RelfrozenXID from order list gtt_session_relfrozenxid_list.
+ */
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+/*
+ * Update of backend Level oldest relfrozenxid to MyProc.
+ * This makes each backend's oldest RelFrozenxID globally visible.
+ */
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->backend_gtt_frozenxid != gtt_frozenxid)
+		MyProc->backend_gtt_frozenxid = gtt_frozenxid;
+}
+
+/*
+ * Get GTT column level data statistics.
+ */
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo 	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate *tupstore;
+	HeapTuple		tuple;
+	Relation		rel = NULL;
+	Oid				reloid = PG_GETARG_OID(0);
+	int				attnum = PG_GETARG_INT32(1);
+	char			rel_persistence;
+	TupleDesc	  	tupdesc;
+	MemoryContext 	oldcontext;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	/* get data from local hash */
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, NoLock);
+	relation_close(pg_tatistic, AccessShareLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get GTT table level data statistics.
+ */
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate	*tupstore;
+	TupleDesc		tupdesc;
+	MemoryContext	oldcontext;
+	HeapTuple		tuple;
+	Oid				reloid = PG_GETARG_OID(0);
+	Oid				relnode = 0;
+	char			rel_persistence;
+	BlockNumber		relpages = 0;
+	BlockNumber		relallvisible = 0;
+	uint32			relfrozenxid = 0;
+	uint32			relminmxid = 0;
+	double			reltuples = 0;
+	Relation		rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[6];
+		bool	isnull[6];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get a list of backend pids that are currently using this GTT.
+ */
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	PGPROC			*proc = NULL;
+	Bitmapset		*map = NULL;
+	Tuplestorestate *tupstore;
+	TupleDesc		tupdesc;
+	MemoryContext	oldcontext;
+	HeapTuple		tuple;
+	Oid				reloid = PG_GETARG_OID(0);
+	char			rel_persistence;
+	Relation		rel = NULL;
+	pid_t			pid = 0;
+	int				backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	/* get data from share hash */
+	map = copy_active_gtt_bitmap(reloid);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			/* backendid map to process pid */
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get backend level oldest relfrozenxid of each backend using GTT in current database.
+ */
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate *tupstore;
+	int				*pids = NULL;
+	uint32			*xids = NULL;
+	TupleDesc		tupdesc;
+	MemoryContext	oldcontext;
+	HeapTuple		tuple;
+	int				num_xid = MaxBackends + 1;
+	int				i = 0;
+	int				j = 0;
+	uint32			oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+
+	/* Get backend level oldest relfrozenxid in all backend that in MyDatabaseId use GTT */
+	oldest = list_all_backend_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+/*
+ * In order to build the GTT index, force enable GTT'index.
+ */
+void
+force_enable_gtt_index(Relation index)
+{
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(RelationGetRelid(index)));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+/*
+ * Fix the local state of the GTT's index.
+ */
+void
+gtt_fix_index_backend_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid heapOid = index->rd_index->indrelid;
+
+	/* Must be GTT */
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	/*
+	 * If this GTT is not initialized in the current backend,
+	 * its index status is temporarily set to invalid(local relcache).
+	 */
+	if (gtt_storage_attached(heapOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+/*
+ * During the SQL initialization of the executor (InitPlan)
+ * Initialize storage of GTT GTT'indexes and build empty index.
+ */
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int			i;
+	Oid			toastrelid;
+
+	if (!(operation == CMD_UTILITY || operation == CMD_INSERT))
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	/* Each GTT is initialized once in each backend */
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	/* init heap storage */
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+
+		index_build(relation, index, info, true, false);
+	}
+
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+
+		/* init index storage */
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid			indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+			/* build empty index */
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+/*
+ * Release the data structure memory used to store GTT storage info.
+ */
+static void
+gtt_free_statistics(gtt_relfilenode *rnode)
+{
+	int i;
+
+	Assert(rnode);
+
+	for (i = 0; i < rnode->natts; i++)
+	{
+		if (rnode->att_stat_tups[i])
+		{
+			heap_freetuple(rnode->att_stat_tups[i]);
+			rnode->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (rnode->attnum)
+		pfree(rnode->attnum);
+
+	if (rnode->att_stat_tups)
+		pfree(rnode->att_stat_tups);
+
+	pfree(rnode);
+
+	return;
+}
+
+/*
+ * Get the current relfilenode of this GTT.
+ */
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
+/*
+ * For cluster GTT.
+ * Exchange new and old relfilenode, leave footprints ensure rollback capability.
+ */
+void
+gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint)
+{
+	gtt_local_hash_entry	*entry1;
+	gtt_local_hash_entry	*entry2;
+	gtt_relfilenode			*gtt_rnode1 = NULL;
+	gtt_relfilenode			*gtt_rnode2 = NULL;
+	MemoryContext			oldcontext;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry1 = gtt_search_by_relid(rel1, false);
+	gtt_rnode1 = gtt_search_relfilenode(entry1, relfilenode1, false);
+
+	entry2 = gtt_search_by_relid(rel2, false);
+	gtt_rnode2 = gtt_search_relfilenode(entry2, relfilenode2, false);
+
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+	entry1->relfilenode_list = list_delete_ptr(entry1->relfilenode_list, gtt_rnode1);
+	entry2->relfilenode_list = lappend(entry2->relfilenode_list, gtt_rnode1);
+
+	entry2->relfilenode_list = list_delete_ptr(entry2->relfilenode_list, gtt_rnode2);
+	entry1->relfilenode_list = lappend(entry1->relfilenode_list, gtt_rnode2);
+	MemoryContextSwitchTo(oldcontext);
+
+	if (footprint)
+	{
+		entry1->oldrelid = rel2;
+		entry2->oldrelid = rel1;
+	}
+
+	return;
+}
+
+/*
+ * Get a relfilenode used by this GTT during the transaction life cycle.
+ */
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok)
+{
+	gtt_relfilenode		*rnode = NULL;
+	ListCell			*lc;
+
+	Assert(entry);
+
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == relfilenode)
+		{
+			rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (!missing_ok && rnode == NULL)
+		elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+	return rnode;
+}
+
+/*
+ * Get one GTT info from local hash.
+ */
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry == NULL && !missing_ok)
+		elog(ERROR, "relid %u not found in local hash", relid);
+
+	return entry;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 0dca65d..e41b5a8 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f84616d..bb6a273 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -104,7 +105,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);
 
@@ -186,6 +187,17 @@ analyze_rel(Oid relid, RangeVar *relation,
 	}
 
 	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
+	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
 	if (RelationGetRelid(onerel) == StatisticRelationId)
@@ -587,14 +599,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]));
 		}
 
 		/*
@@ -1610,7 +1623,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;
@@ -1712,31 +1725,48 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/*
+		 * For global temporary table,
+		 * Update column statistic to localhash, not catalog.
+		 */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 096a06f..f75cb42 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/toasting.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
@@ -73,6 +74,12 @@ static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
 							bool verbose, bool *pSwapToastByContent,
 							TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
 static List *get_tables_to_cluster(MemoryContext cluster_context);
+static void gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables);
 
 
 /*---------------------------------------------------------------------------
@@ -391,6 +398,18 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
 	}
 
 	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap) &&
+		!gtt_storage_attached(RelationGetRelid(OldHeap)))
+	{
+		relation_close(OldHeap, AccessExclusiveLock);
+		pgstat_progress_end_command();
+		return;
+	}
+
+	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
 	 */
@@ -774,6 +793,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	BlockNumber num_pages;
 	int			elevel = verbose ? INFO : DEBUG2;
 	PGRUsage	ru0;
+	bool		is_gtt = false;
+	uint32		gtt_relfrozenxid = 0;
+	uint32		gtt_relminmxid = 0;
 
 	pg_rusage_init(&ru0);
 
@@ -787,6 +809,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	else
 		OldIndex = NULL;
 
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		is_gtt = true;
+
 	/*
 	 * Their tuple descriptors should be exactly alike, but here we only need
 	 * assume that they have the same number of columns.
@@ -854,20 +879,38 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 						  &OldestXmin, &FreezeXid, NULL, &MultiXactCutoff,
 						  NULL);
 
-	/*
-	 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
-	 * backwards, so take the max.
-	 */
-	if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
-		TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
-		FreezeXid = OldHeap->rd_rel->relfrozenxid;
+	if (is_gtt)
+	{
+		/* Gets transaction information for global temporary table from localhash. */
+		get_gtt_relstats(OIDOldHeap,
+					NULL, NULL, NULL,
+					&gtt_relfrozenxid, &gtt_relminmxid);
+
+		if (TransactionIdIsValid(gtt_relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, gtt_relfrozenxid))
+			FreezeXid = gtt_relfrozenxid;
+
+		if (MultiXactIdIsValid(gtt_relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, gtt_relminmxid))
+			MultiXactCutoff = gtt_relminmxid;
+	}
+	else
+	{
+		/*
+		 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
+		 * backwards, so take the max.
+		 */
+		if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
+			FreezeXid = OldHeap->rd_rel->relfrozenxid;
 
-	/*
-	 * MultiXactCutoff, similarly, shouldn't go backwards either.
-	 */
-	if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
-		MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
-		MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+		/*
+		 * MultiXactCutoff, similarly, shouldn't go backwards either.
+		 */
+		if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
+			MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+	}
 
 	/*
 	 * Decide whether to use an indexscan or seqscan-and-optional-sort to scan
@@ -935,6 +978,15 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	table_close(OldHeap, NoLock);
 	table_close(NewHeap, NoLock);
 
+	/* Update relstats of global temporary table to localhash. */
+	if (is_gtt)
+	{
+		up_gtt_relstats(RelationGetRelid(NewHeap), num_pages, num_tuples, 0,
+						InvalidTransactionId, InvalidMultiXactId);
+		CommandCounterIncrement();
+		return;
+	}
+
 	/* Update pg_class to reflect the correct values of pages and tuples. */
 	relRelation = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1371,10 +1423,22 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 	 * Swap the contents of the heap relations (including any toast tables).
 	 * Also set old heap's relfrozenxid to frozenXid.
 	 */
-	swap_relation_files(OIDOldHeap, OIDNewHeap,
+	if (newrelpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		Assert(!is_system_catalog);
+		/* For global temporary table modify data in localhash, not pg_class */
+		gtt_swap_relation_files(OIDOldHeap, OIDNewHeap,
+								(OIDOldHeap == RelationRelationId),
+								swap_toast_by_content, is_internal,
+								frozenXid, cutoffMulti, mapped_tables);
+	}
+	else
+	{
+		swap_relation_files(OIDOldHeap, OIDNewHeap,
 						(OIDOldHeap == RelationRelationId),
 						swap_toast_by_content, is_internal,
 						frozenXid, cutoffMulti, mapped_tables);
+	}
 
 	/*
 	 * If it's a system catalog, queue a sinval message to flush all catcaches
@@ -1582,3 +1646,146 @@ get_tables_to_cluster(MemoryContext cluster_context)
 
 	return rvs;
 }
+
+/*
+ * For global temporary table, storage information is stored in localhash,
+ * This function like swap_relation_files except that update storage information,
+ * in the localhash, not pg_class.
+ */
+static void
+gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables)
+{
+	Relation	relRelation;
+	Oid			relfilenode1,
+				relfilenode2;
+	Relation	rel1;
+	Relation	rel2;
+
+	relRelation = table_open(RelationRelationId, RowExclusiveLock);
+
+	rel1 = relation_open(r1, AccessExclusiveLock);
+	rel2 = relation_open(r2, AccessExclusiveLock);
+
+	relfilenode1 = gtt_fetch_current_relfilenode(r1);
+	relfilenode2 = gtt_fetch_current_relfilenode(r2);
+
+	Assert(OidIsValid(relfilenode1) && OidIsValid(relfilenode2));
+	gtt_switch_rel_relfilenode(r1, relfilenode1, r2, relfilenode2, true);
+
+	CacheInvalidateRelcache(rel1);
+	CacheInvalidateRelcache(rel2);
+
+	InvokeObjectPostAlterHookArg(RelationRelationId, r1, 0,
+								 InvalidOid, is_internal);
+	InvokeObjectPostAlterHookArg(RelationRelationId, r2, 0,
+								 InvalidOid, true);
+
+	if (rel1->rd_rel->reltoastrelid || rel2->rd_rel->reltoastrelid)
+	{
+		if (swap_toast_by_content)
+		{
+			if (rel1->rd_rel->reltoastrelid && rel2->rd_rel->reltoastrelid)
+			{
+				gtt_swap_relation_files(rel1->rd_rel->reltoastrelid,
+									rel2->rd_rel->reltoastrelid,
+									target_is_pg_class,
+									swap_toast_by_content,
+									is_internal,
+									frozenXid,
+									cutoffMulti,
+									mapped_tables);
+			}
+			else
+				elog(ERROR, "cannot swap toast files by content when there's only one");
+		}
+		else
+		{
+			ObjectAddress baseobject,
+						toastobject;
+			long		count;
+
+			if (IsSystemRelation(rel1))
+				elog(ERROR, "cannot swap toast files by links for system catalogs");
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel1->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel2->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+
+			/* Register new dependencies */
+			baseobject.classId = RelationRelationId;
+			baseobject.objectSubId = 0;
+			toastobject.classId = RelationRelationId;
+			toastobject.objectSubId = 0;
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r1;
+				toastobject.objectId = rel1->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r2;
+				toastobject.objectId = rel2->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+		}
+	}
+
+	if (swap_toast_by_content &&
+		rel1->rd_rel->relkind == RELKIND_TOASTVALUE &&
+		rel2->rd_rel->relkind == RELKIND_TOASTVALUE)
+	{
+		Oid			toastIndex1,
+					toastIndex2;
+
+		/* Get valid index for each relation */
+		toastIndex1 = toast_get_valid_index(r1,
+											AccessExclusiveLock);
+		toastIndex2 = toast_get_valid_index(r2,
+											AccessExclusiveLock);
+
+		gtt_swap_relation_files(toastIndex1,
+							toastIndex2,
+							target_is_pg_class,
+							swap_toast_by_content,
+							is_internal,
+							InvalidTransactionId,
+							InvalidMultiXactId,
+							mapped_tables);
+	}
+
+	relation_close(rel1, NoLock);
+	relation_close(rel2, NoLock);
+
+	table_close(relRelation, RowExclusiveLock);
+
+	RelationCloseSmgrByOid(r1);
+	RelationCloseSmgrByOid(r2);
+
+	CommandCounterIncrement();
+}
+
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 8c712c8..0a35f6f 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -290,7 +290,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP_ON_CURRENT_SESSION(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, whereClause,
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 2ed696d..7359500 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -23,6 +23,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "access/xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/copyfrom_internal.h"
 #include "commands/progress.h"
@@ -658,6 +659,9 @@ CopyFrom(CopyFromState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	/* Check and init global temporary table storage in current backend */
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	/*
 	 * Set up a ModifyTableState so we can let FDW(s) init themselves for
 	 * foreign-table result relation(s).
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 8bc652e..a5998ad 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -570,7 +570,7 @@ DefineIndex(Oid relationId,
 	 * is more efficient.  Do this before any use of the concurrent option is
 	 * done.
 	 */
-	if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+	if (stmt->concurrent && !RelpersistenceTsTemp(get_rel_persistence(relationId)))
 		concurrent = true;
 	else
 		concurrent = false;
@@ -2608,7 +2608,7 @@ ReindexIndex(RangeVar *indexRelation, ReindexParams *params, bool isTopLevel)
 	if (relkind == RELKIND_PARTITIONED_INDEX)
 		ReindexPartitions(indOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 persistence != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(persistence))
 		ReindexRelationConcurrently(indOid, params);
 	else
 	{
@@ -2717,7 +2717,7 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 	if (get_rel_relkind(heapOid) == RELKIND_PARTITIONED_TABLE)
 		ReindexPartitions(heapOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(get_rel_persistence(heapOid)))
 	{
 		result = ReindexRelationConcurrently(heapOid, params);
 
@@ -3132,7 +3132,7 @@ ReindexMultipleInternal(List *relids, ReindexParams *params)
 			   relkind != RELKIND_PARTITIONED_TABLE);
 
 		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			relpersistence != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(relpersistence))
 		{
 			ReindexParams newparams = *params;
 
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 34f2270..b8ea2ef 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -57,7 +57,10 @@ LockTableCommand(LockStmt *lockstmt)
 										  RangeVarCallbackForLockTable,
 										  (void *) &lockstmt->mode);
 
-		if (get_rel_relkind(reloid) == RELKIND_VIEW)
+		/* Lock table command ignores global temporary table. */
+		if (get_rel_persistence(reloid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+		else if (get_rel_relkind(reloid) == RELKIND_VIEW)
 			LockViewRecurse(reloid, lockstmt->mode, lockstmt->nowait, NIL);
 		else if (recurse)
 			LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait);
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 0415df9..39f82bc 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -275,8 +278,6 @@ ResetSequence(Oid seq_relid)
 	Buffer		buf;
 	HeapTupleData seqdatatuple;
 	HeapTuple	tuple;
-	HeapTuple	pgstuple;
-	Form_pg_sequence pgsform;
 	int64		startv;
 
 	/*
@@ -287,12 +288,7 @@ ResetSequence(Oid seq_relid)
 	init_sequence(seq_relid, &elm, &seq_rel);
 	(void) read_seq_tuple(seq_rel, &buf, &seqdatatuple);
 
-	pgstuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seq_relid));
-	if (!HeapTupleIsValid(pgstuple))
-		elog(ERROR, "cache lookup failed for sequence %u", seq_relid);
-	pgsform = (Form_pg_sequence) GETSTRUCT(pgstuple);
-	startv = pgsform->seqstart;
-	ReleaseSysCache(pgstuple);
+	startv = get_seqence_start_value(seq_relid);
 
 	/*
 	 * Copy the existing sequence tuple.
@@ -451,6 +447,15 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 
 	init_sequence(relid, &elm, &seqrel);
 
+	if (RELATION_IS_GLOBAL_TEMP(seqrel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(seqrel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary sequence %s when other backend attached it.",
+						RelationGetRelationName(seqrel))));
+	}
+
 	rel = table_open(SequenceRelationId, RowExclusiveLock);
 	seqtuple = SearchSysCacheCopy1(SEQRELID,
 								   ObjectIdGetDatum(relid));
@@ -611,7 +616,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +941,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1153,6 +1158,14 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	/* Initializes the storage for sequence which the global temporary table belongs. */
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1953,3 +1966,51 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+/*
+ * Get the startValue of the sequence from syscache.
+ */
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+/*
+ * Initialize sequence which global temporary table belongs.
+ */
+void
+gtt_init_seq(Relation rel)
+{
+	Datum		value[SEQ_COL_LASTCOL] = {0};
+	bool		null[SEQ_COL_LASTCOL] = {false};
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index ffb1308..feeda23 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -45,6 +45,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -558,6 +559,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
 static void ATExecAlterCollationRefreshVersion(Relation rel, List *coll);
+static OnCommitAction gtt_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -603,6 +605,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -614,7 +617,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * Check consistency of arguments
 	 */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !RelpersistenceTsTemp(stmt->relation->relpersistence))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -644,7 +647,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	if (RelpersistenceTsTemp(stmt->relation->relpersistence)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -745,6 +748,56 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* For global temporary table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* Check parent table */
+		if (inheritOids)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			table_close(relation, NoLock);
+		}
+
+		/* Check oncommit clause and save to reloptions */
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temporary table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1366,7 +1419,7 @@ RemoveRelations(DropStmt *drop)
 		 * relation persistence cannot be known without its OID.
 		 */
 		if (drop->concurrent &&
-			get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(get_rel_persistence(relOid)))
 		{
 			Assert(list_length(drop->objects) == 1 &&
 				   drop->removeType == OBJECT_INDEX);
@@ -1572,7 +1625,16 @@ ExecuteTruncate(TruncateStmt *stmt)
 		Relation	rel;
 		bool		recurse = rv->inh;
 		Oid			myrelid;
-		LOCKMODE	lockmode = AccessExclusiveLock;
+		LOCKMODE	lockmode;
+
+		/*
+		 * Truncate global temp table only cleans up the data in current backend,
+		 * only low-level locks are required.
+		 */
+		if (rv->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+		else
+			lockmode = AccessExclusiveLock;
 
 		myrelid = RangeVarGetRelidExtended(rv, lockmode,
 										   0, RangeVarCallbackForTruncate,
@@ -1837,6 +1899,14 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
 			continue;
 
 		/*
+		 * Skip the global temporary table that is not initialized for storage
+		 * in current backend.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			continue;
+
+		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
 		 * a new relfilenode in the current (sub)transaction, then we can just
@@ -3788,6 +3858,16 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temporary table only current backend use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -5111,6 +5191,42 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot rewrite temporary tables of other sessions")));
 
+			if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+			{
+				if (tab->chgPersistence)
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot change global temporary table persistence setting")));
+
+				/*
+				 * The storage for the global temporary table needs to be initialized
+				 * before rewrite table.
+				 */
+				if(!gtt_storage_attached(tab->relid))
+				{
+					ResultRelInfo *resultRelInfo;
+					MemoryContext oldcontext;
+					MemoryContext ctx_alter_gtt;
+
+					ctx_alter_gtt = AllocSetContextCreate(CurrentMemoryContext,
+											"gtt alter table", ALLOCSET_DEFAULT_SIZES);
+					oldcontext = MemoryContextSwitchTo(ctx_alter_gtt);
+
+					resultRelInfo = makeNode(ResultRelInfo);
+					InitResultRelInfo(resultRelInfo, OldHeap,
+									1, NULL, 0);
+					if (resultRelInfo->ri_RelationDesc->rd_rel->relhasindex &&
+						resultRelInfo->ri_IndexRelationDescs == NULL)
+						ExecOpenIndices(resultRelInfo, false);
+
+					init_gtt_storage(CMD_UTILITY, resultRelInfo);
+					ExecCloseIndices(resultRelInfo);
+
+					MemoryContextSwitchTo(oldcontext);
+					MemoryContextDelete(ctx_alter_gtt);
+				}
+			}
+
 			/*
 			 * Select destination tablespace (same as original unless user
 			 * requested a change)
@@ -8575,6 +8691,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -13078,6 +13200,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -13276,6 +13401,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temporary table");
+
 	/* Check first if relation can be moved to new tablespace */
 	if (!CheckRelationTableSpaceMove(rel, newTableSpace))
 	{
@@ -13580,7 +13708,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,
@@ -14965,6 +15093,7 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
 	switch (rel->rd_rel->relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -17641,3 +17770,40 @@ ATExecAlterCollationRefreshVersion(Relation rel, List *coll)
 	index_update_collation_versions(rel->rd_id, get_collation_oid(coll, false));
 	CacheInvalidateRelcache(rel);
 }
+
+/*
+ * Parse the on commit clause for the temporary table
+ */
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			bool	res = false;
+			char	*sval = defGetString(def);
+
+			/* It has to be a Boolean value */
+			if (!parse_bool(sval, &res))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
+
+			if (res)
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index c064352..02af171 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"
@@ -1243,6 +1244,22 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = RELATION_IS_GLOBAL_TEMP(relation);
+
+	 /* For global temporary table */
+	if (is_gtt)
+	{
+		/* Store relation statistics and transaction information to the localhash */
+		up_gtt_relstats(RelationGetRelid(relation),
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+
+		/* Update relation statistics to local relcache */
+		relation->rd_rel->relpages = (int32) num_pages;
+		relation->rd_rel->reltuples = (float4) num_tuples;
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1256,17 +1273,23 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (!is_gtt &&
+		pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (!is_gtt &&
+		pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (!is_gtt &&
+		pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1311,7 +1334,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNextTransactionId(),
@@ -1322,7 +1346,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1431,6 +1456,13 @@ vac_update_datfrozenxid(void)
 		}
 
 		/*
+		 * The relfrozenxid for a global temporary talble is stored in localhash,
+		 * not pg_class, See list_all_session_gtt_frozenxids()
+		 */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
+		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
 		 * to not be set (i.e. set to their respective Invalid*Id)
@@ -1487,6 +1519,43 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/* If enable global temporary table */
+	if (max_active_gtt > 0)
+	{
+		TransactionId	safe_age;
+		/*  */
+		TransactionId	oldest_gtt_frozenxid =
+			list_all_backend_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid))
+		{
+			safe_age = oldest_gtt_frozenxid + vacuum_gtt_defer_check_age;
+			if (safe_age < FirstNormalTransactionId)
+				safe_age += FirstNormalTransactionId;
+
+			/*
+			 * We tolerate that the minimum age of gtt is less than
+			 * the minimum age of conventional tables, otherwise it will
+			 * throw warning message.
+			 */
+			if (TransactionIdIsNormal(safe_age) &&
+				TransactionIdPrecedes(safe_age, newFrozenXid))
+			{
+				ereport(WARNING,
+					(errmsg("global temp table oldest relfrozenxid %u is the oldest in the entire db",
+							oldest_gtt_frozenxid),
+					 errdetail("The oldest relfrozenxid in pg_class is %u", newFrozenXid),
+					 errhint("If they differ greatly, please consider cleaning up the data in global temp table.")));
+			}
+
+			/*
+			 * We need to ensure that the clog required by gtt is not cleand.
+			 */
+			if (TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+				newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1839,6 +1908,19 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 	}
 
 	/*
+	 * Skip those global temporary table that are not initialized in
+	 * current backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
+	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
 	 * contents are probably not up-to-date on disk.  (We don't throw a
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index f2642db..e088101 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -530,6 +530,12 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	/* Global temporary table are not sensible. */
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 0648dd8..b30155a 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -773,6 +773,10 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		/* This is one kind of temp table */
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index b8da4c5..1110932 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
@@ -607,6 +608,9 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	/* Init storage for partitioned global temporary table in current backend */
+	init_gtt_storage(mtstate->operation, leaf_part_rri);
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 2993ba4..78ebadd 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,6 +42,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -2429,6 +2430,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			ExecOpenIndices(resultRelInfo,
 							node->onConflictAction != ONCONFLICT_NONE);
 
+		/* Init storage for global temporary table in current backend */
+		init_gtt_storage(operation, resultRelInfo);
+
 		/*
 		 * If this is an UPDATE and a BEFORE UPDATE trigger is present, the
 		 * trigger itself might modify the partition-key values. So arrange
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index d73ac56..46a8b32 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -48,7 +48,7 @@
 #include "partitioning/partprune.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/lsyscache.h"
-
+#include "utils/rel.h"
 
 /* results of subquery_is_pushdown_safe */
 typedef struct pushdown_safety_info
@@ -618,7 +618,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			if (RelpersistenceTsTemp(get_rel_persistence(rte->relid)))
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 424d25c..ad18383 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6444,7 +6444,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
-	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+	if (RELATION_IS_TEMP(heap) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index c5947fa..2ba35c0 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -28,6 +28,7 @@
 #include "catalog/catalog.h"
 #include "catalog/heap.h"
 #include "catalog/index.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
@@ -230,6 +231,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temporary table in current backend */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 0f3a70c..a2eb919 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2816,6 +2816,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 652be0b..0e302b1 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3362,17 +3362,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; }
@@ -11507,19 +11501,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 ca02982..4fc46eb 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3652,3 +3653,53 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * Like function isQueryUsingTempRelation_walker
+ * return true if any relation underlying
+ * the query is a global temporary table.
+ */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* Check if the query uses global temporary table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index d56f81c..9f8a125 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -457,6 +457,13 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->options = seqoptions;
 
 	/*
+	 * If a sequence is bound to a global temporary table, then the sequence
+	 * must been "global temporary"
+	 */
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
+	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
 	 * clause, the "redundant options" error will point to their occurrence,
@@ -3232,6 +3239,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		cxt.isforeign = false;
 	}
 	cxt.relation = stmt->relation;
+	/* Sets the table persistence to the context */
+	cxt.relation->relpersistence = RelationGetRelPersistence(rel);
 	cxt.rel = rel;
 	cxt.inhRelations = NIL;
 	cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 23ef23c..e603869 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2111,6 +2111,14 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/*
+			 * Aotuvacuum cannot vacuum the private data stored in each backend
+			 * that belongs to global temporary table, so skip them.
+			 */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2177,7 +2185,7 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		if (RelpersistenceTsTemp(classForm->relpersistence))
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 852138f..68eb5f5 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -2866,6 +2867,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * Returns 0 if this global temporary table is not initialized in current
+	 * backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 3e4ec53..e9092cd 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -22,6 +22,7 @@
 #include "access/subtrans.h"
 #include "access/syncscan.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -150,6 +151,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
@@ -222,6 +224,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* For global temporary table shared hashtable */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 4fc6ffb..6ea2e1f 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -65,6 +65,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -5070,3 +5071,78 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temporary table.
+ */
+int
+list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct *arrayP = procArray;
+	TransactionId result = InvalidTransactionId;
+	int			index;
+	uint8			flags = 0;
+	int			i = 0;
+
+	/* return 0 if feature is disabled */
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+		*n = 0;
+	}
+
+	/* Disable in standby node */
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		uint8           statusFlags = ProcGlobal->statusFlags[index];
+
+		if (statusFlags & flags)
+			continue;
+
+		/* Fetch all backend that is belonging to MyDatabaseId */
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->backend_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->backend_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->backend_gtt_frozenxid, result))
+				result = proc->backend_gtt_frozenxid;
+
+			/* save backend pid and backend level oldest relfrozenxid */
+			if (max_size > 0)
+			{
+				pids[i] = proc->pid;
+				xids[i] = proc->backend_gtt_frozenxid;
+				i++;
+			}
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (max_size > 0)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index adf19ab..c3420e0 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -175,7 +175,9 @@ static const char *const BuiltinTrancheNames[] = {
 	/* LWTRANCHE_PARALLEL_APPEND: */
 	"ParallelAppend",
 	/* LWTRANCHE_PER_XACT_PREDICATE_LIST: */
-	"PerXactPredicateList"
+	"PerXactPredicateList",
+	/* LWTRANCHE_GTT_CTL */
+	"GlobalTempTableControl"
 };
 
 StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 897045e..79da250 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -393,6 +393,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;	/* init backend level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
@@ -575,6 +576,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;	/* init backend level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 64cdaa4..ef980e5 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -982,6 +982,13 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/*
+			 * For global temporary table ,each backend has its own storage,
+			 * also only sees its own storage. Use Backendid to identify them.
+			 */
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 52314d3..b7e8200 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -108,6 +108,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -4889,12 +4890,26 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								/* For global temporary table, get statistic data from localhash */
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -5019,15 +5034,28 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6450,6 +6478,7 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	{
 		/* Simple variable --- look to stats for the underlying table */
 		RangeTblEntry *rte = planner_rt_fetch(index->rel->relid, root);
+		char	rel_persistence = get_rel_persistence(rte->relid);
 
 		Assert(rte->rtekind == RTE_RELATION);
 		relid = rte->relid;
@@ -6467,6 +6496,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6478,6 +6515,8 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	}
 	else
 	{
+		char	rel_persistence = get_rel_persistence(index->indexoid);
+
 		/* Expression --- maybe there are stats for the index itself */
 		relid = index->indexoid;
 		colnum = 1;
@@ -6493,6 +6532,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7411,6 +7458,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		/* attempt to lookup stats in relation for this index column */
 		if (attnum != 0)
 		{
+			char	rel_persistence = get_rel_persistence(rte->relid);
+
 			/* Simple variable -- look to stats for the underlying table */
 			if (get_relation_stats_hook &&
 				(*get_relation_stats_hook) (root, rte, attnum, &vardata))
@@ -7423,6 +7472,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7435,6 +7493,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		}
 		else
 		{
+			char	rel_persistence = get_rel_persistence(index->indexoid);
+
 			/*
 			 * Looks like we've found an expression column in the index. Let's
 			 * see if there's any stats for it.
@@ -7454,6 +7514,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 6bba5f8..fa81808 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -3113,6 +3114,19 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		/* For global temporary table, get statistic data from localhash */
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 7ef510c..61fa8a3 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -66,6 +66,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1144,6 +1145,28 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+
+				/* For global temporary table, get relstat data from localhash */
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+
+				/* And put them to local relcache */
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1198,6 +1221,8 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			/* The state of the global temporary table's index may need to be set */
+			gtt_fix_index_backend_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -1325,7 +1350,22 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+			/*
+			 * For global temporary table, get the latest relfilenode
+			 * from localhash and put it in relcache.
+			 */
+			if (OidIsValid(newrelnode) &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2267,6 +2307,9 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		/* The state of the global temporary table's index may need to be set */
+		gtt_fix_index_backend_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3493,6 +3536,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3600,28 +3647,38 @@ void
 RelationSetNewRelfilenode(Relation relation, char persistence)
 {
 	Oid			newrelfilenode;
-	Relation	pg_class;
-	HeapTuple	tuple;
+	Relation	pg_class = NULL;
+	HeapTuple	tuple = NULL;
 	Form_pg_class classform;
 	MultiXactId minmulti = InvalidMultiXactId;
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
+	/*
+	 * For global temporary table, storage information for the table is
+	 * maintained locally, not in catalog.
+	 */
+	bool		update_catalog = !RELATION_IS_GLOBAL_TEMP(relation);
 
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
 
-	/*
-	 * Get a writable copy of the pg_class tuple for the given relation.
-	 */
-	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+	memset(&classform, 0, sizeof(classform));
 
-	tuple = SearchSysCacheCopy1(RELOID,
-								ObjectIdGetDatum(RelationGetRelid(relation)));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "could not find tuple for relation %u",
-			 RelationGetRelid(relation));
-	classform = (Form_pg_class) GETSTRUCT(tuple);
+	if (update_catalog)
+	{
+		/*
+		 * Get a writable copy of the pg_class tuple for the given relation.
+		 */
+		pg_class = table_open(RelationRelationId, RowExclusiveLock);
+
+		tuple = SearchSysCacheCopy1(RELOID,
+									ObjectIdGetDatum(RelationGetRelid(relation)));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u",
+				 RelationGetRelid(relation));
+		classform = (Form_pg_class) GETSTRUCT(tuple);
+	}
 
 	/*
 	 * Schedule unlinking of the old storage at transaction commit.
@@ -3647,7 +3704,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 				/* handle these directly, at least for now */
 				SMgrRelation srel;
 
-				srel = RelationCreateStorage(newrnode, persistence);
+				srel = RelationCreateStorage(newrnode, persistence, relation);
 				smgrclose(srel);
 			}
 			break;
@@ -3667,6 +3724,18 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 			break;
 	}
 
+	/* For global temporary table */
+	if (!update_catalog)
+	{
+		Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+		Assert(RELATION_IS_GLOBAL_TEMP(relation));
+		Assert(!RelationIsMapped(relation));
+
+		/* Make cache invalid and set new relnode to local cache. */
+		CacheInvalidateRelcache(relation);
+		relation->rd_node.relNode = relnode;
+	}
 	/*
 	 * If we're dealing with a mapped index, pg_class.relfilenode doesn't
 	 * change; instead we have to send the update to the relation mapper.
@@ -3676,7 +3745,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	 * possibly-inaccurate values of relpages etc, but those will be fixed up
 	 * later.
 	 */
-	if (RelationIsMapped(relation))
+	else if (RelationIsMapped(relation))
 	{
 		/* This case is only supported for indexes */
 		Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3722,9 +3791,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 		CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 	}
 
-	heap_freetuple(tuple);
+	if (update_catalog)
+	{
+		heap_freetuple(tuple);
 
-	table_close(pg_class, RowExclusiveLock);
+		table_close(pg_class, RowExclusiveLock);
+	}
 
 	/*
 	 * Make the pg_class row change or relation map change visible.  This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 855076b..e06fa3c 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -40,6 +40,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "commands/prepare.h"
 #include "commands/trigger.h"
@@ -146,6 +147,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temporary table feature.
+ * table schema are still saved in catalog.
+ *
+ * num > 0 means allows the database to manage multiple active tables at the same time.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2068,6 +2081,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."),
@@ -2597,6 +2619,16 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"vacuum_gtt_defer_check_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("The defer check age of GTT, used to check expired data after vacuum."),
+			NULL
+		},
+		&vacuum_gtt_defer_check_age,
+		10000, 0, 1000000,
+		NULL, NULL, NULL
+	},
+
 	/*
 	 * See also CheckRequiredParameterValues() if this parameter changes
 	 */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index eb988d7..8bb3bef 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2434,6 +2434,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
 		dopt->no_unlogged_table_data)
 		return;
 
+	/* Don't dump data in global temporary table/sequence */
+	if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		return;
+
 	/* Check that the data is not explicitly excluded */
 	if (simple_oid_list_member(&tabledata_exclude_oids,
 							   tbinfo->dobj.catId.oid))
@@ -15750,6 +15754,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
 		char	   *foreign = "";
+		char	   *table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15803,9 +15808,15 @@ dumpTableSchema(Archive *fout, const 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);
 
@@ -16170,13 +16181,22 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		}
 
 		/*
+		 * Transaction information for the global temporary table is not stored
+		 * in the pg_class.
+		 */
+		if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			Assert(tbinfo->frozenxid == 0);
+			Assert(tbinfo->minmxid == 0);
+		}
+		/*
 		 * In binary_upgrade mode, arrange to restore the old relfrozenxid and
 		 * relminmxid of all vacuumable relations.  (While vacuum.c processes
 		 * TOAST tables semi-independently, here we see them only as children
 		 * of other relations; so this "if" lacks RELKIND_TOASTVALUE, and the
 		 * child toast table is handled below.)
 		 */
-		if (dopt->binary_upgrade &&
+		else if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
 			 tbinfo->relkind == RELKIND_MATVIEW))
 		{
@@ -17172,6 +17192,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	PQExpBuffer query = createPQExpBuffer();
 	PQExpBuffer delqry = createPQExpBuffer();
 	char	   *qseqname;
+	bool	   global_temp_seq = false;
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
@@ -17181,9 +17202,12 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
-						  "FROM pg_catalog.pg_sequence "
-						  "WHERE seqrelid = '%u'::oid",
+						  "seqcache, seqcycle, "
+						  "c.relpersistence "
+						  "FROM pg_catalog.pg_sequence s, "
+						  "pg_catalog.pg_class c "
+						  "WHERE seqrelid = '%u'::oid "
+						  "and s.seqrelid = c.oid",
 						  tbinfo->dobj.catId.oid);
 	}
 	else if (fout->remoteVersion >= 80400)
@@ -17228,6 +17252,9 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
 
+	if (fout->remoteVersion >= 140000)
+		global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
 	if (strcmp(seqtype, "smallint") == 0)
@@ -17305,9 +17332,13 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	}
 	else
 	{
-		appendPQExpBuffer(query,
-						  "CREATE SEQUENCE %s\n",
-						  fmtQualifiedDumpable(tbinfo));
+		appendPQExpBuffer(query, "CREATE ");
+
+		if (global_temp_seq)
+			appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+		appendPQExpBuffer(query, "SEQUENCE %s\n",
+								  fmtQualifiedDumpable(tbinfo));
 
 		if (strcmp(seqtype, "bigint") != 0)
 			appendPQExpBuffer(query, "    AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 43fc297..ff33906 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -86,7 +86,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -166,7 +166,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 5d9a26c..2de11d5 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -447,8 +449,17 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
 			 "         ON c.relnamespace = n.oid "
 			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
+			 CppAsString2(RELKIND_MATVIEW) ") AND ");
+
+	if (skip_gtt)
+	{
+		/* exclude global temp tables */
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+			"    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND ");
+	}
+
 	/* exclude possible orphaned temp tables */
+	snprintf(query + strlen(query), sizeof(query) - strlen(query),
 			 "    ((n.nspname !~ '^pg_temp_' AND "
 			 "      n.nspname !~ '^pg_toast_temp_' AND "
 			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index e23b8ca..729a9c6 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -407,7 +407,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -638,7 +638,10 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+			/* exclude global temp tables */
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -649,7 +652,10 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+		/* exclude global temp tables */
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 919a784..486d01f 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -388,7 +388,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 20af5a9..d0a581e 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3758,7 +3758,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ecdb8d7..b456e1d 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1057,6 +1057,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2488,6 +2490,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2696,6 +2701,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 6ce480b..a0ccfb3 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -59,7 +59,8 @@ extern Relation heap_create(const char *relname,
 							bool mapped_relation,
 							bool allow_system_table_mods,
 							TransactionId *relfrozenxid,
-							MultiXactId *relminmxid);
+							MultiXactId *relminmxid,
+							bool skip_create_storage);
 
 extern Oid	heap_create_with_catalog(const char *relname,
 									 Oid relnamespace,
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 3e37729..67652c2 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -175,6 +175,7 @@ DECLARE_INDEX(pg_class_tblspc_relfilenode_index, 3455, on pg_class using btree(r
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 93393fc..89490f3 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5636,6 +5636,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '4572',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4573',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4574',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4575',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 0ab32b4..92e9f8b 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,7 +22,7 @@
 /* GUC variables */
 extern int	wal_skip_threshold;
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationPreTruncate(Relation rel);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..d48162c
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,46 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern int		vacuum_gtt_defer_check_age;
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Oid relid,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void force_enable_gtt_index(Relation index);
+extern void gtt_fix_index_backend_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+extern void gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index 40544dd..7b66d80 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 5dbe5ba..b215561 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -119,4 +119,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 359b749..e724f7e 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -399,6 +399,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index a8f052e..4b4ed1a 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -189,6 +189,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_SHARED_TIDBITMAP,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_PER_XACT_PREDICATE_LIST,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 2fd1ff0..7ccb4dd 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -157,6 +157,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId backend_gtt_frozenxid;	/* backend level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index b01fa52..8efffa5 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -94,4 +94,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 5004ee4..e03f02d 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -283,6 +283,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 10b6398..4719c2e 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	struct SMgrRelationData *rd_smgr;	/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -306,6 +306,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -571,11 +572,13 @@ typedef struct ViewOptions
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -583,6 +586,7 @@ typedef struct ViewOptions
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -595,6 +599,30 @@ typedef struct ViewOptions
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP_ON_CURRENT_SESSION
+ *		Test a rel is either local temp relation of this session
+ *		or global temp relation.
+ */
+#define RELATION_IS_TEMP_ON_CURRENT_SESSION(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is local temp relation or global temporary relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RelpersistenceTsTemp
+ *		Test a relpersistence is local temp relation or global temporary relation.
+ */
+#define RelpersistenceTsTemp(relpersistence) \
+	(relpersistence == RELPERSISTENCE_TEMP || \
+	 relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -638,6 +666,19 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* For global temporary table */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/* Get on commit clause value only for global temporary table */
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+/* Get relpersistence for relation */
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..ca2d135
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,14 @@
+reset search_path;
+select pg_sleep(5);
+ pg_sleep 
+----------
+ 
+(1 row)
+
+drop schema gtt cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
+drop cascades to table gtt.gtt_with_seq
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..0ac9e22
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,381 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+ n 
+---
+ 9
+(1 row)
+
+commit;
+-- 0 row
+select * from gtt6;
+ n 
+---
+(0 rows)
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temporary table
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temporary table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  could not create global temporary table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR:  insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count 
+-------
+     1
+(1 row)
+
+select count(*) from orders;
+ count 
+-------
+     0
+(1 row)
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  2 |  3
+(2 rows)
+
+commit;
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+select * from gtt_s_2 order by c1;
+ c1 | c2 
+----+----
+  1 |  2
+  2 |  4
+(2 rows)
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 1)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 300)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 33 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test_new
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.gtt6
+drop cascades to table gtt_function.foo
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to sequence gtt_function.seq_1
+drop cascades to table gtt_function.gtt_s_1
+drop cascades to table gtt_function.gtt_s_2
+drop cascades to table gtt_function.gt1
+drop cascades to table gtt_function.gtt_test1
+drop cascades to table gtt_function.gtt_test2
+drop cascades to table gtt_function.gtt_test3
+drop cascades to table gtt_function.gtt_test4
+drop cascades to table gtt_function.gtt_test5
+drop cascades to table gtt_function.gtt_test6
+drop cascades to table gtt_function.gtt_test7
+drop cascades to table gtt_function.gtt_test8
+drop cascades to table gtt_function.gtt_test9
+drop cascades to table gtt_function.gtt_test10
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..0646aae
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,90 @@
+set search_path=gtt,sys;
+select nextval('gtt_with_seq_c2_seq');
+ nextval 
+---------
+       1
+(1 row)
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..0fccf6b
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,343 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+rollback;
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+commit;
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          114688 |                 614400
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |            49152 |         49152 |                  49152
+(2 rows)
+
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2000
+(1 row)
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+ c1 | c2 
+----+----
+  1 |  1
+(1 row)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..8c0c376
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,10 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..4420fdb
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,80 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+ gtt_stats  | gtt_pkey  |        1 |         0 |             0
+(2 rows)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+reindex table gtt;
+reindex index gtt_pkey;
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+ gtt_stats  | gtt_pkey  |       30 |     10000 |             0
+(2 rows)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 9b12cc1..712f942 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1359,6 +1359,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e280198..774a0b6 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -125,3 +125,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..2c8e586
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,8 @@
+
+
+reset search_path;
+
+select pg_sleep(5);
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..fd8b4d3
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,253 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+commit;
+-- 0 row
+select * from gtt6;
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+commit;
+select * from gtt_s_1 order by c1;
+select * from gtt_s_2 order by c1;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d05745e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,44 @@
+
+
+set search_path=gtt,sys;
+
+select nextval('gtt_with_seq_c2_seq');
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..81a6039
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,154 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+rollback;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+commit;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+select count(*) from gtt_t_kenyon;
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..dbe84d1
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,19 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..d61b0ff
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,46 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+reindex table gtt;
+
+reindex index gtt_pkey;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
#300Pavel Stehule
pavel.stehule@gmail.com
In reply to: wenjing (#299)
Re: [Proposal] Global temporary tables

Hi

st 17. 3. 2021 v 12:59 odesílatel wenjing <wjzeng2012@gmail.com> napsal:

ok

The cause of the problem is that the name of the dependent function
(readNextTransactionID) has changed. I fixed it.

This patch(V43) is base on 9fd2952cf4920d563e9cea51634c5b364d57f71a

Wenjing

I tested this patch and make check-world fails

make[2]: Vstupuje se do adresáře
„/home/pavel/src/postgresql.master/src/test/recovery“
rm -rf '/home/pavel/src/postgresql.master/src/test/recovery'/tmp_check
/usr/bin/mkdir -p
'/home/pavel/src/postgresql.master/src/test/recovery'/tmp_check
cd . && TESTDIR='/home/pavel/src/postgresql.master/src/test/recovery'
PATH="/home/pavel/src/postgresql.master/tmp_install/usr/local/pgsql/master/bin:$PATH"
LD_LIBRARY_PATH="/home/pavel/src/postgresql.master/tmp_install/usr/local/pgsql/master/lib"
PGPORT='65432'
PG_REGRESS='/home/pavel/src/postgresql.master/src/test/recovery/../../../src/test/regress/pg_regress'
REGRESS_SHLIB='/home/pavel/src/postgresql.master/src/test/regress/regress.so'
/usr/bin/prove -I ../../../src/test/perl/ -I . t/*.pl
t/001_stream_rep.pl .................. ok
t/002_archiving.pl ................... ok
t/003_recovery_targets.pl ............ ok
t/004_timeline_switch.pl ............. ok
t/005_replay_delay.pl ................ ok
t/006_logical_decoding.pl ............ ok
t/007_sync_rep.pl .................... ok
t/008_fsm_truncation.pl .............. ok
t/009_twophase.pl .................... ok
t/010_logical_decoding_timelines.pl .. ok
t/011_crash_recovery.pl .............. ok
t/012_subtransactions.pl ............. ok
t/013_crash_restart.pl ............... ok
t/014_unlogged_reinit.pl ............. ok
t/015_promotion_pages.pl ............. ok
t/016_min_consistency.pl ............. ok
t/017_shm.pl ......................... skipped: SysV shared memory not
supported by this platform
t/018_wal_optimize.pl ................ ok
t/019_replslot_limit.pl .............. ok
t/020_archive_status.pl .............. ok
t/021_row_visibility.pl .............. ok
t/022_crash_temp_files.pl ............ 1/9
# Failed test 'one temporary file'
# at t/022_crash_temp_files.pl line 231.
# got: '0'
# expected: '1'
t/022_crash_temp_files.pl ............ 9/9 # Looks like you failed 1 test
of 9.
t/022_crash_temp_files.pl ............ Dubious, test returned 1 (wstat 256,
0x100)
Failed 1/9 subtests
t/023_pitr_prepared_xact.pl .......... ok

Test Summary Report
-------------------
t/022_crash_temp_files.pl (Wstat: 256 Tests: 9 Failed: 1)
Failed test: 8
Non-zero exit status: 1
Files=23, Tests=259, 115 wallclock secs ( 0.21 usr 0.06 sys + 28.57 cusr
18.01 csys = 46.85 CPU)
Result: FAIL
make[2]: *** [Makefile:19: check] Chyba 1
make[2]: Opouští se adresář
„/home/pavel/src/postgresql.master/src/test/recovery“
make[1]: *** [Makefile:49: check-recovery-recurse] Chyba 2
make[1]: Opouští se adresář „/home/pavel/src/postgresql.master/src/test“
make: *** [GNUmakefile:71: check-world-src/test-recurse] Chyba 2

Regards

Pavel

#301Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#300)
Re: [Proposal] Global temporary tables

Hi

I wrote simple benchmarks. I checked the possible slowdown of connections
to postgres when GTT is used.

/usr/local/pgsql/master/bin/pgbench -c 10 -C -f script4.sql -t 1000

script has one line just with INSERT or SELECT LIMIT 1;

PATCH
insert to global temp table (with connect) -- 349 tps (10 clients 443tps)
select from gtt (with connects) -- 370 tps (10 clients 446tps)
insert to normal table (with connect) - 115 tps (10 clients 417 tps)
select from normal table (with connect) -- 358 (10 clients 445 tps)

MASTER
insert to temp table (with connect) -- 58 tps (10 clients 352 tps) -- after
test pg_attribute bloated to 11MB
insert into normal table (with connect) -- 118 tps (10 clients 385)
select from normal table (with connect) -- 346 tps (10 clients 449)

The measurement doesn't show anything interesting - it is not possible to
see the impact of usage of GTT on connect time.

It is interesting to see the overhead of local temp tables against global
temp tables - the performance is about 6x worse, and there is a significant
bloat of the pg_attribute table. And the tested table had only one column.
So an idea or concept of global temp tables is very good, and
implementation looks well (from performance perspective).

I didn't check the code yet, I just tested behaviour and I think it is very
satisfiable for the first stage and first release. The patch is long now,
and for the first step is good to stop in implemented features.

Next steps should be supporting DDL for actively used GTT tables. This
topic is pretty complex, there are possible more scenarios. I think so GTT
behaviour should be the same like behaviour of normal tables (by default) -
but I see an advantage of other possibilities, so I don't want to open the
discussion about this topic now. Current implementation should not block
any possible implementations in future.

Regards

Pavel

#302Andrew Dunstan
andrew@dunslane.net
In reply to: wenjing (#299)
1 attachment(s)
Re: [Proposal] Global temporary tables

On 3/17/21 7:59 AM, wenjing wrote:

ok

The cause of the problem is that the name of the dependent function
(readNextTransactionID) has changed. I fixed it.

This patch(V43) is base on 9fd2952cf4920d563e9cea51634c5b364d57f71a

Wenjing

I have fixed this patch so that

a) it applies cleanly

b) it uses project best practice for catalog Oid assignment.

However, as noted elsewhere it fails the recovery TAP test.

I also note this:

diff --git a/src/test/regress/parallel_schedule
b/src/test/regress/parallel_schedule
index 312c11a4bd..d44fa62f4e 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -129,3 +129,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy
load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean

Tests that need to run in parallel should use either the isolation
tester framework (which is explicitly for testing things concurrently)
or the TAP test framework.

Adding six test files to the regression test suite for this one feature
is not a good idea. You should have one regression test script ideally,
and it should be added as appropriate to both the parallel and serial
schedules (and not at the end). Any further tests should be added using
the other frameworks mentioned.

cheers

andrew

--

Andrew Dunstan
EDB: https://www.enterprisedb.com

Attachments:

global_temporary_table_v44-pg14.patch.gzapplication/gzip; name=global_temporary_table_v44-pg14.patch.gzDownload
��w``global_temporary_table_v44-pg14.patch�[�w��������9i0c��9����y/��I��8B*����T����}�{�J������IXT���|w��|_��L8�*u���(c��q]����DQ�2LY������7v��'?���?�J�h�j9�������Xt����q�����W�5�o_�/��i�'��9x�2'\�;N�I
z����"S��_�������I�Y��x�������7�2I��Td�p���.VT&cw%i2
e��"�K1���LF�$u�����y�J*q?wR����L?��ROr���-�s����_���J�	m3�&�e&'i��^5u���D=F���w����x�����������Q~t;�_�/o���������jh��!Xj�(��,Ii7b����F�?���?�j��v�,��n�yJ���2��75�I�Y�������-#Q<h����DX�J�z'��^�cY#sTf�,�T��3b�]hVWq��g���������7����Y���g��wc��9G]+���� }�W6���c��)�A�<�2K�w��&2&i{;��d�E�Xx�����)���(��w��Abg�~��[x�4��&�~�!<#*����x.S)�s"N��
zTZC��+�a�%$9��?�|�����TI�Fj��33��%��H�i����Ldkok
���l��b���^�W�}�����b�?Qm��N�� �m�v�-��mP��Aw�I�h�P��l�F|�c��������xf~YfAX�t6:������?���V��#��p?'��gs�Jg���
�}�k�a�����B��j������e4����8�~%�;��fi��|��{�Q������XcI��@�_�8~IY�x�!�0H��&Y���/������d<���C\4M�������T�:!3i��W�����������Jh&dc��2��'q�&(C���{I�sG���J��F�����?��:g���{z���}��@�<Q�L��X�''��5�]B�KgS�_��&�]�~"��c��CD2s��*�P��)Pdd�C�3�g���4�d�T�yz�������!��s�>N"aVh���.���������(	/^�&?|UM~����5e��� �_�x��}�G�'C%�}$�
=c_T"��_�h2wb/�i�:��j���9����)���]�r�=}Y�vNY�b;��������7R6���na��	�'�\�f�2olE���(rzReW��7D��>I��0
]�$���?�Gh���RZ�)J
���'�qL��

/B�x��e�J�[��p�~ ex�Gv�x���d6�.��%�;�@/���o��M�����,V8;g�a�y��{0�4��zc��7��O bE}�0p3;�" ?���a��JP�������Euk�{/��+����L�.A\.���J��h���U4K��S��jpy=ys����U��V����e�Uc-w3�*}y��u���V���?�x������t_7����h]�R�JjV�������������U������!^��4g��Q�:�e��x��u�M����@f���v�Uj�}�5�D�����t�EML�qv�������n����x��wB��R|����_t�����a��>���w�\�FcvC�X�`���(�T9N2!�$���E�VqB05i�Nho���M�,;N�X.��K�R]'i���z�k���<
k��%��Ti�]c	������]��r������jJ��|�>Oq6jW-���IO�,��a��/�>�o�i���E��l�8|���t��yP�-��c������H:j� �P�ud�gE�,�!���p�����Ke2g��$��m�<�������3��`���L�h~�	���O����8N���^9n���g�����<=�H_����*as�'�����Z���B�M��Zj	b����(P������
���$�6��|*{}�\�����1A�d�Mf2K� �L�t��c��=J�K�����1�GNE�P2�$��A�HQS%�"=c�f"w��i�r�L	S�%���s�Ib9"Y���K�jPAA�vK�E���>���-��k�	��O���:�\I�R|�Ir�Y��~��������������n8�\
�����!}��
�4x�W��z���
�%#3��������������#��{�G���N�/��\������1�	) ��B�}���r�����>��s�C�H|�oS�>�e��cfE*a���7�S%;Q���t >�a,�3�n�U����Q�LT�C~��q�q7���<�+;WF� �tCB�Z^���bO�G���v�eJ0N�b������G��	��'��a4W�e���3��P��(�j��Ot�\,fH@bBH�!�}�|{��M�0�A�]��F�<�� �q�p���$�f"Eb�i�1�����!���I�~
�G�`�>q���P��}������n����:9�����?�V��sm+��~�M:G�����C���?Q�S���EI0`��^�
6�:���������\-�m�P��n�6�}�Ej��?��l�b����qS��7���`��9�U>����J�Z7�a���j����5����D��)��[��ju��tO���	����O��2wr�"$�v�-�&R8�	4f�1a�>��~^���5��!�yR�i���Z���?x��� Y�p��(�g�����cd��Q��8��Y������l.0U�5$��������T"��b��D-"h����la[N�������m��q����8&/�w�?��l^�1������V�HBLB@{N������"@H�y���Z.@R&�J2"'��-�q����X#�:d�TE���mD�mXD�����IB�C��;
t �����  ��wVk ����g�nN.-C���`�i@}�'I����UnE�'�����F�fO���DU������Y�vA2���:�=r�����f]�nV�ld����8������������P��|��5��m��p�[��������0���96
]��"������M���{RQOd��TS�x�qH[��������q��$U�����lP�pI9p&�d������sR�N��x�I�WTc���;%��\Q8t
f�`�����S���;����x������P>��`�D�%��f�e%2�5N9��d�I��.d&�!�}�Z��
����1�|�L��cma�����z`���.9�N�J�\�_(�N�c>���P.�S�c�Zc�)q����<A-����)Z6rb��6�8���zM3���]M&��+�B��c3@ M;��'"�S���170�g����t?p�^��^.��]����)�5�����Nj-6 �(��Gua�����
��~�|�~|y~�/�������<V���<�b/Sg�`�]����������=Yz���,m�l���Ah!�B�l��D)���	�����uqq1�5V�J��0�y�$��1!��G���d�j�=2)�IN�
��������+�SP^$% �)��U���1o��.F7�����#�5��N9��(�I��->O!��8(���`���I�x�a�{�������T���\��|B&V�x���\�3Ro�GU8�">@�����9~��G��^c�h�1f�%���p��#�g@M�s��H<�����Va��VG��@�Nn����	UL����Y����S��c�(�19q�5�l��s�����X��2�N�������Z_�%��,���)&�4����z�G�;e��"la���,a3J%n�Ks�St/��p�����V����e��:^S��}�"�R��,�hQ��!��+LK��+ D����c_!��l��	S+ �gA�O�?)W���N�a^R��J���B�
$p�$[�I��b.q����Q�Z�[_A�(�:�s'�����Zc����H��`M����:��k��b��pf�G��[����=s�+�s�,t�XYD��Q��2���4������hG���bG������G�T��4D�|���J��c��/�ec}B����4{��p��.mk�����g���PA��HZk|�������mO���ZB����$�A����Qy�V�wE��+h6�%/���
��H�c��O����E.�����P1�X.�$3;6�##�n9"����Z���EB�	�=|s�Ed�����n.���i�y����L��7<{V�j�����9O��N��"��+��q���D�Y �R{o���/Bd�2-�o��_���Zrg�
��Y�ii���r��o�X�hU��kf��Ea�4��.��������@W�6�*!��dJ�����F�Y�yl�h������ �%D3���D��kZ�l��#��|�b��%��|p�,�J8F��Hf�a�gKD�H1��'+�P2��m�/)a�f2�N�)q�������t`h�:�a])�e^��a�M�r4U:5�)��[]v���k���<���Y���M|	D���9�;����~y}1�����{��&�"�����
��Fo��V6s\f@�s�iZ���A���E�\0X�y���Fy|Vv1�1��M�\�p���b���M`�J�Ay����B{>1�>AI�w����6�$K��0wD�O���5���;��n�:,d]���b�{"\+s�Z�����:���G�V��W���h�.��6��n�S���PK��t7�N�4�PWmW=q������T�w�R�>��1�}����@F�m)���`8�"��a�)z�H���v92�����^�c�QB��$r�h@~�&��r���d�+��2,kS�L-�

|Q���kS�l����)Y���<�LV.%l�p4���:��"�.A��H����B����������4�+.��1���[N&<
��YjB�u�_���X@����m���/���K�������l�� 
��8�zq+j�,a��
<(�=5 ?��������s�r�o�����r�g]>�?�b@����Rgs�-M1�<��b�EP�j���#��q�O�S����� �������
�w�,��S���s���UJ6W��L,AW_����t����ra�#���[�����[B�k9��Aa�j��`���\b�"
���RsZ1�E��q�A[~��z�,��y�f��l��n��9g����i���=���������i���.|]��e�k������#1������}9��[�}J�m��^��o����!��e�_%tC����u?KP�i}�B�	b+������u��7L��
?��O��n��?j����������+�����fh�G_�0K��M�2�������K���\�p�6q~���M����)����������T\���%�DG��K�����Mj���K�}�Rq���5�+���m������?�,M��Y)J�h�x�����Q�~��w�c��';� G3I�M��v�����.����
^��\�:������e�7���L��h��$=��]<0�E�6Zk~���f�yZ�Y�c*�o)��.���%O|�d��g v���v��i���#(
Z�+�/�Tw�/��]�V:n���m��M�n��o��%n|���>0eo0����\\D�ye�[8w����wF��t�u*�l_�)���f{��yU�����;6���{Qj�����~�����O���63��m�}#S��-���<oS���GJ���w�O�H�������
���:�	;��%�����{�0H# 3`['��o�v=�5�3�������L?������H�e����s)E����R!I����F��7w�������t�"��`���Q�~��)���;�����U@���u�>rAo����������Gt]{�dCJRd;J�_'�zy��k��r{�H�o�w��WM�A�g$�J{���N��T���v'MN��"J.��A����m�(I���:�1_�������:�Cp�=I�k;	4�r�ASa�a�`d	�,���f+
h
�BK�O�_o�+���ep�-@�/�i�Z�W)�4t��A��������������74�uT@,�������Q
��W��N)I{�[���Bpj�\�������1�Le��TCT�.�\5�J����iv��	���
��-n�/��e0�*q[�z%A��F��`fpE��q�*��r�N^�L��P��U��UQF���=}�Rmq�����Y1�%�z��A�Y"D��9���+���f�"KF��[���%���q!=�$��@���b�����R�OM�t���l��m����:�GC�r	�	�p!����!�#�8/�./�/yMA�A���Oz���EO��gWC!�����m���{?
"X�Z��u]�Y��z���UD����KO�� �W�e���	��I5a���hc#z�,��Z��1!7��H�xt1��Y�,Y=�Nk-bOkf��`�.�4����z�mnG��Z��[�jb4�D$��`�F x�cP$��-8��z;�{u��
�Cg6Czzr�kV������� �u��|	�#��u�q���5���b���|.H�Q�hB���kG�P�WZT;�Q�J�����,��������HH�B�Kv�2E{u������������?D�J����<!"����4;��	�|m��{� vPxm%�a0[���H��P;�z�
�[����gRBy�R��A�>�DJw#��.��s��@7�H��-��i�����50!�^�%��	��gY0TN)�����
#C_\��B�����������X��`>�;]�jh�V0��v�n��F��
��@a��a�9��,��H�/��Mk��5��������"���C�K�����a��$U��IE�^$?���b)Eg6d��l�������VC0s��*���f�����$
������x�f�g���-�����^�MTZ
�x�&�?d�������7d�i��}}9O�{���.m����o��
�Nl�6�"��;r���GOn��B�,��O�N�\S������O}��!fI_G�#ma�v(���E�d�O�j$d������8������ ��M�����D�� ���M�t���C��0���F0xC*��R�K���X�A�����r�@����Q`�>[2<�Z-h�y�n����t�$m1*��Y�+*�$`DD$��gGYAH>h�#~C��F	����/���W?
����?�
g�\I����|'��j�Q�����e��SGG���;�J���,��P�l6�VF��	�ap{�Amk�5f��Ax�H2��z����8��K5��9i���|P(��B�r������
l��>x�C�0��Z�h�T�)K�m����<��d������� X��u��_��g�W�����������c�t�(�<���w�$[��&2����
1�e����J�`l%��(n�_h���(y3f���w��H�����nR�DdSZ�{��B]�,���2��=��*H99'op5)��k�a��|��y��&��!�1�_�}�?=�R#TG�Z�{9����8y���Q��~�X����*CR��Cj�5����"���w@8����4����/�c��t/�m?�_�������+K
�[�n�2U�.�z���5�i�j�#�n����CA���P�������VG�CO���R�����]�K�Z���"
����Bd���`zL��1X���J��K��aS�M�#�ziiw�"���l��C�v��1�g���N��@��z���z�S�U��W��`����������6b��3j��6W��i~������[�
26�J�=����*�����I�8�O~�o�}(:�{)��:`�W�l��V��4uob��%/��=S=<!R�����q�o���������N��	��T���1�����d��oC�*����8���u&Z)���T���udt\�7|�a��2D)	o|��!h�`n���	�+e�1���;��	��8<HGR[���p>�w���B������k"{��(
i*-��R�J��S�KR�0q��)���Q�b)��G�T�/IL���N�@�;��I�QA����vq*Ba������Q�%�*~�8�d��7]N�K�(jo�"������h�P9�'w���5��)XB��k�dAV��4���V#<����tU���C?g�9�d�s%�&�d-'U��-�������I���b��i��kd0�*��#�o(Mv���%�������q����w�w����i���u�P%��^m���i�#|�%����R� i,�1zf�U��k���~T�����MW�t�i+K4���}�Rei9{-H:5���(73[k��H�<�S�����.l�%oZ�����5�t���^������������� 5��b}J*'%��������G���c[�Y2���o�J�s�%8-�U'`�c����r�\�"S5���y��jc���Q��"�Z~I��DR%��O�-�c�0��~@�'�|�^���7h��es�2���4w��7����D@X.[�m����{(��P�
6����>c��B�d��Ba�G�1�U�J,��|�[kI.��p;����j����	uy��PQa�e��g2(.B �w����vx�g�+���BR}74.O��1���n�Q���#U�n�P��A�����r3hvF��N��]�n���c7K��)����_B�����;;
	�C� K`-��N\D+6��s�1���E*�`?�H��Pp+��<yjJ��<�-LO(����f�j��#I+�-�.�@�r&aX���N8�Uv�������}�����3�/�*)`KF"w�X
���u��w������av���swl������L��R[e�%�_�����w�x�C��_)�;�i�]�M;���D���Gt���e�2��F�{�/� �-:�4x���Gfx����1�&�Ta�f��. ���Z��	A �K#�@�^��S��2�e��F�aP�WNg��3.(G��p_����_�O��h�3�H�x�gVG�@VLlznDjO��1��^�0���+�����
E��X�� Z����,�?�����|���+r���g1
L	��L13 |�LQ���J���r��Vw�q���Efj����gy�z@�M�E��W�py\5�;�]�r�k{G���P�H����C������J�sEO��C��P�:U9Y�
����M
TjY����b��C<������>�������.��V�@�h/Q��������#����U������{�qm�!���5\�W����$S�����F<�d�}Z�4$��So5�� �t���H�@��NBSA����(�B)��Ih0�Ng�@��	PQ��d#^��|�#�f���Q�gDo�n���:�V.�2Mh�w���m��lg��@D�2�1
�����A/efX��|X ���u!��'���&��*N]������2�
4K�NC�?4�?(\�i��KN-A����Z�Z�V��U
�r�pyb��e�����e��=e�G�����w)#�����?�El��# y.���;X������2��bv�����tFF-z:��E������)Pn����^��x�)��0�-�RJ`�h��+=��F�.P-5��~Q��pXl�q��O����_A>�W��}AK�_�!f�Q?�#��pDIL��A�5<]p����������j����E���g��-�3�9�|�
2)�1�"XQPEe���l�O0�8v� @]�q���ObzX�=���Z|�B�����\����G�����U��W��$K&�|{{���w���E�iS����O'�����3�i����A( �����D26�i5��>h���f��V�����4t�F'6��3�@K�%��	��Y����<�#�G�,�X��OTN|=8@�DER_n@�d~L ����OH�D�#U�$gi4l����������vIL{���*Y��������/�1� ,
��	�-��J�I������������2y3��T&��Q�*v?f���E���������^����x���{�h9���*�S��VNn��<�7��.����������8�������8 �0��A���R8�8�/���j��sA����5o������f2�x�����2a�z�N0�y�.����q���r��l��-�c�1pw��^
�m��\
�t���c~���[r����*�����~��C�E�6��2|���Y����g�����z����ir����[����R��x5[�a#��ul�+?�����O���2���n�?��������cz)����
\t���7�����.r�����_~����p6z�35�C��l���(���XGO�=J���]�Q��p���/O���+|.��?���5�������{���xh�*��UA<�������� �sq����`g���'��<YJbzi���]�=������V8�p��0��8_i_���U�[�EJ}%1Y�S��[�
��6�KLRHb;��MKwq��������u�~�?GC/�fcH/���14�����#�
�^(����by��TbH��2#W����G��0�c�7���N���.�z>����������
�(I��Ve
�������^�d>g��R����9�5"L�"9{2:Cu��-����+hD���(����kN{G����������X?}��v���I��dLv��K9L�L��q"q�s�Q_���:�6QH^>�E���E�AD-���W)Z���P��00�Z�����6�dY���e�.g�ey�9�j;5(���S)X��\������h#qx�-
��#��6`��P�)�d�!��1wCinO$C����|B����������t;J������%�1��������� ��G`�S��&��WU�i$Hg#�������<�,�(�����h��]�jc��@@���q~x�H��4q���z9��d!r\:O|��4�0(��IM��8K:p�z/D���[�W��
P�����.d�{�d����(����������X������	p6���b,�@#y�����vO��� ����7��09��{�v5�Ci�]Z�����J�&�4�Ce�=������7`u����;et�2�.��~r�so��=��&SMhb/V^�<�d�z�������IQU2������#����86�Q�b�H���8x�����=��=��m�,��8��=���P���^2��0%��/��8���t~�cL$�0���=&-�Q9qB��#4*$����+�hX ��rb��]��J���@�ty��d�
*��������r��ST��Z��������K�e6�I,bl�0\R�����v�u�_�"��X��r���],*��b�0��D�}��v�Z7��������HCn.Y��P�����T������b�%/�=)����qPxf;���D�T=^X	U�l�9�����FH��`]x�AK������?w����<��v����|k���
�T5.���O��%
����i���+}����������c�Ii1��X���P���Gb��;~L�M��G�X�,������ki4���d�6�!��i�9���*e�����p�
�K�Y2'+���{�K9X�7���#�J����������
n|������(>+���>��]A�P�d��!}D�Z�1=��ic��dr��jx�f�������DHP��M�`�,E^�?-7����Y+[��/��� (���N�����<�`�Y4�~B7+f[����k�w],(nn�@z���"@�c��xDo=
$j[X s����"Ttd������:���F����L�$A[yw��3���"�MbT6VMfx<��F�<q�� 9�<Xb�g�.�g�:]2��?�!����F��6��:I��������I���6��~^���uB���u����Y���`|��� ��A�M�A-���d�_2���;���b����^,I�gb���^|��!n���-����`�c'2P��,
���3������XQq����R���{��`/�g�c�'[Vl�p�Z��8av�g��U�7���`
�F\�g�&FWV
�X:O�J�Lf��������� �\�A!�(�i���8���sQ�AYee�����d&N�`K��C�kH��]�I��`�:'��5�C����Dl'
�/>�8C5��{�;�'7��*7eC���FU�!�������g�%����^v�x�l5�V���f9j� '.x���)j���-�����)���'%���q�+�>6�����7��������
������f�	r��Yac���G�7l�ElZ���r��X�,��U��}�w)J����#�����1������e���b��I��	�6���aj�Q���J�h&�%�E"���.����;�\&}�l�E9R.n^�rI�g�5#�fHJa\���)�ga7��^^fx"�%������(��-+���S������2Y�'��=Dp�L��![t���uS��(�1����-S(r�"�������'n�}����^9�����(�`���S�Y�,��Y������1��D���y���.��\����Y���J1*��H�������8/�Nn�c�`.��M��\�|m������g�f%e� ��T������!�@�����Vj
�'����12$���poh�L���[�|[��[��
���� Xsqj��l�J�O�$���7^vO��@Us�{�}J*����W�I��j��59�[R[}.C��[SOmh��tLW��-Y|�Z�X/�6��b�3\��+���8g�r����lp	���;M���2����j��g�Q��v�
������ay���
G;�5l������V���E��b<�{Y<�N��c*`�R�r�)7l4����"4����yo�����K��2E�/,�F�TT�4fs��@�&�,A49�A��qs!�e�2l6��Tp�w���h��2R�kG��Mm
����E|��{�zq���iGF.}B:����_�b�J��"<?��^��0�u��X�|`��+�J��L
j�ud�FBem9#K#;�iC�d|�zi��w��]#�����7�m��-D���R�����*$~'�:�a%�F�#L��G)��0G�I���l�������l�`~�W�x�(���e���%O6�y��1L#m@���6�em;�B��h�6��8&C����96��bM8VS�U���C���4C�o����h����|�7b*�����4���!���-��z��������A����M��nM��I*�z�G�9 Q�SpPvq�O6!�d|f�0�rn�Y@��!z�`!��Wv��;����
���$EG�wnq���5e�L8��4��
�;�[��Gs��iwi)2xLm�bN���4��}��+��~[��o"�	E���n"������vI�0���
L��'6r��}�����k?��qz+�X��zj�Z1n i��5��� ����
2N��	� �:��U8,�T]�;T!������Z�X�"��K���R�*�B�3��Z�Z�f#Z0��b<J�I�����(y��I;m���|�8]���^���oe���t���s��D
�$����p
y5�2�C�y.[��c�t�@XA:�.��U*8k9�ehA��S�GL�P��=��cq�%�+��/��1)�!{��[(jYr�n�n�������1�pT�^�1��l%���&���q,$�j��y��Y�+@yw�d����B[|z?:�C{.�?�������R�e�~�b�t��XG����A���q��"�o����`���k�p�����2\��t���g.��}b����/���%���L�wT�&
[��FO�9�����-�@L�"�X�G����MF���]Pd^FV2BS�M��Dd{�qp��}�A�hPn���e��>��.]��S�d�������YK�#o�X��CN~�,|/��C��l���l�m�M����(�lB���?za~�?�P�s��N!V*�s#[������l����{�~��m����3�B� T{)���8���|9���U"F������Z���������P�8�p]���\J\����L�L�$��%��?b�/H��s^���VM3E���EHM[c������1�<�0��Y��K^��d�Rkmu�/C~����nL���h^t�7K��{/�����r�V3�@��f����0ob�D�KB��I��������QJ��V���=wX9�i*��0q�.���r�n)N��m�0q��d��R��`o��_�:	����������
���L$�+'%�^bTVzTfH<x�/6@�">�w�)�%�B�uP<�wz��f�"��py&�Y��3��c�Q���oh�>i������$��a���'����}�j������zJ�������f��LA6�\L�M�u)T0
p����c��B�Q�<�	�h�����E�Ha9�HU�k�$����fuj4y���%p}���
���	���9a
�4���iZ�����������R��K��p��9�ZJ-1�!�n!=�Fj	��6���L�<0w�^o�xI��pl��� NC��T��j�����4���PA����>����T�bZ�dQ�?�^M�0�@NWr����K;���>��-�%3������R�3Mlc��i8��9��I��&�[�sn����rZS���mu����q0	�BJ+%;��<<w#��?9>����b)��jm�}n1�����*[f^�[Z�2!f�����ut�q�U�It0.�
z'	���4���%��h1dr����wU��o����^�TL�3�N=4�8�jP��������}���#�����"T9M�(~�� ����F��X����7Y��u�>X��8��6m��������)W��f�V��	�;v��VrU�	�z���c9QmLI4O���hJ$�0�\
��Z��E���tnVK�����#��y&�
!���GV����40�Jr{.g�q������X��S4&Rl-��I�����VB����B4����F^�"�����3�v��$o���������lN[��bb�1��%]����Y�Cp�u���nA��m��d����y�.Nw�����p����Ab�8 ���`z���KL�����:�8l�z@��F	�,�W�G�Q���$���*�y�a�a����Z��*{�8o`9*d���h�)X��.P�M�S�x�/�����B�I��T��.�}��$s'����TY�rj�i7����~Q�i~4�i����������7,*�������g��u�v�!����@[{@E�T����:��;�Y
Z��L�GB��-��2#�y}�E�KY���	��R�n��� X�'�C��y�y���4u���Y
y9&��X|if��9�L\�`a�`�|� �5
0����Ak���?c�t�������Rk�o�������7���fA���=�����u��-���!7(�j�R���=�������e����Ta5AP$�������!�J��w�^�b�M���L��������=O#����X�l_?q��4Y?ILB�"=FB�x5t_��r���6"I�X6"��8��aVKt���*�+]�HWz�"�?jb�V}���o���,i��w��N�m�L0J&�w����)}���|���JBb��-�&�3����j���������*B����(g3G����raP�~r�33��%n�A���u���0B3���>o�yQ��cx,WK�y��l����c���R���CV���6#pe��b���'��w%�:<��*E�V�U�L���^+��9����FjW5X��<F$�~�P<x1f@�H@\7��X+����/L	T ��]�{�C@����.z�B��yq>�_�\�
Hc$��<x#��!���GVL^G&Z���.?/cq��B�������
p�L�J�GT�A��`���j�px�1�����=�o.�
�A�p�,��S��
SXN7a�6k`f.a�����y|*��t�/��RSr��o������7Ax7�8a���m`d�v�p\�C���E����q��������Y0_`�e�L�r�=*��ske�s����6$�+���c�2HPls
��6��+j�JOs%{N*���U�%P�1*�i�B�j1�!����,O���
H���s�jR�F��~
��X,������!�IM��b������rRg�
Z`��/�t�7�WI�����Q��a�����O�I���AO8J�,F
[��I_D�P�4	�z&Qx��0��x�)��*�f]m��>�Q�:f��r�����
����W����;�D�LDt�%�cG�D]�aJ���Y<�q\�����b�p+�+�*���q����j��I7�
����_%^��)|E7�{F^!�&�U��apvct��G�q����r39����������?��$���3m*�L���w�$�(l(��7�\��6�8	^���*K�/�$����c����p2�].�����3#����U��J�X12�0����4BT�Q���0k��-Vf��{�1C0.�t6�Sq���zs���W>f�O�@W�y�g�a<���8�\�z[�����.�^\���!�51":@4F���J��E�8�,�KSN��^ITYi��uQ�Z�����������0G	=��z��)��c��x�r~&��H�t����u�w!>1-�u5�x�T!�V*@���gR�np�QPu���h�
�FV�~\�^|�����qY6�U�|V���}�����[�8.��Y,�J����u����U^��g,r����o{���PD1xK�d2��oR���Az�H2E���N�9Y	#�M��aO�;8�5�M�E4T6#�������T8/>�/�q�^����C�LSF�Y������������h�(���I&�l	u����%%,)����S�G�����hy�����c��C�� ��4��
<�J�%�3��l����rp��0����7��==�Q0�B�W��L��"��{�f���0w�.n�e�%'�b�-	v$����.Wu�7����c]rs.����0C�������L%x��W������f6������g�@f�����DH�q0�2C2�eT�lj��$�*�w`J*��C�Y�e�0M�
go���M��3ItT��:X�����Z���v�:��}&{_�`&n��#�s�������X�
�s�;���;�
�e�b6t�&��D��1����}��d,�#��L����m|��l��.��Dugk��G1��)���w��S�h}���������C'��^F�n��YH���n0u4�Lt�VL"d,���p���7$9�&4c\
>��|	 y���_���*�
���R�$��D�07���[3pr��vrl�
,L,p%�I���Jpa��h��('@5�R'���?��������N �T
���������i��'����g���U�f�D���`8�:�����@�
�������d5��{>{�`ZG����:��1��?Aet���1=Fx����V(�����E�JWj���-�.�g����'� �����(�w���D ���oH`fqyKmxJ��z�$L��W[�aF����>H�3�?�K�G�Y���=�Q��PsZ�k�#D%���/���/q.�_Evl2�\��T��v�,?�>CHKC#�R�F���l5'J���C|�'`%�B��9�N�pD�p���rC�on�yP;>�Yze�&�<�YZ�H��
EV�����-%�U):�����q�^�9% J�Y�����I�i��j�6��B*�{3
f&@74���58����@B{<�_j��xL�DTA
�����yl-3o�f:��9�i4�y�n����m�������!�����r��8��Y9�d����o2��*,����s���V��������#��?7ZG|4I�S����f�C ����t�>!��OLL(y�|t`�HH��q~����l>[�%e��D�(��'���~RK������v,���~_���Uc?g�3SUd��������`KsD��I��}�<M�I���K�%Rm�.�d�::�����Zy��{z�2t���w5�;Uu�5w�f"+G�x[[Zs�0����Y�#��-���
���|!���?^�w����&����s������L��o��������'|"���$�f���b�u;��Js��`�.����~���
�lA����!�iz�D�2t��Ly��2����]�GP��P�D����.D_os_m����2;���Sw�d���)�:I���h�\�`NiAJF���HH#�Z:�l����a�����$�����>�&��
���E�g�\$;	+6�M�����{��Q8~�z��c!���^Fw���~���r����*������lT'�j�\�U��J�Qkz�J��h<9<<��������=����V��R�;��N�O�/{���w~)����q�����C�?o�H*���'���]�����_��Y4\
Zb>�a4�	n��Y��=��=;����|���������n��'<(9�"�����W�!5>q8��p�E��7������Ey���;�hh�K�i��[��x�a���<G������"o�N{o���:���+�����z��3�1��.�]I7�0p#� ZP$J���a�����s$�@P���������o&�[?/b�	��$��^VVN�*���c����i?�/��=��?��p����_m�	��r�}-�(�_����r��?��}?��f��
��?^���v����_�/�H���;��_Ly��2@���w���%s5�J�Y��U���R���pVidUi:�4u���.���`�!	�r1e��k��������0�����N �:�0�������;�%�lY��V�%�lX�J^B�Y/�C�v8�iWp�8�������Q���Z�]���Z�]���Z�]�iUc�����
z���-Ye+�%�lE�d�������9�yp�+�5�A�uv�X^�����rQ4���Z��s��
9G���s��
9G�$�ZcA��CE��P�)�L_fv�|�2��e"��r�c���vTb&
bg<�������$��s��"H�
&XQH�C�35\����yp8�	���jT��
{1Vx+�$VXK�)q�X/6�G�R���OP���D���F����'�ry4j���zm2���
9��d!��
����GBv���`���I��K�sb����~J	�}�Et�P��P� YL*v���������+�QN{����@
FfP�4���*���PC%�#�v5����p������M.=��������/�&�\��5�������
��M�j�����h9�v�P�A��'�����}�����pS�x��
�6�cP�
%�_=�t���z����P0�����O�R�$~1�����nLl�5�J��TbUx��V��%\G~�s���<}�����(e@9�0�p�n�Y���mZ�Ux/PC��A��4�#�,�+�
$Vd\I���{�Dut��`��;��8���_��=�����%$����b���7�8Vc3/q����R��4;�R�	�6Y�uKlO�����/j��+���#3{���tTaBd�B��k��X�Gt�,����k����K����4 ��=	�h8^�������K1s��UU�%`�'��-&l�"���[��(��nJ�us���	Ay����k�V�����o�t_�k����F�u4�I�g))%��}/Vzt�=���X}hU+xF�ju:$r2x/�����kt��O���R����T ���M*�����	��0���$�����Zs�O<@5e7�-=6vp��`T���U��Z��h�u�:����]]b��;��-�o�R�
�dYq�(��*Dp��[��]t��^�4�H���g��
�����;�M��/\`E��u�����	����Y�[U��>��pd(~D�����O�=6�q�\%.���9|���wsx�1�W��*����p��=����L'�0X�A���{���n��9�c"����d������52�v�~�c�tH$���K���c���`W���`�����|Z�"�p�T��PG0����1T0���~Y�vl�h�@����a��DR�k] �9G�-�U%�8�o�s��K����G�E
��I���`h#�V����F�<��(���H�b���l��2��{���Ez�i��,��"���G�N:���J�v����]�����	P�����&��G�2��%~c�����#�m�v�[Z�k$T�HT:-���������95jM�1�5����.��:j$���m��%nC#���#����\�����a��ISab�o����{�(Uk��%z��BL-r9������o2������o��Wz��>�hIs���'A#`&������U�X����������OR�8����)>3$�SL
�G}IS(;:?5M!M����J
�����I{\i{���,J��$/��������QB�<c
>��f��B����X�B���A���$�=�^�8|�?�6�
������G���*�9,���Z����-T|�E�8;�M>��x�� �x�/�o�5"�V�JD�?
*�P����
Bj&��O�����0��������W!�6X�F�5��M�	���Dm%����H,)����-<����]����k��BX]��2�#Tu����)�gQ$1<g>u-��,�a�����?{s�}����}��%/�^��D��p��0����`�n���(2�h<�(3��
g'� .�:�������}pjW:_N���%������T@a�y��n�Y�,�D�����f��O�=�,�j.���C@�Y���P�EL�-A�7���j�y7�4�|vt�x��6��v�Q�U�:�z��@�H����n�(�X������u�S����������,�{�f�$�&�#!oi"\	7B����zI�Y���aD�����Z��?���S�����3,OP;Q�a�`SI�
d�R�6��_g��C�w��RP�(5��4U�`X9��3s���+�H��5������@��M��n��-�L�[a��Ir�	�xc0�t�������������-�5�����+��~��H��5����a+���;r-���_�-���$��f�����JL�	^0���
�p
�����(��N�jE�KF�W�<`����c���H6�fH����I0�	���M�\�z�A�|�;����������,�� �??� �2�>�7x�v�x��9j�=�����RB�R���{��~���%+�|��I	��w�.�:UgA����K����2
|E��\�J����b����?�� �/�B��t*�/������`
v0���	�x��%��@�c-������y�b2���3%�_����������Z�TC�$#�n0�
����KX*�J�Jh�@$Rr��VR�&x%B:K�p��!>�P]���^j������{N��9lc��E�(��R�����
,d?V��e\�x�c�`��H��(�`.'M)�:/�3J��Ha�m����D�i�������$���D�����O��}
&��O��Z ����������&��}_�A��[!�C
@T��n0N�(��"�y�v�T�k�F�Tm�`��G���N�A�����\�Ya2$��!���FM��S��s��4�@���n]=Q���H���;�����pZ�;` ��	M�wj�x���f����tU|���mT�D��j�J�OV��V�*j���+U�����:����l�J2�N�s��U$���a"^A�' c�������i.9>�K��2
������M^,�uw.p����C7�v�1��bw#��E:q��^�h��:[TSZA�7�d�k+��sKN��������mb%�h�����,�N��=����K�-�s�+�K����U���"�����at'�Cvti)�A��\P�mn[6M�&��N&�R� �/]��ab7��y����-g�	.}?��-��vWq�I�g�j�l6 �Q�����G8HA�(��I*z���A(f!��#�������y9��
���p�4����*��F�y%@��AW,�(:�1",����+�[���W������.,�a(��m��<��w�'l�r\�0b��T��@

{zS��J����"����c�������r�f�N6��~H�
�����2LKF@�= ��
s��gFc%���� ��.zg'B�	���.������S�W�:�������qR�Gk��-���}��}O������y#p,*�$Gl<b~W������q#v!�^��6� �=��q�p�b]��Vm"�zl�$��Z����X}3��-L���pj��o
YH�s�	��r�T����J�b�
I'U�������:����|b���J^;K��F�-WwvY�������jm�>���������F'�Q��e�E%@'Q���;}�'�c��p���]f��8���^��k�E$�E������/���d���������h�����dN�@��h
����u� lO�Qag�@�Q����y?����z����wv5��
�I0!ZA�����������O���/�4F(@�UHPx.o8!�|��x�o�\���p���T���LZ���Q���G�f�Y���<��FK[�K!r��Z-i�G�w�kG�v��`G��,���"�������W/B�i������l���W~��@H@
���`|.^u]��Q���|�q���G�Z�)�b�5����{�Dw�d��.�v�4s���CU��FS��`�7N������P��4Fp����j�����4\�Q��P�oG{�d��L�{�#~���5����\��`2�W:��o6���f1t?;B�H@��l�+�Q��A�W��da���L0��uY0YK6cnx��||K"��\d��mP[��D_r &h��7F
A�R�J-�#W���Hk���u5r]	�,l��h����;��L�K��B�������O��2���E�c����%Y*����A��ru
�d0.f���E����DO�>�a�f�3��w:T\�����<��@�����Z�=l���jx|~�g��O�,�����sK���Kc�[�SP���8������V��z��A�z5�Z��;.�D$^��#ee�����RV��~��q���=�*�=\�_���s�e�dl��j�^��`�S� r���@�b�L�Q��R��X$����y��J�%=��}/�b7������=�E���(�����o?���5���%���7���QeL��Q;�k�t���8���2�-��Zth����
�n^�p���w�G��g�����>E�~�U#�@:�l���[���If>��CJvjnx���~�8^��x��`6 �e
�T6�2��n���3j=[P*M=rO@��~|���#������1/����Z���Sr�S���n�`��s�����X�KH��F�9�v��Q�\�L����v����FKXm�B�F��R;�e(�K�j5����L�D�����-#�	
,�j��j�{��kNA��?Sg�>�&�x����F��/���d8���$�,L�}+����Z
r+>����ks���l]{�`�8��������C��>�����mC���s4�e��u���P�sR�d�� k
Y���}��N�poo���6U��=A����k���q�G�+"��C1=��%�1��@��p���4I��C`��{[���cGl�]����,���C%=A��4��Z����\���)�Pfj/��p����R�?)%h�8��Cr'|[e�dz���)����m!�v@7.
�_m�K4^�H�b��8���������������*�*\������e����z���a�9:����^�Ya��M�hV�4Gl���
\�_��J<�g�x������C��v�T�Y�Z20P�����M'r�����jx����� �[��t��Q���4�2?2� �Rh�����|��F��y�uY������g�m�����h�i����2�/���{�5��41��f�����^$��U��������(�Tg�����
���C��y`@��VG��G6l*	���#�z����K!�H����U8�ei���~�"��:������v<)"J��W�<�j�Y'���� N��M]P�-��� PC$6�
x�w_E�%�;�x�=4�[B���~������u��t5���O7��M���(�����a���I��t*��J�g,��4y�l�}�\����7���mxG,X�vZ�R����t�G
�����<��s4��X����dB��c�2�����!(5�j1��+�����6����lg���R�\�x�t�g>�$�����yR��6��cS��4��b2E.���L������jT��5��gjD���D%������:i����l�D/���$ =��������b-��������J|B&�_*�i/a���*��h,�`�mt���[9��lf��9��cq�mS�`���kv�4�@�OtvH����������*'ko5$B��h�WB�e3Vg�����������4)$J�1�p�j:��Qv�V�	e�w�0(I��}����T��QD����V�o�:p����������0��P^���EG���Qj�C���v(/M�,���jvH�7Z�"�4�MTB�[�f��y�<B&m�7t�����2�1�VR�Wt�N8=�*�ly��pe����r.�������Ag�\X�F��K+,)�b]RJ�E���b(�k�^�9:�hH�`m���|��l�%��|��
IIS�46�k�m���"��n6��I4 �[�^5/?e���H��
�:R���MH�����~'d���<�4�����#�������D�=`�/�%��Y�(�l�)����k������������������b���z����*\��<��kA��Hlm5�I�7bZf�������j�@��?�<i30"}�t�����2V�g���y���5c��<{�Q)y}��r���^g7I�CL����|�L^}�Nb����0Io/%���y434Z@yh��� �f,t
���9��9z�����=���E2c������
��ng�7ktt"{ao�X�@r9���!��&��><��l
�;Z�Q�4A��7.;��N���
Kq��k#p��p��� �,��E9�NX0x��M�����e���i�mO����a���F��z�J�J24��~6PE��h�����WO,��?.��{V�����!�����x��*;�dD������os������Q"�:W��$pbu]�l��*�O[MF��L?���S�-9==�����a(���WU;y��M���]AM��1�"�E(gi���8��iy��i�w��`���;&
�Q.#��a��H�dM�~�������O������yp�\��u���2�s��x}�
\��BWC~�J���N�adD����`VL����y�.���8�z��w��g����+���0��6��G
r0v�p��kc�NDGW����^<�8�=���\���!�la����J_ g�pq��.����<w��@���v�l
����f�o�K�@O &/��@l=/�)��I���^���"3�����A�_/34�%V��%��V��%f���l����8���wz��N�^�szZ(����UJ^F	�E��Y���i����Fu��FS]M�.?�:*���������k��������6R��f���y���D�����b��hT�b���k'SHe�%���A<��&���G�
��Ta�_����u��M ^h�}�+��F��[�Ry �i��)����b]��',Z��f��L�����7�`�wtx����j$��d�9Ko���y7N��r��M���s�������.�Pp5#f-c�c����!�D!$U���DL���1U����t���l+��L�A����T�>���k"�I�<h�c[^�VujI��8��e�����&�*o7��5B*���3
�#�:��5��:@�Y��f�r��9
c+���\DA������\��1���A���E���1.�
32r�awQ0���S��������a�O!M<��B�$��w�O�C�B�!W
����I<Hh!�������
k+��V�"���LJP5F����)N��m!&p���aa����l.?B�+!����)���f��N8=z2���]lv�����&��P&��o��{Z0�3�h��a��0\@Q��l��/���#8��)����-���W�&\Jo�,;�bd����	=Ry:\4*�ku�d�z�Yj`����e�	�������@�_��2v{�	������sm���W�/{����_\�_^�Nt�����4�����0�+��t%�
�a���S�����an���o��IC9�(`���"1�L��8��u��A��H�@5
LZ��b�v��o_J`�$!��0T��+���E���������L�����~��<�-$Ce�z�� l�A��j��*
�Z�}���:�b�K^�T��������S�������{z��[�����Z�������a���q�3,w�����\,|+�U��QQ��aTk7J���"�8���S<>n��7�+�����++�����q:�������;Ou����>�����W����4��	)�w���� F&����1#D�P�R@���<���Z��{��"��#1�M�v[09D:�����vA�6�dJ��W��p!����#�H��	y��bT��Phde��r��xj��>�4������������z&_�[b;��0��.n|�����8w�}Y��Y��I�6t���,��`*���"3�R�

!O��h������&9��E����p��N�)|)�-��Ky3Q��3.��
�����{%h
�������{t��TrW>���,��eF����>�9�H����dl<�j���3��<�-��4e�Z]S���EE~�
Y�(�*�-V�Q��j�Z��"�����*�'�����m�Kl�m���M�%�o��T(�*�&5�`*�K(+��t��j-\[� (�G��0)�nZ��>���h�������>���i��y��'�c�Q��a��7�9��j�P0�����wYl��|6�g��# �[Lo���Uw�1�����
iD���{}|���#H�C���P�c�j[��^�|�!yJ��n�l�����g���
�Y���f�����A���P4T��A�,���X�7�����bQ��:`���������w�C5E� ��l�������������*z����Z-q�����Z��n���1��o��$f�Jw� �$nu�#v S�@����|��������&�����%��xG����S��%O�O"��wp`����+zs$_���#A`��v���U+�]����b\\���:��-7����� �.}��u�+"�C��`��5��>f�Q%:d��=E=�z+@�%����x?���{�}����BS%�#}��
��� ���6fwx���5n�wf���e��As�:������g�I���/�{6�WZ�z����r��O�G�q;O��N���*��M��_o��]
�!HK�eS��R+]������IK��Z�</j�k���������`��))Y~�c��'.	(1��q���ty4���;me��m5� 4�rX�����Al��Ri?9f~J��\U���RlM$���v�;�������g�l*)�IP =���6#:KBbj2-�$/�a*d��A�5X������t+�
��L�K�(sK�%&������;��I;yH��Z�U�o[�m���wW+!�*��k*K�@Z��p���6��>����7����U(���KH5��6sv������Xe$�����\�<#���c��8�s��=��syf�Ss�9��3$i�<1A��!��������B�Q[���M����p:�Op��) ��I�������k��Fp��ma��D���x��$����AVg��P��;���~�3���W�IuW������z[`��j�F�u!]��PR��
d�#�B��0+��:(����N��OVYLB���Q�8�$@]5%8�w���A�5%"�=�6��R5N�-|�Xqs�=NIjX�PM�?�i��u�a������\��*�	0����	�Id&TgQ�3;��o��
%��`���m���ss�4�*� ������7�p�?KH�H-������ ��;}?Z.dj&��
L�^I^FJ�YmV;�F�ed�)7�X������R���e����X���^@���o������w�7$��� b{`�1��3o��v�%&����{M�Bu�YAu*V&C��2-K!C��_�����2�5�N��'������(I���?yo@C@�[#b�U�<x�ZT�������ZH�!0�2�(�������� }�UDx�$�,d]GQ�o��}�X�Z�K�O3�5��R7 �u��O'%'�)�[�]p�_�W��$��P�k��y�/5?t/��go�Q�
��.��=���&�F���"6�d�
B����PtL��?�����9���(z�,���n��o�?����l������
=��Bf��"1����R�7f!Q�,XD����5���`�IC�%t>��jq]`t.���,eKZzd"�zb����`����#Q\mr���8�����v�Ou*G�j�	;��0�B`��E~�%aY���UJZqH�eF�q�����p�n�[.�p�8\)5�8�/ys��%.��.�7�����L%HJ^�d�"��&����p;��)��r1������Pb�Dz�"-��JS���	"��[qw��Y����lg�0Pd���D��2T�����Z�Q�����\*�v�R�'y��J���J��1��Af
�i��
���cf����	�;�Y�y�
~:���8D�s������_��["�K�sL���M� ��{���	�7����~�4��cl���6)z����O�	��3��J�f���>�����8b=�d��$!��.�[�F�����v'���#���z	:Gp��w@������g��.p��q&�Q����z�\�T+�����P��ZJ�W)�����e�c���@���_���E&l�I?�)s���L*��G�R��i^������uA�s�E�\������{�\����������u�L@���)T��l�0�1z����N��Q.�����Qgd������Y�d���.��n]�G�t\���;�|����/��O�j��P8�V�����G�?&����]sT�?���dC��d�������yw�6]�'���i����z�Q�M�[�1R8���x&�>&���A�B R��Y�J���'�������z���/�?��0�W�e<��J��S@-1�a�P\��4����[����A�9���
%���o�DA�������F=4�����d�
:���m�,�qh�2�Q{�dr�����?�$x��>��f� ��Zs��:���`���� k��>�H~^0����0�&X�i����PKl��drO[��D��l�:�T���I���j=�������'��ZAA��A�|���o�|�������2X���?f������R��!���[�Vn�D���/c���O��xN�������r�����zm��H���^r�������.�sQE
x8���"n��^/������bE6��<z9WQ���CsG�{��`= $�)
�)��Px�ta��f����Sh��� A�7��gX�:�8�h�b���o�����e�'fCv�X.�h�(�t�`kn����h�(��H��o���@����\�~��O,e+������3�����S��� �d�#�X�s�w_FRs���U�4������IK�<�D��%mY��*&e�f�#��I�\Y�V�T��\��n.s��E)�� �A	?�#x?$�F��7��?h9L���9�%@�Mr��)��*�MP-A���/C~$�C6�T�����}
�����n\�{���	M���G��=�;���%L'�a���pB�{7u!�#����WS��@7��L&0�������)����Q}�>e0E��e�[�(RG!]R18�[X��W8��"-�$�0����Hr 
[������H�UO_�g�CzI��.zl���1Ne8��
��!
e]��X�iH�(��Z���p6�jZ+O��v����k�|-�f���Tf��X��O���w����b��]��a�JRe;[��"��[?��$8�@�w,��[\�S��Q��A��N�]l��_����I�1���Q����Q��i
�%hN�ZG����o�D3�
�(B�zD[S|V1
�
\BZd�����1���H.�d����S��FCy�@JK<0`QG�p�������lm"�p�I��3<�wu��=�4.{������z������'t���I�G����k�����?��y�"{����8!�X���^�n��k�}���x�N��tZo�����{��j������V��6R����Qr�RA�E���-x^`��bv~���	���/��H��������&�=Et�D7=�a�/��:��Y�U7�\_��I ����:������n�#xE[�e�"O�W����R��8�zot��9��o<��g��o��N2pF�X���h���/3j����g��+��A��x��a��l���K�!�6�:u�����@{?o���U����,����5��%Y���^>������T�N	r��nK/%�Z�J���P+M��V0�m?9���!���N����d0!���	��
�qA}������`_���V[���x���U�'x�Q���-��=?��������`�r^���<gA��9����!���'.�z�KIK���vCq*Of�z�Y?B�Y/5���t
~{��V�=��Lg���0�^�Kv�X�MA������F`h8+,
����iym�E�����������,3�F�|����n�����Q�����,���cAA����$q&lZ(� C���m��?
�H�Z��{�54b�wv�����*���
{ny,�Z��\4���V��8f�@���0�52�!�u�o�
�?B���^8���F�j)���%`e{�/	u &��|bD~I��f�q"�������g�B����Y���p���Q+��q �����ye����M�M&����������Dn�i�4���R�h��p�+�u���l|;�>�T!>�F�V{\kN&�r�*��Y�)�m9�tAJ|Gi5 �m�TZ�	(��>�C���
.2�C^���|s�8	���A|���X�J%G�G���)C�����aU�h������+���/H�N�	���
�io�V�$j���]"D��e�8�|������qpm�}$�����A�!��
k[�h' ���&S��DA`��ZB�N�U
���=
��
��`��M����w�'BDx�; ����Hs�z�N>J�dG�3
]��1��uy����D�|i����^��� XGF�G�<e�W�l��R[��;�-b07@��|0[�����g�S�&�)��-��,�� eF�L�R\�SJI��z0����zP.�J�����*4&�5�I)���jyl�$��d9����\�HF���8��3b�.���e�����V��#hCqSi_&��Z]{:�k�-yS�O��"�zk��[�o�r��B{��XxJ���wl��	�<D[p���%�!NFD��J��`���U�0�y��n�����R��TX%������1s�������s����w]d�������9#	r�D`T[��@������#Lo������R��Y������S.����9m������Zrc��DJ��=�TK�+���e���7���D������.k>��bW9no!A��v�PWv�V��@ZG���|�P&�}�D��2�]���)�1�4>1�?=:����������
�d��)}��b�1��3�*���R9]G�f}�Gm����n���i6��~1���v�0B-%������������m����6[���Y�N�x���F0n�}�Yt*��x"x����h(}���N��j'mF��
���X�r�PO�~Z�n8����}� k���Ux���(�
{����r��
��]L����Gf�O�RY�Vt_�O���J������vY�.;c��,��9�e��jG�a�S��2��N�c6����������?��N2
�~K��
C�N�?����1�Q��������W��������r��=�oD�. �S�����1�
uc8��h&;2���5x]b�B�b�����o#���=m������[���S����:�<��6[���fI4���H�dl$�Kj�W�w�m@.�������w��1��������zm�=>�
�sA7��(x�BaC14
/
s����g���3,��I��U9��W|�}���g���"!u�g��2����B���?����2�.p�������Dk�|�;M�����r��Z�&.F.�'�������io��+�� �������ld����Bl��|��r[e;P�U1��
�p��~B�m���!S�Q��^��+�SA$�J.���8���i>�������q������`���������b!'���	
Xq�,&�;�4�I0^~���B��t��b�a�~}�]\�w�}u�}����w�������o����9������R+���1z,��\�N��^�NTj9N�V���GXr��bs�����{� =Ua������kL�k�P��v!�*\6�J�����s�k���#�U����X��8TEM.�\�neD�gb���_e5*�h��?�X���j����7�����j�<�%�{�b3PEf8�		�����N�l�'�������~%dN�(���Pm')2�G�X���������JF�:|���L�Q_�5w�SZ'�y����#����O����I��u�t��������d�>��dfovp`_��[�d
U$
7{e�����HVb.����O��%����0��9jN���i�Q.W:���o�'9���t�.��������q7n^Q��o���"#���3�S����a=����?OO{���dl���U�l^�EU�~)Q�w9��{�a8O��
O����Ie����*�k�����E��%��������)!�>#e��YX��2��z'�x^��d�i�ph��h���TB��:G�F3j��=�L�Z�2�s"�����:uNrDB�d��O�%��.���/��xf�^����4���s���_�� ���II���E �]�p���K@�����'���;����7�E�|k|N�s�RD���A��|��g�����q��#����_NF@��/Y@��7��oT��N�4'������[�Q���F%|���Z�@�xp�+\��yv�:������0k.W��Z6�M&C����\C��d�J�}	�����pY���$+��Q$����bH<����	2�Od�:E���S3��~��OT�����D�����&?>#�y�����W�}�}?u�-p�5�8�_�� ���K&�>��ob90�B7�q�q�UD�e��h�l5��r#��~'��<Xk4���F1d��>2bG�om"<��go���9�T���_i��E�t��h��mV:M�vs ��J5�r>��~0���3��v�	��_��eQ��,�~�_���
Y�&���iW��/��T4��������3���������{�7W����+�I�����c>/1.�U��XW[��jy^5^"��2�J�y�����.��i�c�6�d���|A!o��C4>r�$JYp
w6��	�H4J��%�{)��kfqJ�&"kg-�z�z����
�[�O�Ny��>�b*GWb4����(�:/w��^���7m�����nAW{�9���� n���N*��m?�����2� Em+�G�j�K�&��j�K��Iu"��M|0��(�1v5�������F��p��l!C(b��A\�%���h|�JdQ�&��c_~�`���qD�J�m�8��@�</���x������o�?`��L��P�3gz�/v�b��
]���K��<�r���Df�q�cA:�V����$I�������P
:������6���F�X�T�N������F������0M�]!So�/n8�����'�� B����M�XI�>��X��h]�Z
�Fz�FB�FZI���1� J��2�rZ�s\�a(dgPI��G�A��U���v��=v=��E�Lz���n�M�bwZ�Z�m����)�pY"	`��q�]��^����X����/:L'��������
mn�O���������������� ��NDli���c!�d?ps��+\=mo�w��7
(@;z��;��P�%eL��&����xY���&��V�U�c#�7�)��sw��Q�B�@�G������pE,8������h�Ap%��3m������xB{�`	z?ESO��}�*��q��v����H���7���Xx�R��q��\�K�3^G��qt���|��TA�z�������wY�~���b��).\�w�N�����RI�c�>Mnd���6��
��?�^���wX��g�98ysp��<�q�S�7��1E��e�4X?�d3������y���z���eT�b��/�m����:��Q���������[n?�O#<��$QJ���F~s���ry����J{2�z�l-�$Y2o�������9�p��0W%sX���W�d&P�68�`'8|��4��o,���:d��H�\�K��
`�����w�Q2gT�_���#l��jo��c�a
��6d�bC,��~+���
*+������]
�.�_a�Lap�{����9ux4�-�W�1Ie��d[��s����3s��B2����i���Q��n�u��h��m~����oDa�t*���/7���J�t����J�������n���	+��)={����y����1t

�����Z��ZEh5|�fn1t4=�i��'��������Z������6eG3V�J�ccS��,!,r3!%U�d1$�
wH����L0�,���j/�I�pJ]0%�p���������'.��iY{���Z����c�����&�E<��-���2�������I�7��{y�Mr�d(~�~�H��B�����L�����2�I�;(4{%uI�#R���n\.u���
�8�Hk�/h�6��A��m�m��Y�e�@*��N��:��0���2�)Y����!nN���.��6��$Lz�A�!�������BY~����[E���A�4'y��0S�~r���=@�E�I��PD�n7���:4��r��������)Z��$�>d�a��������s��v���	)/y^:��1��[�Yt���z������Z�W���|H[1<�)�(�l�rDv�rQ����������H�<O����$F�(��Dp��zH�7���� $�K�G�	�I��=|���+a|���9!��k�,MR|�N�D���y���) �|bE|��h8,��wa�*6� ����5�8�����z�cp����V)&]M` (]�T����D;���Y���A��}\��'�� ���
z���d2�<T(���H��(���Wo�=)�Q
�4��o��P�3�x�m�E8Z�'�1��[��}#���f-I�^�E�e����
��h@���b�"��q��)��x"#����9���������tR��s�d
�������n��m��#A��v(�	E�B�V�?$�1����Sb��}1�IB�C��;I���JH�]�>[���FXe�&561 ���{��-8�>SV�*�������y�k�J�X��
<�y����Jh2X4�JQ��q�a�d�1p6��l4�d��D��e��yWo�<K�"O$����r	�}�>Pl����U9N��w\�t�/�A���\;Q��������f�.��'�9��->��#/�RQ4�t�	%X����<t 4���Uz�����`h1G/$���B�$@%�P�SZ��<�~t�
e���v#�0"���~��J��R�\��fI��r8��)�&��r@!0U(6QM�P�G���=df�,����4es�2��� ���b���S���w`��hWL���"t��Z��U���	�����,$)1�A������%R19}��!����X�?�!uz �
��������#���y����5�#�zq�W�Xj)=0�-�/�VP��Sd�
��=�����bG81��2M���l��b�((c��P*��P���y���"�����7�(e,������*�� 1,��<�6+�.�0�y��`��d�}��!�FP[��)����X�YI�����c��x����.H�)y���	&]%O�PH�S��:�S� W�g2�8='��KY���-mq����<��(.�R�~�"D�m�-����3���Z&�7'������z�5��&�Zl�c1�I�]1�`�Z�'����������6����K]��"l�{�0s�/�Il���
.x�m�~��x������`a�t���"�@Y�m�n�,��� >d�X�n����3i�'��)S�t2���,6�q#&������Q]a��H%��g��Y�C�F�,��w|RF���V���I{��g����������@���������*YF��Q�]�Gw,����bay^{���f��=0Y
j���
�z14V���A<1��hvt��P��
iz�.���"P-VQ+�p%Vs=-�K^��`�/�/)���-7�8�k��(D3}�E���V���j��?�8�����v���@�sp,�@���:�	iW�/�T�I��7���8T�H�,�K���������]wp�B�������/z,�\k,X��
�=}����j�o�@�G,�|f�4>s#R���Y��	}GF��i��~�z4_n�<�A��w�/J����^����x��
x�����$- "����Jc:�cj�j���F�cj��v�)y8%��l�$�VB&Ob����z�����i�wv5<>?;�5B����ZU�c[���LB2����M�y5�Z�0������{�h�=T�0i�XV3�����_<�����O��	�{d$E7T��#?bf1y��pN6�+���x�;���o���fcT)��j����Qw���6�����RN�����;$��1����D�?���/&����a2(\�@��|!�;�f�
q~)��	��k �~��b�-!:��^Nn�R]Wb"u{���r����#V�n�U��|6�	�_|E���i�u�y
�>�b��'��x��!��"��M��_}��wY���Iy���(�g������*�Q�v<�]"A��H�G�%Q<�t�C�K=�~���q��������������"T��h�5�1h��KL�U���9��i�Q���fu��p��h��j�gSI,���jLe?��n2�)�,���O����{.��O$.��8J���2yT^���y���.y���=���U�U$��W�e���A����l���Z�Ai>=O�N���=�x����)d���;\$D�V����/��#��o����,76����X����b�o�r1t�B
�`��l��&�%�\e�>�.��3�|�����E$�������8E��
�t>�����Os�x�
���t���!eF���:�p�u/�Z^�:Y}�j���i��
E�W�xn�x������\����
�$�pC��9�8�+V(@J�	�E�T�>U~:=c&�E)��������zNN���e�}�������t	��Z�����h@�X����dR,J�nR ��E'������L�Y�
@^��K��S����a�(j�
�A�"�d�*��?)X�MmD:x��:���$�4z���+��
�����	m�]6�cv?��o���D�O�R�;���P��o�wc����7����:���#�0����.{�D�~����<��kq������F��c��|�5��n�.�W�V��[�Pi_6���4'�F����][�v�������^���T��;�W[����
��������w�!���B��8��������oWG���z�(������T f����k�A7�������f��`�h���ng��dd`Z� �
q������lqMV����_+k����q�d��C�����SH��r,rU:u���Z�C��sY,C�C'�E����bL&il��&#&��_d�P��0��H�wZ'��}3�Il���Lo�����I/�����:�"�j��5��r����w����]1I\���%*��z����_��k��`�*���z�Si���T�=�HBb�����p�����XLP�
�kWk1o��AU��(-�C�=����QB�5m���TFb�a�2���B�S#��-A%��M[V���N��!�gd��X���5.�x9��?d���`�x����L���"��Jr,�_Rgc���Q�.�o��8$���$_���~(��[d
�K��#H�^z����t�Zk<m����$�V'��3[���T������2]%s�R2��f!8�	Qn������
�����MF���L�@���t���"&����!4'�����R�cZ*�^/�a��n�U�d�I�+����-�O����s��E�)N�:f��z��x������{�J���G���V�����
h�'��w�
�]V�.b1���M���O��y7�k�(n(eH��i/(��G��W�Y, �0��\Fa����eOH��� "]��?�~ �\H$�;Y���i�r{��\d
MO�n�*(��:��-Q�#|���In���� w�8�-.�-���[Ir�i#�*�p�0o��RzU��
�%W�������G����(���_����t�j:�bE��b����s�������g�` �C�� �`�w�v�}����AO0$[���>V��	o�����J�m&H=�*�+\�`�*����"��a
�8�$�c=�Ng���"�w���M��#U#�{!#�x+���|���;�j9���V���H1���Q`�N�� ���P�"8K�|*%O��{��%�����������.��[�I8�EI����~a�;^����j������K��]P*}'�u�NX�Y�H���L;|�EH��E���|aN
�����YT���;�����K�RC� ��$X�4����'�3���e`�����p>1�NBcA��(/��������^� �=��1�Qt�y*��_!�<�����=7��C2"y�����_��M���A�>j��v{��3nU��V��j)���JI="����t�"�H'��?/��Z���7\;��A0r�(����~Mg3��{rth�������L>�����	,+��`~X��������Uo�E����������'�{��������4��xg�'���m�H�O��f/H��,�g�+���tc�Jtt�G�v��8Mp�!�[�k
��������Ah��j�vG�����C].����
5l������h�#V��yqb�����,J���nt���7��I�:;s�f������8�F�G����*�����l��L��d��7�����H�,p����S� )dN���
5W�@������h�J����_����U�j!�.��yNII7*E?�_B��p6����KmTjq��N�<m���i�S���N�����R��
��'����5��e�)H��cK��`����(D��2
$To��w�TZ�]-G�eI�R����n��3���H����-�6�z^��L�3����w�_�����u���|��������������u��G�mIQ�����}�"b���-����B�(#��4]�w�G��$�����`��e�-��_i�G�j�h����V�6�M\�RZq�t���i��:|�i�V���p}#���`S��$�����a�G���7�����3����io�����&v_fB����5C!I�u�r:��#�������L+BS�_����}I R�����|xry~���wO�z��� �(������z�-�pv�&�^x�r���G����%��;<��������=����z�H�����w������'������ol�h��������'C��x��z"�lF_���[X�I��`�����J��������>*��"�\�BE����+��.��������i��7���]a�;�6�b�}���E�s�q~h6��)������1Z�{ll�
�Q��k��;E����R����L���������.&)��q\��9���������3���X�����y�1�����U8��CVY�+��mH���PM�@�e����	�4MpCs��t�����Ls�������"��k��
��H�g�����Y�|�V�8���(��]������'��8y�m�����I����2
�8h�+�Fg\.���x:�O����NJ#��I)�	qP��r*����!�w|���_�?9�gR]���fFw��e����i_m��xa*&4KeF�x��S#�PJ	��h��H��F��A�K������
���R��G�K�lI�0C`�����Z�Qo����V�6�UG���a4��!F!���E	nQ<;��s�G���!s��������wr��l��rN�6x�1Z�AP�x�OLpu1�\|�=��]���Y��7sz�<����{�����yDO�������l�h����^��,��-H��G%�2���y0V6�a>
>�s\�A��-T��y���br.�����"����i��V{�'�s!&&�"n:��������S�����o��QM<�A���2qS�B�$ 5,|6*��������%{������K�,"Z��G��M����E|!��%nD��q�!H��uQ\1:K�O�s�\��##Z���_<��&;���s�B�P��g�y�����t�����q�!,
T���-�$UFz(���Y@���L8��X�
~��	\X��j�
J`[]�3���>D�V�_��
�t�?���l�(�$��k��f������O1(����oz��5���g%���!����#��F[���^�>��_����S� l&�����\����$#��	��@j�����R�_��KC~i����J5���G�>��x9�W����R�_�K�FYQ��^3����
�;���U�����������56�c����o3c�� ���W��gn�������+����o���l�%�p���
�kq����\Z���������[�(��r�P������f����f.������� ��9���p�6��[�L�a1P��h�/��iK��r[��E�J�x��9�"�X�%�����%���k�!~���"�q|'����*�Y�{e����d��j��,b��s�*<I
/����,��^5A�\�����=����v��V]����Em�#�����c�m��3,����'>D�u}6�	��V�Kg��K#>%���Sc�����'���{@�S��D�0����/�t,c,�vXc���v�B����#�o^���6��Ne�������+��QO����F����[�6���I��%�l������*Bd/5 ��9|�?~��S<��=�^��R��%�U��X��uv\�Q'B�l*Vb�
��/�o1���;%�����|���F�&�HH<��#�����`tb%(�L�9���[������R�^4�M����(�Art0�a$��8�!8�L��m���}R�����<x8��<5����a�7nlZ
	�������1�j�`"��"�a�w��*���9���5�1H^�Y�Y9R�pi�\�}���3����xl&���m���B����J���a�Q�Q�
��b�0�B��d��6�������I�a�)@�����j�f1�a,��!�7�d������o��=����2���B�x�5�!�PB����"Lf��0d/Sc�������`��y��K�P���JYa��k�;���
���+���+�����A���
"�����HjX��]���'hx��S-I}�|����j�I���Mr���?��u�+i=�,��uH�~2����Mr����l4&����\>�Z�v�=uD �j��m8J�Ib����R��F��i?�N��O�����`��^��u���4H2z�e�o[m�J!-����k����S��[?�P��A�����������4�������K,6��nYm!`
��>�%Zb�SJ)O�Q����V.�j�f�U��~�����\����D����V�2�����}�����
�%j/�2<��j|~���{�W23.������9Pc���]���qXq��U���;H.�E�r���oYv�!n�+���x/�����Q�3=����Q�1=
M��gz;�EN�!�;kxK<Yz�xSwL��(z,��BR���Pd�P(�kJeR��M����d�������
E�K�>��z��
��������1D�-y���;l��P�y�g�<�#����������&�x���m���'$RN���k�i�Y�F��5�I���3�A��tQE��)�Y�	� I���\���-D���}��W�����z��w�������m��]��d�;�{z�;���I�e�r�c��jx!Z�w�z�S�����B �_��5��/W.C�g��'�o�l�������o#�V�^�z����tZ��p(��#8�[9��I_
.@���P[b�������Odb |g�>�?y�	{�'����Ah1H��j3�L#�|����16S2���I�;����o�[�(@�E���dAR��R2��?�+��7h�3R/�:R�-��:�������5�\�WsT�N�f�Z����`:?�� �Y-e/�,��i��	�b,��(�����V%|���Q��|�hBO�
����n����`��
����h����G"H�AxXP=�e�
�0zL(��_d��r�
L�^^v�r���e��x#9�J������A�>��&�N�qk��\d�-j�)8|�K���Y�W��e����>�a��A"����U���P���&lP�{/�9��R��9�����8�^�]�c|#}N��_��Z�\��Z����2v8�ZH�1�E;�!l�G�G���+P���:px#�!��^�H��t��\
W;0���F�w���t�X�B�A��fk��*]���"F���h(%i0��ScM%+N�UD)��b��hj��Dj����6�f��X����=���p����	2���V
d%�8�����$g2}\�
`
��D)�������
X�Y}`�h1�Hh�[���1��'����S�����c�M6uQ�E�A�F��%c�3/6_R��,|T�������5�\XM�;O�����h"�y�RF!r��n(����}�f��D]f���u/���M�������/� �3o�#"�^�#q� ��,��
����r��#��PP|�	hD�#t��d�9$�o��D�����BD8$�~� '��������+�(��Z�"�z�L�IqN�x������	f%�,�C�����?�Dz;��[#wF:q�D� �31GN����b�8K������E&���r�'T�.bRYfTE��f�0O�0�X�K�o�
���c�$�+�c�r����^���D���X8�%���	�XhB
y��h5����:������z��*�R��
�!B�?MO���aB��������~��+��+H�����V��8��.$����3���K`A��)�r�	�9[^(l��?�|��]E�n�C�l�1����� P��{� *�@D�(8�f�m�J���M��9�I��E����G��JijU!�������������D�O�2B�Yp��V:���=�#�P��w��W����vm�
	; en���vM�����������G�)Go2���b�e�����W��9*��@�qX2c��H6x����s�$(��uh�B�'�0�BclZuYVH}�RJ�� ���:	r�MH��^��:�!�c���m
e1��s��k�4�k�j�Yi�R-���f�7Uh��`�2I~�+}1������`U�(��� 1�	���B��!&M��R�B.���}��xv~�?�}�yX��G�P69=;X>98�]u����D��I�W��R���o+0\O|<�����?��/��+����bM�w�Z�=��k�
�����9�������z?
�5����P��6_��;!p ��
]�Qoi8n���~��-U�0?A|�^����2���W��
��5xq��.��mmr}��+�q�[G�.���
V�pG_��������gK4�l���=9�aA��!��T+�ZQ�P�v���'h�=��t\Cz=T�9�U��A&�c23��C���������Kz.'p/���'K�1������b/���S�
��6:��J�1Zc����@I�O��J�"yk�'���p��F����l�O��[��b(%(ru��H�P�Q~��c�|����j���"�-*a�n5a"c��g,�����&�A�^�����XV��t������p%6�+G�HK�������������;T@<]����a�x���+B��E��g-c���������PWa��C�1h��mC$96����?���&�������7��0D	���*�{a�S��#��BR>=7+��Wj�� ,y�nf�9s���811�?L�����c�dX'scC�7<�>B| �=McP 2�5+��1�Sb�w`dE����Dc��.�{��� �.�Pa�!���~�!�]RZ!#��)��5�?`+�'p��l���A�����)����'��{����#�Z�(W��R�1
a��F�k���L��U��I�p�n���r����x�����#���FE��8���������w�N�iX�T�Ot��o��'o�/=��7�����P��R�l�����E���^u��6���v�>6�����:\5�	[��t�5�j�9��U�P��_��L5�v���HG�j�_�8U�m��>���\�j.�v��~����t~|����L�1�L�Q�m��Q��9\<�v�~=C�e��!`Bp��:�����b�a��|�!�BI:a�RD����R��O���I�e2��'�k���>�s�n_=��6��+�����^����������k\!��USY���������q���,��~�V��u��/��of��Y\m���v�K�=�`��D������������0E��9��#���#A���������[��oR�����=p�v�Z�����q�\�2�]�/������s��@�N�	69s:�����U>�4H���$��l?���������)L���`�7���+5:S�3Kx��������3�"Am�o�!�6r��	��H���<)E����]���.��m`h�t�"��y�S1�Gz:���h��]���Hi�w��F��-���.�sg������2��CU���	..*xN.*3D�}���}�A�
������M�Y������q��������B�(sS���k�F�*�X�c�,Q��$��L���T-W���)@�"eqG�BV����P���4�i�YsGpB�X7�0�	�o@$) t: �<��f+�BGABo�m��w)w�uO��4���24�W\	��C�V���O���k��Z���_T���ej����XJ1��	��k�����^M<�����yM����|@G��}����PC<��0����(���GR�k
���jP�D����@��p*���H
���]8�;���e����y��@�nT����AT
�JV������"���9��x�F;LK
�7�q��0����~�*���k:�u�e����G<r��:�yX}��� �
��q��AY��m��x��?r�X�����f��cB��������� rj��8;T�$k�r�L���������H�����N-G;iT
^�1��5@2O���C�`P��)x�.L��j���@kj�
{�[7#�V%A��2���
��F�I"�5���(S�2�������u��J�T�|�����WsI�2�u:-&hp��Q��j\����}Ol������<�FH�$�����
C�x�O�N�����������@�^�����o<Y���U��R�X#��^���P�����Q-�*�^���P�y�	<S���L=
2����l21��~�?H����(7�L4�s��p��w�).v�&�G&��y�=����|��r�KM�1�z�l���A3�]���#GB�5�Ig*������Z��*���jl���F����k�j�.��5����b����|���X;���z7�e���N��
���Z������=f���s�����0N��������J��F���U2��]�$uD����{W4���5"u��!��Wr�\�+h�����F��x�5v����N3_IR*���`�E���Y�QNi����^�a����v$;�O����xc��91I��?��x{���������
�Q���c����j�|?����������`z ���0'����K�p\�<�e	�mVl��J��&
SH���L�h����Z�*���C��Do���'�E���������9�x�<�����:Tw2�&�h+�/�s='+�H��!k��4��v�v:
����5jW�9
j	�F}�q[�z���?p�S�������#9�\�[����)�����bA��n��R��0ww=ooy���
qk_b3�M�@%^�qFl����^����������%��ss/�����p|����d(7�7MF�^�h�0Z�]�o�O�����x�&.X#��e���{���m8�~��8��]y��1�%����[)���������oc�~���
n�;��j�9�yT��N0u��6���e��������d4���;����R|v���n�r\d�l(�5�v�6�����s^�������dHs��py����~z�l�Zw_�'o��F}2���Q�5�s�]x��a�����������~-~{�"lH�v9y^jV*.���?���ep�������i.�H��x��4�9�f���i�����%p3�1�(a��+%��_���C��Q%nWA�C����?w����*�}J�i����&��H�~����!&��c�x�,�J	�-�0�l��|�X�<�]����F�sT��E��F���U��V��������.K��hz@(�Jb�����1q���t\^&1�x�@;z�����@�o����Qr51=�Y1���s�q�e����^I�*�����S��=��[���h�w��}��b~<j��e7
5�a�F�V�Yo�����kv�F��4��/��2��P�i��r�gL��Kq��v6��L�8D��H�r�������-V���kyI��S��:T���?���;�o��_�|� �	�C)[��b_��I�.�aWz�~����e�^��!��Q�Q���2���\���U�U�}��+n�?4���
��C���2������;?gXW��d�H�7�K�����x-�0*�cRn����av��T\���<la��\[H�����Ze:M��{l ���}����uo"z�[I���|wd�k�ush
!�[
9�.��[�,s�Eh��0^d�N��������?6x�������-��"�(Z��~������]��]>���(�5T)�C��s�,���CN��
������Muc7����x��Q�W}7�aK���)�%��1��ZF�RB������*���6����m���6xa�:��������zQ#����?����3Y����d������� N5�_���i��nD��u��G������_aW���7�Tk��z����i+�����������"n�Fg�"���3�W��+�J��������l�W�����z%��C��/����K�����������_�8N?;|�mj� �g
��a�EX]��W��B@�X��������%�;�����:�*��B�*��B�*��B�*��B�j�Z�`�P�5jP�5jP�5jP�����?P����u�Q�u�Q�u�Q��h�?P�58���
���
������?P�	5�P����M���M���$�?P�5ZP�5Z'���-�����?P�jA�#�q5��P�ju�����m���m���m\
�������Fjt�Fjt�Fjt�FP�`�7�h�~&��O*��r�j������������9�?��M�����f����y�8t]y���F�[���7;�f�?5�f��G�i��N�T�!�����%A�Y�71�3~v � ��r����N{�W����of�r�$����EP|b����.{^��oHA�M#tC`�Q�f@��`�U��n��� ��b���-@a�R���/��h%�l�����&���j�eTF#�kr����~�{s���y�F
�P
��&,l�z������{
!4��.��x��]��[���N����
Y���_���������G�(��x��W�^^v���P,y�����\�*���b��l���%��x9�o����4�)������
�,��)#���0�h�������og�����e���eWS��p��z:�������cf�XI|z4d���QK/�fU�WZ�=�GP;"
(U�Y��%��R��o��+�JI"w!e�y���(��aE��������. ���j�J��Juw�zf���R#�R�]�iW��z�I���������{�D�j��u��SlG�r�!��v-���\+�a���u6�B�v�uT�[G���uT�YG���6�6��u�������uU�m�XO�X�V��R���b3�b3Q1��	H��]����:*mG^G������y�DrQ�� �����Z;�67�u����As��|W7�bW�� v��bW�� v����k��Or����c8m��M�L�t@��\m��/%.�����W ~vs[��k��"X���sA�UX���(~e>�K���l+�@�R�j�[]}k�oM��\U����u�l�'�/����VS���[C}k�x	��5�G���0PL���5�{���0�7"�L����X�`B/n�H �|s����G!B^I$���"Y��#Ii��7#��UNJT����"�Y���w�@���3#?�Un*�
H|��K��(�
�z�6�V��hR.O����M��[��}[� ��j�R�;�4���_�Fk�&�����/�,<RVBv��X���
�,'���DT#0�6��l<�H�uB�&�?�y��?y�}`k�'�W�LQ��T���@F�3��%��H�����B�y�/��l����*[�\0N�k?Z��f���T���&;2MG�r4M�,5����A�"���j�PJ�����
�\^M'�QcR���d�e����3��Y��tI3[Y�-m�o�����
�!�����Q2-��n���b��������_�ai\U&�UNoK@���[xr@C3���t�?!(�*�������9$������0���*��L�t���o�L�������/kz�g����=G��dR@J�(�]%	�?���o����Va�cr{�1�7�;5�H�-��B����\����2��D�JF9G�O"V��g��//{gW�?��_*�WaT�W�^PU��*m�<���l0�H�*�+Z��E��'#���@��5��dr�
u��=��A�11�!�h��k&i�t�,�IA�i��4�\��L�i���[l����+��],wT��V�<h��f!����MZrz�H4s�:5����Oy���	�l	!�I�=L+�6�
�"\>���Y.�^C��gidbM�lT�4�b�����0��=���8;%�}5%�}2M��`�I�z���v�Q��O���jd��JU�5�n��ix�����<�!�z�(��k�_tr��Rm�Y���wGr��c'�v�94JqP(��V�E8�8��?r�&���Gc1VJ����y������eb��>�s�_���{i��+�7B��2�������!I�����~S����/�-��}r��/;��������)y��_��}�<�����12l�Kd��=��6v���e��_���:O��?�M;r��3��=���+S�C���L����d�g�W�;�v��#�h�{[#7���[�d���I�y�h�j��q)�������N��?s�c��z��`��I=v�;$��=�cv��=�Y��f_&k��������F-7���B5�U�U�wj��=�F-N5��|d�q5=v����m;q�Y�v�����v�<W�����j�E��+���~����#�����|�4_"������=/���E,��x����c��<RZ�?2��2��,��I��G���d)����C�v���r<8���YR�W����%��Vh���~�D�$q�mj$],��y����]�Q�dte*�:�|�^m�0=zb����������-�t���#��e�B��P�:�L��Z{�=�;2/nZ9��"�k�=���7<<3��4+�cL���$�Q���"~�� ���3?#j~<n����~�[�x<�'�?�g�v
#303Pavel Stehule
pavel.stehule@gmail.com
In reply to: Andrew Dunstan (#302)
Re: [Proposal] Global temporary tables

ne 28. 3. 2021 v 15:07 odesílatel Andrew Dunstan <andrew@dunslane.net>
napsal:

On 3/17/21 7:59 AM, wenjing wrote:

ok

The cause of the problem is that the name of the dependent function
(readNextTransactionID) has changed. I fixed it.

This patch(V43) is base on 9fd2952cf4920d563e9cea51634c5b364d57f71a

Wenjing

I have fixed this patch so that

a) it applies cleanly

b) it uses project best practice for catalog Oid assignment.

However, as noted elsewhere it fails the recovery TAP test.

I also note this:

diff --git a/src/test/regress/parallel_schedule
b/src/test/regress/parallel_schedule
index 312c11a4bd..d44fa62f4e 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -129,3 +129,10 @@ test: fast_default
# run stats by itself because its delay may be insufficient under heavy
load
test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean

Tests that need to run in parallel should use either the isolation
tester framework (which is explicitly for testing things concurrently)
or the TAP test framework.

Adding six test files to the regression test suite for this one feature
is not a good idea. You should have one regression test script ideally,
and it should be added as appropriate to both the parallel and serial
schedules (and not at the end). Any further tests should be added using
the other frameworks mentioned.

* bad name of GTT-README - the convention is README.gtt

* Typo - "ofa"

2) Use beforeshmemexit to ensure that all files ofa session GTT are deleted
when
the session exits.

* Typo "nd"

3) GTT storage file cleanup during abnormal situations
When a backend exits abnormally (such as oom kill), the startup process
starts
recovery before accepting client connection. The same startup process checks
nd removes all GTT files before redo WAL.

* This comment is wrong

  /*
+ * Global temporary table is allowed to be dropped only when the
+ * current session is using it.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(rel))
+ {
+ if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+ errmsg("cannot drop global temporary table %s when other backend attached
it.",
+ RelationGetRelationName(rel))));
+ }

* same wrong comment

  /*
+ * Global temporary table is allowed to be dropped only when the
+ * current session is using it.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(rel))
+ {
+ if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+ errmsg("cannot drop global temporary table %s when other backend attached
it.",
+ RelationGetRelationName(rel))));
+ }

* typo "backand"

+/*
+ * Check if there are other backends using this GTT besides the current
backand.
+ */

There is not user's documentation

Regards

Pavel

Show quoted text

cheers

andrew

--

Andrew Dunstan
EDB: https://www.enterprisedb.com

#304曾文旌
wenjing.zwj@alibaba-inc.com
In reply to: Pavel Stehule (#300)
1 attachment(s)
Re: [Proposal] Global temporary tables

2021年3月28日 15:27,Pavel Stehule <pavel.stehule@gmail.com> 写道:

Hi

st 17. 3. 2021 v 12:59 odesílatel wenjing <wjzeng2012@gmail.com <mailto:wjzeng2012@gmail.com>> napsal:
ok

The cause of the problem is that the name of the dependent function (readNextTransactionID) has changed. I fixed it.

This patch(V43) is base on 9fd2952cf4920d563e9cea51634c5b364d57f71a

Wenjing

I tested this patch and make check-world fails

make[2]: Vstupuje se do adresáře „/home/pavel/src/postgresql.master/src/test/recovery“
rm -rf '/home/pavel/src/postgresql.master/src/test/recovery'/tmp_check
/usr/bin/mkdir -p '/home/pavel/src/postgresql.master/src/test/recovery'/tmp_check
cd . && TESTDIR='/home/pavel/src/postgresql.master/src/test/recovery' PATH="/home/pavel/src/postgresql.master/tmp_install/usr/local/pgsql/master/bin:$PATH" LD_LIBRARY_PATH="/home/pavel/src/postgresql.master/tmp_install/usr/local/pgsql/master/lib" PGPORT='65432' PG_REGRESS='/home/pavel/src/postgresql.master/src/test/recovery/../../../src/test/regress/pg_regress' REGRESS_SHLIB='/home/pavel/src/postgresql.master/src/test/regress/regress.so' /usr/bin/prove -I ../../../src/test/perl/ -I . t/*.pl
t/001_stream_rep.pl <http://001_stream_rep.pl/&gt; .................. ok
t/002_archiving.pl <http://002_archiving.pl/&gt; ................... ok
t/003_recovery_targets.pl <http://003_recovery_targets.pl/&gt; ............ ok
t/004_timeline_switch.pl <http://004_timeline_switch.pl/&gt; ............. ok
t/005_replay_delay.pl <http://005_replay_delay.pl/&gt; ................ ok
t/006_logical_decoding.pl <http://006_logical_decoding.pl/&gt; ............ ok
t/007_sync_rep.pl <http://007_sync_rep.pl/&gt; .................... ok
t/008_fsm_truncation.pl <http://008_fsm_truncation.pl/&gt; .............. ok
t/009_twophase.pl <http://009_twophase.pl/&gt; .................... ok
t/010_logical_decoding_timelines.pl <http://010_logical_decoding_timelines.pl/&gt; .. ok
t/011_crash_recovery.pl <http://011_crash_recovery.pl/&gt; .............. ok
t/012_subtransactions.pl <http://012_subtransactions.pl/&gt; ............. ok
t/013_crash_restart.pl <http://013_crash_restart.pl/&gt; ............... ok
t/014_unlogged_reinit.pl <http://014_unlogged_reinit.pl/&gt; ............. ok
t/015_promotion_pages.pl <http://015_promotion_pages.pl/&gt; ............. ok
t/016_min_consistency.pl <http://016_min_consistency.pl/&gt; ............. ok
t/017_shm.pl <http://017_shm.pl/&gt; ......................... skipped: SysV shared memory not supported by this platform
t/018_wal_optimize.pl <http://018_wal_optimize.pl/&gt; ................ ok
t/019_replslot_limit.pl <http://019_replslot_limit.pl/&gt; .............. ok
t/020_archive_status.pl <http://020_archive_status.pl/&gt; .............. ok
t/021_row_visibility.pl <http://021_row_visibility.pl/&gt; .............. ok
t/022_crash_temp_files.pl <http://022_crash_temp_files.pl/&gt; ............ 1/9
# Failed test 'one temporary file'
# at t/022_crash_temp_files.pl <http://022_crash_temp_files.pl/&gt; line 231.
# got: '0'
# expected: '1'
t/022_crash_temp_files.pl <http://022_crash_temp_files.pl/&gt; ............ 9/9 # Looks like you failed 1 test of 9.
t/022_crash_temp_files.pl <http://022_crash_temp_files.pl/&gt; ............ Dubious, test returned 1 (wstat 256, 0x100)
Failed 1/9 subtests
t/023_pitr_prepared_xact.pl <http://023_pitr_prepared_xact.pl/&gt; .......... ok

Test Summary Report
-------------------
t/022_crash_temp_files.pl <http://022_crash_temp_files.pl/&gt; (Wstat: 256 Tests: 9 Failed: 1)
Failed test: 8
Non-zero exit status: 1
Files=23, Tests=259, 115 wallclock secs ( 0.21 usr 0.06 sys + 28.57 cusr 18.01 csys = 46.85 CPU)
Result: FAIL
make[2]: *** [Makefile:19: check] Chyba 1
make[2]: Opouští se adresář „/home/pavel/src/postgresql.master/src/test/recovery“
make[1]: *** [Makefile:49: check-recovery-recurse] Chyba 2
make[1]: Opouští se adresář „/home/pavel/src/postgresql.master/src/test“
make: *** [GNUmakefile:71: check-world-src/test-recurse] Chyba 2

This is because part of the logic of GTT is duplicated with the new commid cd91de0d17952b5763466cfa663e98318f26d357
that is commit by Tomas Vondra merge 11 days ago: "Remove Temporary Files after Backend Crash”.
The "Remove Temporary Files after Backend Crash” is exactly what GTT needs, or even better.
Therefore, I chose to delete the temporary file cleanup logic in the GTT path.

Let me update a new version.

Wenjing

Show quoted text

Regards

Pavel

Attachments:

smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#305曾文旌
wenjing.zwj@alibaba-inc.com
In reply to: Andrew Dunstan (#302)
1 attachment(s)
Re: [Proposal] Global temporary tables

2021年3月28日 21:07,Andrew Dunstan <andrew@dunslane.net> 写道:

On 3/17/21 7:59 AM, wenjing wrote:

ok

The cause of the problem is that the name of the dependent function
(readNextTransactionID) has changed. I fixed it.

This patch(V43) is base on 9fd2952cf4920d563e9cea51634c5b364d57f71a

Wenjing

I have fixed this patch so that

a) it applies cleanly

b) it uses project best practice for catalog Oid assignment.

However, as noted elsewhere it fails the recovery TAP test.

I also note this:

diff --git a/src/test/regress/parallel_schedule
b/src/test/regress/parallel_schedule
index 312c11a4bd..d44fa62f4e 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -129,3 +129,10 @@ test: fast_default
# run stats by itself because its delay may be insufficient under heavy
load
test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean

Tests that need to run in parallel should use either the isolation
tester framework (which is explicitly for testing things concurrently)
or the TAP test framework.

Adding six test files to the regression test suite for this one feature
is not a good idea. You should have one regression test script ideally,
and it should be added as appropriate to both the parallel and serial
schedules (and not at the end). Any further tests should be added using
the other frameworks mentioned.

You're right, it doesn't look good.
I'll organize them and put them in place.

Wenjing.

Show quoted text

cheers

andrew

--

Andrew Dunstan
EDB: https://www.enterprisedb.com

<global_temporary_table_v44-pg14.patch.gz>

Attachments:

smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#306曾文旌
wenjing.zwj@alibaba-inc.com
In reply to: Pavel Stehule (#303)
1 attachment(s)
Re: [Proposal] Global temporary tables

2021年3月29日 16:37,Pavel Stehule <pavel.stehule@gmail.com> 写道:

ne 28. 3. 2021 v 15:07 odesílatel Andrew Dunstan <andrew@dunslane.net <mailto:andrew@dunslane.net>> napsal:

On 3/17/21 7:59 AM, wenjing wrote:

ok

The cause of the problem is that the name of the dependent function
(readNextTransactionID) has changed. I fixed it.

This patch(V43) is base on 9fd2952cf4920d563e9cea51634c5b364d57f71a

Wenjing

I have fixed this patch so that

a) it applies cleanly

b) it uses project best practice for catalog Oid assignment.

However, as noted elsewhere it fails the recovery TAP test.

I also note this:

diff --git a/src/test/regress/parallel_schedule
b/src/test/regress/parallel_schedule
index 312c11a4bd..d44fa62f4e 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -129,3 +129,10 @@ test: fast_default
# run stats by itself because its delay may be insufficient under heavy
load
test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean

Tests that need to run in parallel should use either the isolation
tester framework (which is explicitly for testing things concurrently)
or the TAP test framework.

Adding six test files to the regression test suite for this one feature
is not a good idea. You should have one regression test script ideally,
and it should be added as appropriate to both the parallel and serial
schedules (and not at the end). Any further tests should be added using
the other frameworks mentioned.

* bad name of GTT-README - the convention is README.gtt

* Typo - "ofa"

2) Use beforeshmemexit to ensure that all files ofa session GTT are deleted when
the session exits.

* Typo "nd"

3) GTT storage file cleanup during abnormal situations
When a backend exits abnormally (such as oom kill), the startup process starts
recovery before accepting client connection. The same startup process checks
nd removes all GTT files before redo WAL.

* This comment is wrong

/*
+ * Global temporary table is allowed to be dropped only when the
+ * current session is using it.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(rel))
+ {
+ if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+ errmsg("cannot drop global temporary table %s when other backend attached it.",
+ RelationGetRelationName(rel))));
+ }

* same wrong comment

/*
+ * Global temporary table is allowed to be dropped only when the
+ * current session is using it.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(rel))
+ {
+ if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+ errmsg("cannot drop global temporary table %s when other backend attached it.",
+ RelationGetRelationName(rel))));
+ }

* typo "backand"

+/*
+ * Check if there are other backends using this GTT besides the current backand.
+ */

There is not user's documentation

This is necessary, and I will make a separate document patch.

Wenjing.

Show quoted text

Regards

Pavel

cheers

andrew

--

Andrew Dunstan
EDB: https://www.enterprisedb.com <https://www.enterprisedb.com/&gt;

Attachments:

smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#307wenjing
wjzeng2012@gmail.com
In reply to: Pavel Stehule (#303)
1 attachment(s)
Re: [Proposal] Global temporary tables

HI all

I fixed the document description error and the regression test bug
mentioned by Pavel.
This patch(V45) is base on 30aaab26e52144097a1a5bbb0bb66ea1ebc0cb81
Please give me feedback.

Wenjing

Attachments:

global_temporary_table_v45-pg14.patchapplication/octet-stream; name=global_temporary_table_v45-pg14.patchDownload
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index d897bbe..aef7996 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -168,6 +168,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * In order to avoid consistency problems, the global temporary table
+	 * uses ShareUpdateExclusiveLock.
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temporary table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},
 	/* list terminator */
 	{{NULL}}
 };
@@ -1817,6 +1830,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1962,12 +1977,16 @@ bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
 	/*
-	 * There are no options for partitioned tables yet, but this is able to do
-	 * some validation.
+	 * Add option for global temp partitioned tables.
 	 */
+	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)}
+	};
+
 	return (bytea *) build_reloptions(reloptions, validate,
 									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+									  sizeof(StdRdOptions), tab, lengthof(tab));
 }
 
 /*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index 1ff1bf8..21eb87b 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1026,7 +1026,7 @@ gistproperty(Oid index_oid, int attno,
 XLogRecPtr
 gistGetFakeLSN(Relation rel)
 {
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	if (RELATION_IS_TEMP(rel))
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 0752fb3..5c85d77 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -151,7 +151,7 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	if (!RELATION_IS_TEMP(index))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 7a9a640..67fcc75 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -591,7 +591,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
@@ -644,7 +644,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 efe8761..88f51d2 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -63,6 +63,7 @@
 #include "access/xlog.h"
 #include "catalog/index.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -449,9 +450,13 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
 	Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
-	/* not every AM requires these to be valid, but heap does */
-	Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
-	Assert(MultiXactIdIsValid(onerel->rd_rel->relminmxid));
+	/*
+	 * not every AM requires these to be valid, but regular heap does.
+	 * Transaction information for the global temp table will be stored
+	 * in the local hash table, not the catalog.
+	 */
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ MultiXactIdIsValid(onerel->rd_rel->relminmxid));
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index ef48679..df0e843 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -677,6 +678,14 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		/*
+		 * If a global temporary table storage file is not initialized in the
+		 * current backend, its index does not have a root page, just returns 0.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 5fcd004..58b994c 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -212,7 +212,8 @@ Boot_CreateStmt:
 												   mapped_relation,
 												   true,
 												   &relfrozenxid,
-												   &relminmxid);
+												   &relminmxid,
+												   false);
 						elog(DEBUG4, "bootstrap relation created");
 					}
 					else
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index e36a960..be0b600 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -43,6 +43,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/README.gtt b/src/backend/catalog/README.gtt
new file mode 100644
index 0000000..721f25a
--- /dev/null
+++ b/src/backend/catalog/README.gtt
@@ -0,0 +1,166 @@
+Global Temporary Table(GTT)
+==============
+
+Feature description
+--------------------------------
+
+Previously, temporary tables are defined once and automatically
+created (starting with empty contents) in every session before using them.
+
+The temporary table implementation in PostgreSQL, known as Local temp tables(LTT),
+did not fully comply with the SQL standard. This version added the support of
+Global Temporary Table .
+
+The metadata of Global Temporary Table is persistent and shared among sessions.
+The data stored in the Global temporary table is independent of sessions. This
+means, when a session creates a Global Temporary Table and writes some data.
+Other sessions cannot see those data, but they have an empty Global Temporary
+Table with same schema.
+
+Like local temporary table, Global Temporary Table supports ON COMMIT PRESERVE ROWS
+or ON COMMIT DELETE ROWS clause, so that data in the temporary table can be
+cleaned up or reserved automatically when a session exits or a transaction COMMITs.
+
+Unlike Local Temporary Table, Global Temporary Table does not support
+ON COMMIT DROP clauses.
+
+In following paragraphs, we use GTT for Global Temporary Table and LTT for
+local temporary table.
+
+Main design ideas
+-----------------------------------------
+
+STORAGE & BUFFER
+
+In general, GTT and LTT use the same storage and buffer design and
+implementation. The storage files for both types of temporary tables are named
+as t_backendid_relfilenode, and the local buffer is used to cache the data.
+
+The schema of GTTs is shared among sessions while their data are not. We build
+a new mechanisms to manage those non-shared data and their statistics.
+Here is the summary of changes:
+
+1) CATALOG
+GTTs store session-specific data. The storage information of GTTs'data, their
+transaction information, and their statistics are not stored in the catalog.
+
+2) STORAGE INFO & STATISTICS & TRANSACTION
+In order to maintain durability and availability of GTTs'session-specific data,
+their storage information, statistics, and transaction information is managed
+in a local hash table tt_storage_local_hash.
+
+3) DDL
+A shared hash table active_gtt_shared_hash is added to track the state of the
+GTT in a different session. This information is recorded in the hash table
+during the DDL execution of the GTT.
+
+4) LOCK
+The data stored in a GTT can only be modified or accessed by owning session.
+The statements that only modify data in a GTT do not need a high level of table
+locking.
+Changes to the GTT's metadata affect all sessions.
+The operations making those changes include truncate GTT, Vacuum/Cluster GTT,
+and Lock GTT.
+
+Detailed design
+-----------------------------------------
+
+1. CATALOG
+1.1 relpersistence
+define RELPERSISTENCEGLOBALTEMP 'g'
+Mark Global Temporary Table in pg_class relpersistence to 'g'. The relpersistence
+of indexes created on the GTT, sequences on GTT and toast tables on GTT are
+also set to 'g'
+
+1.2 on commit clause
+LTT's status associated with on commit DELETE ROWS and on commit PRESERVE ROWS
+is not stored in catalog. Instead, GTTs need a bool value on_commit_delete_rows
+in reloptions which is shared among sessions.
+
+1.3 gram.y
+GTT is already supported in syntax tree. We remove the warning message
+"GLOBAL is deprecated in temporary table creation" and mark
+relpersistence = RELPERSISTENCEGLOBALTEMP.
+
+2. STORAGE INFO & STATISTICS DATA & TRANSACTION INFO
+2.1. gtt_storage_local_hash
+Each backend creates a local hashtable gtt_storage_local_hash to track a GTT's
+storage file information, statistics, and transaction information.
+
+2.2 GTT storage file info track
+1) When one session inserts data into a GTT for the first time, record the
+storage info to gtt_storage_local_hash.
+2) Use beforeshmemexit to ensure that all files of session GTT are deleted when
+the session exits.
+
+2.3 statistics info
+1) relpages reltuples relallvisible
+2) The statistics of each column from pg_statistic
+All the above information is stored in gtt_storage_local_hash.
+When doing vacuum or analyze, GTT's statistic is updated, which is used by
+the SQL planner.
+The statistics summarizes only data in the current session.
+
+2.3 transaction info track
+frozenxid minmulti from pg_class is stored to gtt_storage_local_hash.
+
+4 DDL
+4.1. active_gtt_shared_hash
+This is the hash table created in shared memory to trace the GTT files initialized
+in each session. Each hash entry contains a bitmap that records the backendid of
+the initialized GTT file. With this hash table, we know which backend/session
+is using this GTT. Such information is used during GTT's DDL operations.
+
+4.1 DROP GTT
+One GTT is allowed to be deleted when there is only one session using the table
+and the session is the current session.
+After holding the AccessExclusiveLock lock on GTT, active_gtt_shared_hash
+is checked to ensure that.
+
+4.2 ALTER GTT/DROP INDEX ON GTT
+Same as drop GTT.
+
+4.3 CREATE INDEX ON GTT
+1) create index on GTT statements build index based on local data in a session.
+2) After the index is created, record the index metadata to the catalog.
+3) Other sessions can enable or disable the local GTT index.
+
+5 LOCK
+
+5.1 TRUNCATE GTT
+The truncate GTT command uses RowExclusiveLock, not AccessExclusiveLock, because
+this command only cleans up local data and local buffers in current session.
+
+5.2 CLUSTER GTT/VACUUM FULL GTT
+Same as truncate GTT.
+
+5.3 Lock GTT
+A lock GTT statement does not hold any table locks.
+
+6 MVCC commit log(clog) cleanup
+
+The GTT storage file contains transaction information. Queries for GTT data rely
+on transaction information such as clog. The transaction information required by
+each session may be completely different. We need to ensure that the transaction
+information of the GTT data is not cleaned up during its lifetime and that
+transaction resources are recycled at the instance level.
+
+6.1 The session level GTT oldest frozenxid
+1) To manage all GTT transaction information, add session level oldest frozenxid
+in each session. When one GTT is created or removed, record the session level
+oldest frozenxid and store it in MyProc.
+2) When vacuum advances the database's frozenxid, session level oldest frozenxid
+should be considered. This is acquired by searching all of MyProc. This way,
+we can avoid the clog required by GTTs to be cleaned.
+
+6.2 vacuum GTT
+Users can perform vacuum over a GTT to clean up local data in the GTT.
+
+6.3 autovacuum GTT
+Autovacuum skips all GTTs, because the data in GTTs is only visible in current
+session.
+
+7 OTHERS
+Parallel query
+Planner does not produce parallel query plans for SQL related to GTT. Because
+GTT private data cannot be accessed across processes.
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 245d536..a38b594 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -441,6 +441,7 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 9f63032..4c2aaa6 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -62,6 +62,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,
@@ -305,7 +307,8 @@ heap_create(const char *relname,
 			bool mapped_relation,
 			bool allow_system_table_mods,
 			TransactionId *relfrozenxid,
-			MultiXactId *relminmxid)
+			MultiXactId *relminmxid,
+			bool skip_create_storage)
 {
 	bool		create_storage;
 	Relation	rel;
@@ -370,7 +373,9 @@ heap_create(const char *relname,
 	 * storage is already created, so don't do it here.  Also don't create it
 	 * for relkinds without physical storage.
 	 */
-	if (!RELKIND_HAS_STORAGE(relkind) || OidIsValid(relfilenode))
+	if (!RELKIND_HAS_STORAGE(relkind) ||
+		OidIsValid(relfilenode) ||
+		skip_create_storage)
 		create_storage = false;
 	else
 	{
@@ -428,7 +433,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -999,6 +1004,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -1037,8 +1043,21 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/*
+	 * The transaction information of the global temporary table is stored
+	 * in the local hash table, not in catalog.
+	 */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1303,7 +1322,8 @@ heap_create_with_catalog(const char *relname,
 							   mapped_relation,
 							   allow_system_table_mods,
 							   &relfrozenxid,
-							   &relminmxid);
+							   &relminmxid,
+							   false);
 
 	Assert(relid == RelationGetRelid(new_rel_desc));
 
@@ -1410,6 +1430,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1996,6 +2017,19 @@ heap_drop_with_catalog(Oid relid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
 	/*
+	 * Only when other sessions are not using this Global temporary table,
+	 * is it allowed to DROP it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot drop global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
+	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
 	if (RELKIND_HAS_STORAGE(rel->rd_rel->relkind))
@@ -3260,7 +3294,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3272,7 +3306,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3318,8 +3352,16 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		/*
+		 * Truncate global temporary table only clears local data,
+		 * so only low-level locks need to be held.
+		 */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3352,6 +3394,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3360,23 +3403,47 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	/* For global temporary table only */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		/*
+		 * If this GTT is not initialized in current backend, there is
+		 * no needs to anything.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(rel)))
+			return;
+
+		/*
+		 * Truncate GTT only clears local data, so only low-level locks
+		 * need to be held.
+		 */
+		lockmode = RowExclusiveLock;
+	}
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	/*
+	 * After the data is cleaned up on the GTT, the transaction information
+	 * for the data(stored in local hash table) is also need reset.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(RelationGetRelid(rel), 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index a628b32..a793be7 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -55,6 +55,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "commands/defrem.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -732,6 +733,29 @@ index_create(Relation heapRelation,
 	char		relkind;
 	TransactionId relfrozenxid;
 	MultiXactId relminmxid;
+	bool		skip_create_storage = false;
+
+	/* For global temporary table only */
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temporary table with concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot reindex global temporary tables concurrently")));
+
+		/*
+		 * For the case that some backend is applied relcache message to create
+		 * an index on a global temporary table, if this table in the current
+		 * backend are not initialized, the creation of index storage on the
+		 * table are also skipped.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+		{
+			skip_create_storage = true;
+			flags |= INDEX_CREATE_SKIP_BUILD;
+		}
+	}
 
 	/* constraint flags can only be set when a constraint is requested */
 	Assert((constr_flags == 0) ||
@@ -939,7 +963,8 @@ index_create(Relation heapRelation,
 								mapped_relation,
 								allow_system_table_mods,
 								&relfrozenxid,
-								&relminmxid);
+								&relminmxid,
+								skip_create_storage);
 
 	Assert(relfrozenxid == InvalidTransactionId);
 	Assert(relminmxid == InvalidMultiXactId);
@@ -2272,7 +2297,7 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 * lock (see comments in RemoveRelations), and a non-concurrent DROP is
 	 * more efficient.
 	 */
-	Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+	Assert(!RelpersistenceTsTemp(get_rel_persistence(indexId)) ||
 		   (!concurrent && !concurrent_lock_mode));
 
 	/*
@@ -2305,6 +2330,20 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
 	/*
+	 * Allow to drop index on global temporary table when only current
+	 * backend use it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation) &&
+		is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+	{
+		ereport(ERROR,
+			(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+			 errmsg("cannot drop index %s or global temporary table %s",
+					RelationGetRelationName(userIndexRelation), RelationGetRelationName(userHeapRelation)),
+			 errhint("Because the index is created on the global temporary table and other backend attached it.")));
+	}
+
+	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
 	 *
@@ -2912,6 +2951,7 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = RELATION_IS_GLOBAL_TEMP(rel);
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -3006,20 +3046,37 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
-		{
-			rd_rel->relpages = (int32) relpages;
-			dirty = true;
-		}
-		if (rd_rel->reltuples != (float4) reltuples)
+		/* For global temporary table */
+		if (is_gtt)
 		{
-			rd_rel->reltuples = (float4) reltuples;
-			dirty = true;
+			/* Update GTT'statistics into local relcache */
+			rel->rd_rel->relpages = (int32) relpages;
+			rel->rd_rel->reltuples = (float4) reltuples;
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+
+			/* Update GTT'statistics into local hashtable */
+			up_gtt_relstats(RelationGetRelid(rel), relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+		else
 		{
-			rd_rel->relallvisible = (int32) relallvisible;
-			dirty = true;
+			if (rd_rel->relpages != (int32) relpages)
+			{
+				rd_rel->relpages = (int32) relpages;
+				dirty = true;
+			}
+
+			if (rd_rel->reltuples != (float4) reltuples)
+			{
+				rd_rel->reltuples = (float4) reltuples;
+				dirty = true;
+			}
+
+			if (rd_rel->relallvisible != (int32) relallvisible)
+			{
+				rd_rel->relallvisible = (int32) relallvisible;
+				dirty = true;
+			}
 		}
 	}
 
@@ -3132,6 +3189,26 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, progress_index, progress_vals);
 	}
 
+	/* For build index on global temporary table */
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		/*
+		 * If the storage for the index in this session is not initialized,
+		 * it needs to be created.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			/* Before create init storage, fix the local Relcache first */
+			force_enable_gtt_index(indexRelation);
+
+			Assert(gtt_storage_attached(RelationGetRelid(heapRelation)));
+
+			/* Init storage for index */
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3687,6 +3764,20 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	if (!OidIsValid(heapId))
 		return;
 
+	/*
+	 * For reindex on global temporary table, If the storage for the index
+	 * in current backend is not initialized, nothing is done.
+	 */
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		!gtt_storage_attached(indexId))
+	{
+		/* Suppress use of the target index while rebuilding it */
+		SetReindexProcessing(heapId, indexId);
+		/* Re-allow use of target index */
+		ResetReindexProcessing();
+		return;
+	}
+
 	if ((params->options & REINDEXOPT_MISSING_OK) != 0)
 		heapRelation = try_table_open(heapId, ShareLock);
 	else
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 005e029..5e59b47 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -655,6 +655,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/* Do not allow create global temporary table in temporary schemas */
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp table in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index cba7a9a..9cf6802 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,6 +27,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -61,6 +62,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			temprelOid;			/* InvalidOid if not a global temporary rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -115,7 +117,7 @@ AddPendingSync(const RelFileNode *rnode)
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -126,7 +128,12 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/*
+		 * Global temporary table and local temporary table use same
+		 * design on storage module.
+		 */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -154,6 +161,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
@@ -165,6 +173,21 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		AddPendingSync(&rnode);
 	}
 
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		Assert(rel && RELATION_IS_GLOBAL_TEMP(rel));
+
+		/*
+		 * Remember the reloid of global temporary table, which is used for
+		 * transaction commit or rollback.
+		 * see smgrDoPendingDeletes.
+		 */
+		pending->temprelOid = RelationGetRelid(rel);
+
+		/* Remember global temporary table storage info to localhash */
+		remember_gtt_storage_info(rnode, rel);
+	}
+
 	return srel;
 }
 
@@ -201,12 +224,21 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
 	/*
+	 * Remember the reloid of global temporary table, which is used for
+	 * transaction commit or rollback.
+	 * see smgrDoPendingDeletes.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->temprelOid = RelationGetRelid(rel);
+
+	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
 	 * once with atCommit false.  Hence, it will be physically deleted at end
@@ -602,6 +634,7 @@ smgrDoPendingDeletes(bool isCommit)
 	int			nrels = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -631,14 +664,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->temprelOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -648,12 +685,21 @@ smgrDoPendingDeletes(bool isCommit)
 
 	if (nrels > 0)
 	{
+		int	i;
+
 		smgrdounlinkall(srels, nrels, false);
 
-		for (int i = 0; i < nrels; i++)
+		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* Delete global temporary table info in localhash */
+			if (gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..ec10a2d
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1648 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  The body implementation of Global Temparary table.
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *	  See src/backend/catalog/GTT_README for Global temparary table's
+ *	  requirements and design.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_info_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+int		vacuum_gtt_defer_check_age = 0;
+
+/*
+ * The Global temporary table's shared hash table data structure
+ */
+typedef struct gtt_ctl_data
+{
+	LWLock		lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+/* record this global temporary table in which backends are being used */
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+/*
+ * The Global temporary table's local hash table data structure
+ */
+/* Record the storage information and statistical information of the global temporary table */
+typedef struct
+{
+	Oid			relfilenode;
+	Oid			spcnode;
+
+	/* pg_class relstat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+
+	/* pg_statistic column stat */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+
+	Oid			oldrelid;			/* remember the source of relid, before the switch relfilenode. */
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_free_statistics(gtt_relfilenode *rnode);
+static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok);
+static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok);
+static Bitmapset *copy_active_gtt_bitmap(Oid relid);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+/*
+ * Calculate shared hash table entry size for GTT.
+ */
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	/* hash entry header size */
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	/*
+	 * hash entry data size
+	 * this is a bitmap in shared memory, each backend have a bit.
+	 */
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+/*
+ * Calculate shared hash table max size for GTT.
+ */
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	/* shared hash header size */
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	/* hash entry size */
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	/* max size */
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+/*
+ * Initialization shared hash table for GTT.
+ */
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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();
+	}
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(gtt_fnode);
+	info.entrysize = action_gtt_shared_hash_entry_size();
+	active_gtt_shared_hash =
+		ShmemInitHash("active gtt shared hash",
+						gtt_shared_ctl->max_entry,
+						gtt_shared_ctl->max_entry,
+						&info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+/*
+ * Record GTT relid to shared hash table, which means that current backend is using this GTT.
+ */
+static void
+gtt_storage_checkin(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			found;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *)&(fnode), 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_global_temporary_table.")));
+	}
+
+	if (!found)
+	{
+		int			wordnum;
+
+		/* init bitmap */
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	/* record itself in bitmap */
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+/*
+ * Remove the GTT relid record from the shared hash table which means that current backend is
+ * not use this GTT.
+ */
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode				fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	if (!skiplock)
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		if (!skiplock)
+			LWLockRelease(&gtt_shared_ctl->lock);
+
+		if (isCommit)
+			elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+	/* remove itself from bitmap */
+	bms_del_member(entry->map, MyBackendId);
+
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+
+	if (!skiplock)
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+	return;
+}
+
+/*
+ * Gets usage information for a GTT from shared hash table.
+ * The information is in the form of bitmap.
+ * Quickly copy the entire bitmap from shared memory and return it.
+ * that to avoid holding locks for a long time.
+ */
+static Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	Bitmapset	*map_copy = NULL;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		return NULL;
+	}
+
+	Assert(entry->map);
+
+	/* copy the entire bitmap */
+	if (!bms_is_empty(entry->map))
+		map_copy = bms_copy(entry->map);
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return map_copy;
+}
+
+/*
+ * Check if there are other backends using this GTT besides the current backend.
+ */
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool		in_use = false;
+	int			num_use = 0;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		return false;
+	}
+
+	Assert(entry->map);
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+	/* how many backend are using this GTT */
+	num_use = bms_num_members(entry->map);
+	if (num_use == 0)
+		in_use = false;
+	else if (num_use == 1)
+	{
+		/* check if this is itself */
+		if(bms_is_member(MyBackendId, entry->map))
+			in_use = false;
+		else
+			in_use = true;
+	}
+	else
+		in_use = true;
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return in_use;
+}
+
+/*
+ * Record GTT information to local hash.
+ * They include GTT storage info, transaction info and statistical info.
+ */
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+	gtt_local_hash_entry	*entry;
+	MemoryContext			oldcontext;
+	gtt_relfilenode			*new_node = NULL;
+	Oid						relid = RelationGetRelid(rel);
+	int 					natts = 0;
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	/* First time through: initialize the hash table */
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		HASHCTL		ctl;
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_info_context =
+			AllocSetContextCreate(CacheMemoryContext,
+								"gtt info context",
+								ALLOCSET_DEFAULT_SIZES);
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		ctl.hcxt = gtt_info_context;
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+	}
+
+	Assert(CacheMemoryContext);
+	Assert(gtt_info_context);
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	entry = gtt_search_by_relid(relid, true);
+	if (!entry)
+	{
+		bool		found = false;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash,
+						(void *) &relid, HASH_ENTER, &found);
+
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+		entry->oldrelid = InvalidOid;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			/* record the on commit clause */
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			gtt_storage_checkin(relid);
+		}
+	}
+
+	/* record storage info relstat columnstats and transaction info to relfilenode list */
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	new_node->natts = 0;
+	new_node->attnum = NULL;
+	new_node->att_stat_tups = NULL;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* init column stats structure */
+	natts = RelationGetNumberOfAttributes(rel);
+	new_node->attnum = palloc0(sizeof(int) * natts);
+	new_node->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+	new_node->natts = natts;
+
+	/* only heap have transaction info */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+
+		/**/
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+
+	/* Registration callbacks are used to trigger cleanup during process exit */
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+/*
+ * Remove GTT information from local hash when transaction commit/rollback.
+ */
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	gtt_relfilenode *d_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+		return;
+	}
+
+	d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+		else if (entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+			gtt_relfilenode *gttnode2 = NULL;
+
+			/*
+			 * For cluster GTT rollback.
+			 * We need to roll back the exchange relfilenode operation.
+			 */
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+			Assert(gttnode2->relfilenode == rnode.relNode);
+			Assert(list_length(entry->relfilenode_list) == 1);
+			/* rollback switch relfilenode */
+			gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+									   entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+									   false);
+			/* clean up footprint */
+			entry2->oldrelid = InvalidOid;
+
+			/* temp relfilenode need free */
+			d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+			Assert(d_rnode);
+		}
+		else
+		{
+			/* rollback transaction */
+			if (entry->relfilenode_list == NIL)
+			{
+				if (entry->relkind == RELKIND_RELATION ||
+					entry->relkind == RELKIND_SEQUENCE)
+					gtt_storage_checkout(relid, false, isCommit);
+
+				hash_search(gtt_storage_local_hash,
+						(void *) &(relid), HASH_REMOVE, NULL);
+			}
+
+			return;
+		}
+	}
+
+	/* Clean up transaction info from Local order list and MyProc */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+
+		/* this is valid relfrozenxid */
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	/* delete relfilenode from rel entry */
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	gtt_free_statistics(d_rnode);
+
+	if (entry->relfilenode_list == NIL)
+	{
+		/* this means we truncate this GTT at current backend */
+
+		/* tell shared hash that current backend will no longer use this GTT */
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(relid, false, isCommit);
+
+		if (isCommit && entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+
+			/* commit transaction at cluster GTT, need clean up footprint */
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			entry2->oldrelid = InvalidOid;
+		}
+
+		hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_REMOVE, NULL);
+	}
+
+	return;
+}
+
+/*
+ * Check if current backend is using this GTT.
+ */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool found = false;
+	gtt_local_hash_entry *entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (!OidIsValid(relid))
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry)
+		found = true;
+
+	return found;
+}
+
+/*
+ * When backend exit, bulk cleaning all GTT storage and local buffer of this backend.
+ */
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS				status;
+	gtt_local_hash_entry		*entry;
+	SMgrRelation	*srels = NULL;
+	Oid				*relids = NULL;
+	char			*relkinds = NULL;
+	int			nrels = 0,
+				nfiles = 0,
+				maxrels = 0,
+				maxfiles = 0,
+				i = 0;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	/* Search all relfilenode for GTT in current backend */
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel;
+			RelFileNode		rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel = smgropen(rnode, MyBackendId);
+
+			if (maxfiles == 0)
+			{
+				maxfiles = 32;
+				srels = palloc(sizeof(SMgrRelation) * maxfiles);
+			}
+			else if (maxfiles <= nfiles)
+			{
+				maxfiles *= 2;
+				srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+			}
+
+			srels[nfiles++] = srel;
+		}
+
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			relids  = palloc(sizeof(Oid) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			relids  = repalloc(relids , sizeof(Oid) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		relkinds[nrels] = entry->relkind;
+		relids[nrels] = entry->relid;
+		nrels++;
+	}
+
+	/* drop local buffer and storage */
+	if (nfiles > 0)
+	{
+		smgrdounlinkall(srels, nfiles, false);
+		for (i = 0; i < nfiles; i++)
+			smgrclose(srels[i]);
+
+		pfree(srels);
+	}
+
+	if (nrels)
+	{
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+		for (i = 0; i < nrels; i++)
+		{
+			/* tell shared hash */
+			if (relkinds[i] == RELKIND_RELATION ||
+				relkinds[i] == RELKIND_SEQUENCE)
+				gtt_storage_checkout(relids[i], true, false);
+		}
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+		pfree(relids);
+		pfree(relkinds);
+	}
+
+	/* set to global area */
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;
+
+	return;
+}
+
+/*
+ * Update GTT relstats(relpage/reltuple/relallvisible)
+ * to local hash.
+ */
+void
+up_gtt_relstats(Oid relid,
+					BlockNumber num_pages,
+					double num_tuples,
+					BlockNumber num_all_visible_pages,
+					TransactionId relfrozenxid,
+					TransactionId relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (!OidIsValid(relid))
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (num_pages > 0 &&
+		gtt_rnode->relpages != (int32)num_pages)
+		gtt_rnode->relpages = (int32)num_pages;
+
+	if (num_tuples > 0 &&
+		gtt_rnode->reltuples != (float4)num_tuples)
+		gtt_rnode->reltuples = (float4)num_tuples;
+
+	/* only heap contain transaction information and relallvisible */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (num_all_visible_pages > 0 &&
+			gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+		{
+			gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+		}
+
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNextTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			/* set to local order list */
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			/* set to global area */
+			set_gtt_session_relfrozenxid();
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search GTT relstats(relpage/reltuple/relallvisible)
+ * from local has.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update GTT info(definition is same as pg_statistic)
+ * to local hash.
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	MemoryContext			oldcontext;
+	int						i = 0;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return;
+
+	Assert(entry->relid == reloid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (gtt_rnode->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	/* switch context to gtt_info_context for store tuple at heap_form_tuple */
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == 0)
+		{
+			gtt_rnode->attnum[i] = attnum;
+			break;
+		}
+		else if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			heap_freetuple(gtt_rnode->att_stat_tups[i]);
+			gtt_rnode->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < gtt_rnode->natts);
+	Assert(gtt_rnode->att_stat_tups[i] == NULL);
+	gtt_rnode->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search GTT statistic info(definition is same as pg_statistic)
+ * from local hash.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	int						i = 0;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return NULL;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return NULL;
+
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			return gtt_rnode->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Insert a RelfrozenXID into the list and keep the list in order.
+ */
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell		*cell;
+	int				i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Remove a RelfrozenXID from order list gtt_session_relfrozenxid_list.
+ */
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+/*
+ * Update of backend Level oldest relfrozenxid to MyProc.
+ * This makes each backend's oldest RelFrozenxID globally visible.
+ */
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->backend_gtt_frozenxid != gtt_frozenxid)
+		MyProc->backend_gtt_frozenxid = gtt_frozenxid;
+}
+
+/*
+ * Get GTT column level data statistics.
+ */
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo 	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate *tupstore;
+	HeapTuple		tuple;
+	Relation		rel = NULL;
+	Oid				reloid = PG_GETARG_OID(0);
+	int				attnum = PG_GETARG_INT32(1);
+	char			rel_persistence;
+	TupleDesc	  	tupdesc;
+	MemoryContext 	oldcontext;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	/* get data from local hash */
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, NoLock);
+	relation_close(pg_tatistic, AccessShareLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get GTT table level data statistics.
+ */
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate	*tupstore;
+	TupleDesc		tupdesc;
+	MemoryContext	oldcontext;
+	HeapTuple		tuple;
+	Oid				reloid = PG_GETARG_OID(0);
+	Oid				relnode = 0;
+	char			rel_persistence;
+	BlockNumber		relpages = 0;
+	BlockNumber		relallvisible = 0;
+	uint32			relfrozenxid = 0;
+	uint32			relminmxid = 0;
+	double			reltuples = 0;
+	Relation		rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[6];
+		bool	isnull[6];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get a list of backend pids that are currently using this GTT.
+ */
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	PGPROC			*proc = NULL;
+	Bitmapset		*map = NULL;
+	Tuplestorestate *tupstore;
+	TupleDesc		tupdesc;
+	MemoryContext	oldcontext;
+	HeapTuple		tuple;
+	Oid				reloid = PG_GETARG_OID(0);
+	char			rel_persistence;
+	Relation		rel = NULL;
+	pid_t			pid = 0;
+	int				backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	/* get data from share hash */
+	map = copy_active_gtt_bitmap(reloid);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			/* backendid map to process pid */
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get backend level oldest relfrozenxid of each backend using GTT in current database.
+ */
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate *tupstore;
+	int				*pids = NULL;
+	uint32			*xids = NULL;
+	TupleDesc		tupdesc;
+	MemoryContext	oldcontext;
+	HeapTuple		tuple;
+	int				num_xid = MaxBackends + 1;
+	int				i = 0;
+	int				j = 0;
+	uint32			oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+
+	/* Get backend level oldest relfrozenxid in all backend that in MyDatabaseId use GTT */
+	oldest = list_all_backend_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+/*
+ * In order to build the GTT index, force enable GTT'index.
+ */
+void
+force_enable_gtt_index(Relation index)
+{
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(RelationGetRelid(index)));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+/*
+ * Fix the local state of the GTT's index.
+ */
+void
+gtt_fix_index_backend_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid heapOid = index->rd_index->indrelid;
+
+	/* Must be GTT */
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	/*
+	 * If this GTT is not initialized in the current backend,
+	 * its index status is temporarily set to invalid(local relcache).
+	 */
+	if (gtt_storage_attached(heapOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+/*
+ * During the SQL initialization of the executor (InitPlan)
+ * Initialize storage of GTT GTT'indexes and build empty index.
+ */
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int			i;
+	Oid			toastrelid;
+
+	if (!(operation == CMD_UTILITY || operation == CMD_INSERT))
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	/* Each GTT is initialized once in each backend */
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	/* init heap storage */
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+
+		index_build(relation, index, info, true, false);
+	}
+
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+
+		/* init index storage */
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid			indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+			/* build empty index */
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+/*
+ * Release the data structure memory used to store GTT storage info.
+ */
+static void
+gtt_free_statistics(gtt_relfilenode *rnode)
+{
+	int i;
+
+	Assert(rnode);
+
+	for (i = 0; i < rnode->natts; i++)
+	{
+		if (rnode->att_stat_tups[i])
+		{
+			heap_freetuple(rnode->att_stat_tups[i]);
+			rnode->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (rnode->attnum)
+		pfree(rnode->attnum);
+
+	if (rnode->att_stat_tups)
+		pfree(rnode->att_stat_tups);
+
+	pfree(rnode);
+
+	return;
+}
+
+/*
+ * Get the current relfilenode of this GTT.
+ */
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
+/*
+ * For cluster GTT.
+ * Exchange new and old relfilenode, leave footprints ensure rollback capability.
+ */
+void
+gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint)
+{
+	gtt_local_hash_entry	*entry1;
+	gtt_local_hash_entry	*entry2;
+	gtt_relfilenode			*gtt_rnode1 = NULL;
+	gtt_relfilenode			*gtt_rnode2 = NULL;
+	MemoryContext			oldcontext;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry1 = gtt_search_by_relid(rel1, false);
+	gtt_rnode1 = gtt_search_relfilenode(entry1, relfilenode1, false);
+
+	entry2 = gtt_search_by_relid(rel2, false);
+	gtt_rnode2 = gtt_search_relfilenode(entry2, relfilenode2, false);
+
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+	entry1->relfilenode_list = list_delete_ptr(entry1->relfilenode_list, gtt_rnode1);
+	entry2->relfilenode_list = lappend(entry2->relfilenode_list, gtt_rnode1);
+
+	entry2->relfilenode_list = list_delete_ptr(entry2->relfilenode_list, gtt_rnode2);
+	entry1->relfilenode_list = lappend(entry1->relfilenode_list, gtt_rnode2);
+	MemoryContextSwitchTo(oldcontext);
+
+	if (footprint)
+	{
+		entry1->oldrelid = rel2;
+		entry2->oldrelid = rel1;
+	}
+
+	return;
+}
+
+/*
+ * Get a relfilenode used by this GTT during the transaction life cycle.
+ */
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok)
+{
+	gtt_relfilenode		*rnode = NULL;
+	ListCell			*lc;
+
+	Assert(entry);
+
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == relfilenode)
+		{
+			rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (!missing_ok && rnode == NULL)
+		elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+	return rnode;
+}
+
+/*
+ * Get one GTT info from local hash.
+ */
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry == NULL && !missing_ok)
+		elog(ERROR, "relid %u not found in local hash", relid);
+
+	return entry;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 5f2541d..171c401 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f84616d..bb6a273 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -104,7 +105,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);
 
@@ -186,6 +187,17 @@ analyze_rel(Oid relid, RangeVar *relation,
 	}
 
 	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
+	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
 	if (RelationGetRelid(onerel) == StatisticRelationId)
@@ -587,14 +599,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]));
 		}
 
 		/*
@@ -1610,7 +1623,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;
@@ -1712,31 +1725,48 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/*
+		 * For global temporary table,
+		 * Update column statistic to localhash, not catalog.
+		 */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 096a06f..f75cb42 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/toasting.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
@@ -73,6 +74,12 @@ static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
 							bool verbose, bool *pSwapToastByContent,
 							TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
 static List *get_tables_to_cluster(MemoryContext cluster_context);
+static void gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables);
 
 
 /*---------------------------------------------------------------------------
@@ -391,6 +398,18 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
 	}
 
 	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap) &&
+		!gtt_storage_attached(RelationGetRelid(OldHeap)))
+	{
+		relation_close(OldHeap, AccessExclusiveLock);
+		pgstat_progress_end_command();
+		return;
+	}
+
+	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
 	 */
@@ -774,6 +793,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	BlockNumber num_pages;
 	int			elevel = verbose ? INFO : DEBUG2;
 	PGRUsage	ru0;
+	bool		is_gtt = false;
+	uint32		gtt_relfrozenxid = 0;
+	uint32		gtt_relminmxid = 0;
 
 	pg_rusage_init(&ru0);
 
@@ -787,6 +809,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	else
 		OldIndex = NULL;
 
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		is_gtt = true;
+
 	/*
 	 * Their tuple descriptors should be exactly alike, but here we only need
 	 * assume that they have the same number of columns.
@@ -854,20 +879,38 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 						  &OldestXmin, &FreezeXid, NULL, &MultiXactCutoff,
 						  NULL);
 
-	/*
-	 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
-	 * backwards, so take the max.
-	 */
-	if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
-		TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
-		FreezeXid = OldHeap->rd_rel->relfrozenxid;
+	if (is_gtt)
+	{
+		/* Gets transaction information for global temporary table from localhash. */
+		get_gtt_relstats(OIDOldHeap,
+					NULL, NULL, NULL,
+					&gtt_relfrozenxid, &gtt_relminmxid);
+
+		if (TransactionIdIsValid(gtt_relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, gtt_relfrozenxid))
+			FreezeXid = gtt_relfrozenxid;
+
+		if (MultiXactIdIsValid(gtt_relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, gtt_relminmxid))
+			MultiXactCutoff = gtt_relminmxid;
+	}
+	else
+	{
+		/*
+		 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
+		 * backwards, so take the max.
+		 */
+		if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
+			FreezeXid = OldHeap->rd_rel->relfrozenxid;
 
-	/*
-	 * MultiXactCutoff, similarly, shouldn't go backwards either.
-	 */
-	if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
-		MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
-		MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+		/*
+		 * MultiXactCutoff, similarly, shouldn't go backwards either.
+		 */
+		if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
+			MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+	}
 
 	/*
 	 * Decide whether to use an indexscan or seqscan-and-optional-sort to scan
@@ -935,6 +978,15 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	table_close(OldHeap, NoLock);
 	table_close(NewHeap, NoLock);
 
+	/* Update relstats of global temporary table to localhash. */
+	if (is_gtt)
+	{
+		up_gtt_relstats(RelationGetRelid(NewHeap), num_pages, num_tuples, 0,
+						InvalidTransactionId, InvalidMultiXactId);
+		CommandCounterIncrement();
+		return;
+	}
+
 	/* Update pg_class to reflect the correct values of pages and tuples. */
 	relRelation = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1371,10 +1423,22 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 	 * Swap the contents of the heap relations (including any toast tables).
 	 * Also set old heap's relfrozenxid to frozenXid.
 	 */
-	swap_relation_files(OIDOldHeap, OIDNewHeap,
+	if (newrelpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		Assert(!is_system_catalog);
+		/* For global temporary table modify data in localhash, not pg_class */
+		gtt_swap_relation_files(OIDOldHeap, OIDNewHeap,
+								(OIDOldHeap == RelationRelationId),
+								swap_toast_by_content, is_internal,
+								frozenXid, cutoffMulti, mapped_tables);
+	}
+	else
+	{
+		swap_relation_files(OIDOldHeap, OIDNewHeap,
 						(OIDOldHeap == RelationRelationId),
 						swap_toast_by_content, is_internal,
 						frozenXid, cutoffMulti, mapped_tables);
+	}
 
 	/*
 	 * If it's a system catalog, queue a sinval message to flush all catcaches
@@ -1582,3 +1646,146 @@ get_tables_to_cluster(MemoryContext cluster_context)
 
 	return rvs;
 }
+
+/*
+ * For global temporary table, storage information is stored in localhash,
+ * This function like swap_relation_files except that update storage information,
+ * in the localhash, not pg_class.
+ */
+static void
+gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables)
+{
+	Relation	relRelation;
+	Oid			relfilenode1,
+				relfilenode2;
+	Relation	rel1;
+	Relation	rel2;
+
+	relRelation = table_open(RelationRelationId, RowExclusiveLock);
+
+	rel1 = relation_open(r1, AccessExclusiveLock);
+	rel2 = relation_open(r2, AccessExclusiveLock);
+
+	relfilenode1 = gtt_fetch_current_relfilenode(r1);
+	relfilenode2 = gtt_fetch_current_relfilenode(r2);
+
+	Assert(OidIsValid(relfilenode1) && OidIsValid(relfilenode2));
+	gtt_switch_rel_relfilenode(r1, relfilenode1, r2, relfilenode2, true);
+
+	CacheInvalidateRelcache(rel1);
+	CacheInvalidateRelcache(rel2);
+
+	InvokeObjectPostAlterHookArg(RelationRelationId, r1, 0,
+								 InvalidOid, is_internal);
+	InvokeObjectPostAlterHookArg(RelationRelationId, r2, 0,
+								 InvalidOid, true);
+
+	if (rel1->rd_rel->reltoastrelid || rel2->rd_rel->reltoastrelid)
+	{
+		if (swap_toast_by_content)
+		{
+			if (rel1->rd_rel->reltoastrelid && rel2->rd_rel->reltoastrelid)
+			{
+				gtt_swap_relation_files(rel1->rd_rel->reltoastrelid,
+									rel2->rd_rel->reltoastrelid,
+									target_is_pg_class,
+									swap_toast_by_content,
+									is_internal,
+									frozenXid,
+									cutoffMulti,
+									mapped_tables);
+			}
+			else
+				elog(ERROR, "cannot swap toast files by content when there's only one");
+		}
+		else
+		{
+			ObjectAddress baseobject,
+						toastobject;
+			long		count;
+
+			if (IsSystemRelation(rel1))
+				elog(ERROR, "cannot swap toast files by links for system catalogs");
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel1->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel2->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+
+			/* Register new dependencies */
+			baseobject.classId = RelationRelationId;
+			baseobject.objectSubId = 0;
+			toastobject.classId = RelationRelationId;
+			toastobject.objectSubId = 0;
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r1;
+				toastobject.objectId = rel1->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r2;
+				toastobject.objectId = rel2->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+		}
+	}
+
+	if (swap_toast_by_content &&
+		rel1->rd_rel->relkind == RELKIND_TOASTVALUE &&
+		rel2->rd_rel->relkind == RELKIND_TOASTVALUE)
+	{
+		Oid			toastIndex1,
+					toastIndex2;
+
+		/* Get valid index for each relation */
+		toastIndex1 = toast_get_valid_index(r1,
+											AccessExclusiveLock);
+		toastIndex2 = toast_get_valid_index(r2,
+											AccessExclusiveLock);
+
+		gtt_swap_relation_files(toastIndex1,
+							toastIndex2,
+							target_is_pg_class,
+							swap_toast_by_content,
+							is_internal,
+							InvalidTransactionId,
+							InvalidMultiXactId,
+							mapped_tables);
+	}
+
+	relation_close(rel1, NoLock);
+	relation_close(rel2, NoLock);
+
+	table_close(relRelation, RowExclusiveLock);
+
+	RelationCloseSmgrByOid(r1);
+	RelationCloseSmgrByOid(r2);
+
+	CommandCounterIncrement();
+}
+
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 8c712c8..0a35f6f 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -290,7 +290,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP_ON_CURRENT_SESSION(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, whereClause,
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 2ed696d..7359500 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -23,6 +23,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "access/xlog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/copyfrom_internal.h"
 #include "commands/progress.h"
@@ -658,6 +659,9 @@ CopyFrom(CopyFromState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	/* Check and init global temporary table storage in current backend */
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	/*
 	 * Set up a ModifyTableState so we can let FDW(s) init themselves for
 	 * foreign-table result relation(s).
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 166374c..a40edd3 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -570,7 +570,7 @@ DefineIndex(Oid relationId,
 	 * is more efficient.  Do this before any use of the concurrent option is
 	 * done.
 	 */
-	if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+	if (stmt->concurrent && !RelpersistenceTsTemp(get_rel_persistence(relationId)))
 		concurrent = true;
 	else
 		concurrent = false;
@@ -2608,7 +2608,7 @@ ReindexIndex(RangeVar *indexRelation, ReindexParams *params, bool isTopLevel)
 	if (relkind == RELKIND_PARTITIONED_INDEX)
 		ReindexPartitions(indOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 persistence != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(persistence))
 		ReindexRelationConcurrently(indOid, params);
 	else
 	{
@@ -2717,7 +2717,7 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 	if (get_rel_relkind(heapOid) == RELKIND_PARTITIONED_TABLE)
 		ReindexPartitions(heapOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(get_rel_persistence(heapOid)))
 	{
 		result = ReindexRelationConcurrently(heapOid, params);
 
@@ -3132,7 +3132,7 @@ ReindexMultipleInternal(List *relids, ReindexParams *params)
 			   relkind != RELKIND_PARTITIONED_TABLE);
 
 		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			relpersistence != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(relpersistence))
 		{
 			ReindexParams newparams = *params;
 
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 34f2270..b8ea2ef 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -57,7 +57,10 @@ LockTableCommand(LockStmt *lockstmt)
 										  RangeVarCallbackForLockTable,
 										  (void *) &lockstmt->mode);
 
-		if (get_rel_relkind(reloid) == RELKIND_VIEW)
+		/* Lock table command ignores global temporary table. */
+		if (get_rel_persistence(reloid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+		else if (get_rel_relkind(reloid) == RELKIND_VIEW)
 			LockViewRecurse(reloid, lockstmt->mode, lockstmt->nowait, NIL);
 		else if (recurse)
 			LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait);
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 0415df9..39f82bc 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -275,8 +278,6 @@ ResetSequence(Oid seq_relid)
 	Buffer		buf;
 	HeapTupleData seqdatatuple;
 	HeapTuple	tuple;
-	HeapTuple	pgstuple;
-	Form_pg_sequence pgsform;
 	int64		startv;
 
 	/*
@@ -287,12 +288,7 @@ ResetSequence(Oid seq_relid)
 	init_sequence(seq_relid, &elm, &seq_rel);
 	(void) read_seq_tuple(seq_rel, &buf, &seqdatatuple);
 
-	pgstuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seq_relid));
-	if (!HeapTupleIsValid(pgstuple))
-		elog(ERROR, "cache lookup failed for sequence %u", seq_relid);
-	pgsform = (Form_pg_sequence) GETSTRUCT(pgstuple);
-	startv = pgsform->seqstart;
-	ReleaseSysCache(pgstuple);
+	startv = get_seqence_start_value(seq_relid);
 
 	/*
 	 * Copy the existing sequence tuple.
@@ -451,6 +447,15 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 
 	init_sequence(relid, &elm, &seqrel);
 
+	if (RELATION_IS_GLOBAL_TEMP(seqrel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(seqrel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary sequence %s when other backend attached it.",
+						RelationGetRelationName(seqrel))));
+	}
+
 	rel = table_open(SequenceRelationId, RowExclusiveLock);
 	seqtuple = SearchSysCacheCopy1(SEQRELID,
 								   ObjectIdGetDatum(relid));
@@ -611,7 +616,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +941,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1153,6 +1158,14 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	/* Initializes the storage for sequence which the global temporary table belongs. */
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1953,3 +1966,51 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+/*
+ * Get the startValue of the sequence from syscache.
+ */
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+/*
+ * Initialize sequence which global temporary table belongs.
+ */
+void
+gtt_init_seq(Relation rel)
+{
+	Datum		value[SEQ_COL_LASTCOL] = {0};
+	bool		null[SEQ_COL_LASTCOL] = {false};
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 88a68a4..e92d5dc 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -585,6 +586,7 @@ static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
 static void ATExecAlterCollationRefreshVersion(Relation rel, List *coll);
 static char GetAttributeCompression(Form_pg_attribute att, char *compression);
+static OnCommitAction gtt_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -630,6 +632,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -641,7 +644,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * Check consistency of arguments
 	 */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !RelpersistenceTsTemp(stmt->relation->relpersistence))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -671,7 +674,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	if (RelpersistenceTsTemp(stmt->relation->relpersistence)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -772,6 +775,56 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* For global temporary table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* Check parent table */
+		if (inheritOids)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			table_close(relation, NoLock);
+		}
+
+		/* Check oncommit clause and save to reloptions */
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temporary table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1406,7 +1459,7 @@ RemoveRelations(DropStmt *drop)
 		 * relation persistence cannot be known without its OID.
 		 */
 		if (drop->concurrent &&
-			get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(get_rel_persistence(relOid)))
 		{
 			Assert(list_length(drop->objects) == 1 &&
 				   drop->removeType == OBJECT_INDEX);
@@ -1612,7 +1665,16 @@ ExecuteTruncate(TruncateStmt *stmt)
 		Relation	rel;
 		bool		recurse = rv->inh;
 		Oid			myrelid;
-		LOCKMODE	lockmode = AccessExclusiveLock;
+		LOCKMODE	lockmode;
+
+		/*
+		 * Truncate global temp table only cleans up the data in current backend,
+		 * only low-level locks are required.
+		 */
+		if (rv->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+		else
+			lockmode = AccessExclusiveLock;
 
 		myrelid = RangeVarGetRelidExtended(rv, lockmode,
 										   0, RangeVarCallbackForTruncate,
@@ -1877,6 +1939,14 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
 			continue;
 
 		/*
+		 * Skip the global temporary table that is not initialized for storage
+		 * in current backend.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			continue;
+
+		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
 		 * a new relfilenode in the current (sub)transaction, then we can just
@@ -3862,6 +3932,16 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temporary table only current backend use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -5236,6 +5316,42 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot rewrite temporary tables of other sessions")));
 
+			if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+			{
+				if (tab->chgPersistence)
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot change global temporary table persistence setting")));
+
+				/*
+				 * The storage for the global temporary table needs to be initialized
+				 * before rewrite table.
+				 */
+				if(!gtt_storage_attached(tab->relid))
+				{
+					ResultRelInfo *resultRelInfo;
+					MemoryContext oldcontext;
+					MemoryContext ctx_alter_gtt;
+
+					ctx_alter_gtt = AllocSetContextCreate(CurrentMemoryContext,
+											"gtt alter table", ALLOCSET_DEFAULT_SIZES);
+					oldcontext = MemoryContextSwitchTo(ctx_alter_gtt);
+
+					resultRelInfo = makeNode(ResultRelInfo);
+					InitResultRelInfo(resultRelInfo, OldHeap,
+									1, NULL, 0);
+					if (resultRelInfo->ri_RelationDesc->rd_rel->relhasindex &&
+						resultRelInfo->ri_IndexRelationDescs == NULL)
+						ExecOpenIndices(resultRelInfo, false);
+
+					init_gtt_storage(CMD_UTILITY, resultRelInfo);
+					ExecCloseIndices(resultRelInfo);
+
+					MemoryContextSwitchTo(oldcontext);
+					MemoryContextDelete(ctx_alter_gtt);
+				}
+			}
+
 			/*
 			 * Select destination tablespace (same as original unless user
 			 * requested a change)
@@ -8763,6 +8879,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -13344,6 +13466,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -13542,6 +13667,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temporary table");
+
 	/* Check first if relation can be moved to new tablespace */
 	if (!CheckRelationTableSpaceMove(rel, newTableSpace))
 	{
@@ -13846,7 +13974,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,
@@ -15374,6 +15502,7 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
 	switch (rel->rd_rel->relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -18334,3 +18463,40 @@ GetAttributeCompression(Form_pg_attribute att, char *compression)
 
 	return cmethod;
 }
+
+/*
+ * Parse the on commit clause for the temporary table
+ */
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			bool	res = false;
+			char	*sval = defGetString(def);
+
+			/* It has to be a Boolean value */
+			if (!parse_bool(sval, &res))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
+
+			if (res)
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index c064352..02af171 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"
@@ -1243,6 +1244,22 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = RELATION_IS_GLOBAL_TEMP(relation);
+
+	 /* For global temporary table */
+	if (is_gtt)
+	{
+		/* Store relation statistics and transaction information to the localhash */
+		up_gtt_relstats(RelationGetRelid(relation),
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+
+		/* Update relation statistics to local relcache */
+		relation->rd_rel->relpages = (int32) num_pages;
+		relation->rd_rel->reltuples = (float4) num_tuples;
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1256,17 +1273,23 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (!is_gtt &&
+		pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (!is_gtt &&
+		pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (!is_gtt &&
+		pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1311,7 +1334,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNextTransactionId(),
@@ -1322,7 +1346,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1431,6 +1456,13 @@ vac_update_datfrozenxid(void)
 		}
 
 		/*
+		 * The relfrozenxid for a global temporary talble is stored in localhash,
+		 * not pg_class, See list_all_session_gtt_frozenxids()
+		 */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
+		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
 		 * to not be set (i.e. set to their respective Invalid*Id)
@@ -1487,6 +1519,43 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/* If enable global temporary table */
+	if (max_active_gtt > 0)
+	{
+		TransactionId	safe_age;
+		/*  */
+		TransactionId	oldest_gtt_frozenxid =
+			list_all_backend_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid))
+		{
+			safe_age = oldest_gtt_frozenxid + vacuum_gtt_defer_check_age;
+			if (safe_age < FirstNormalTransactionId)
+				safe_age += FirstNormalTransactionId;
+
+			/*
+			 * We tolerate that the minimum age of gtt is less than
+			 * the minimum age of conventional tables, otherwise it will
+			 * throw warning message.
+			 */
+			if (TransactionIdIsNormal(safe_age) &&
+				TransactionIdPrecedes(safe_age, newFrozenXid))
+			{
+				ereport(WARNING,
+					(errmsg("global temp table oldest relfrozenxid %u is the oldest in the entire db",
+							oldest_gtt_frozenxid),
+					 errdetail("The oldest relfrozenxid in pg_class is %u", newFrozenXid),
+					 errhint("If they differ greatly, please consider cleaning up the data in global temp table.")));
+			}
+
+			/*
+			 * We need to ensure that the clog required by gtt is not cleand.
+			 */
+			if (TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+				newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1839,6 +1908,19 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 	}
 
 	/*
+	 * Skip those global temporary table that are not initialized in
+	 * current backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
+	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
 	 * contents are probably not up-to-date on disk.  (We don't throw a
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index f2642db..e088101 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -530,6 +530,12 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	/* Global temporary table are not sensible. */
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 8de78ad..3d106cb 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -773,6 +773,10 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		/* This is one kind of temp table */
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 619aaff..67f6079 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
@@ -608,6 +609,9 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	/* Init storage for partitioned global temporary table in current backend */
+	init_gtt_storage(mtstate->operation, leaf_part_rri);
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 2993ba4..78ebadd 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,6 +42,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -2429,6 +2430,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			ExecOpenIndices(resultRelInfo,
 							node->onConflictAction != ONCONFLICT_NONE);
 
+		/* Init storage for global temporary table in current backend */
+		init_gtt_storage(operation, resultRelInfo);
+
 		/*
 		 * If this is an UPDATE and a BEFORE UPDATE trigger is present, the
 		 * trigger itself might modify the partition-key values. So arrange
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 59f495d..16596f0 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -48,7 +48,7 @@
 #include "partitioning/partprune.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/lsyscache.h"
-
+#include "utils/rel.h"
 
 /* results of subquery_is_pushdown_safe */
 typedef struct pushdown_safety_info
@@ -619,7 +619,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			if (RelpersistenceTsTemp(get_rel_persistence(rte->relid)))
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index f529d10..2dba230 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6429,7 +6429,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
-	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+	if (RELATION_IS_TEMP(heap) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 0fa8875..82873c8 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -28,6 +28,7 @@
 #include "catalog/catalog.h"
 #include "catalog/heap.h"
 #include "catalog/index.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
@@ -230,6 +231,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temporary table in current backend */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 7149724..e5d767a 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2817,6 +2817,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 7ff36bc..55199ab 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3400,17 +3400,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; }
@@ -11587,19 +11581,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 ca02982..4fc46eb 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool is_query_using_gtt_walker(Node *node, void *context);
 
 
 /*
@@ -3652,3 +3653,53 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * Like function isQueryUsingTempRelation_walker
+ * return true if any relation underlying
+ * the query is a global temporary table.
+ */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* Check if the query uses global temporary table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index b968c25..ae0ea7a 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -458,6 +458,13 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->options = seqoptions;
 
 	/*
+	 * If a sequence is bound to a global temporary table, then the sequence
+	 * must been "global temporary"
+	 */
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
+	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
 	 * clause, the "redundant options" error will point to their occurrence,
@@ -3356,6 +3363,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		cxt.isforeign = false;
 	}
 	cxt.relation = stmt->relation;
+	/* Sets the table persistence to the context */
+	cxt.relation->relpersistence = RelationGetRelPersistence(rel);
 	cxt.rel = rel;
 	cxt.inhRelations = NIL;
 	cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 23ef23c..e603869 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2111,6 +2111,14 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/*
+			 * Aotuvacuum cannot vacuum the private data stored in each backend
+			 * that belongs to global temporary table, so skip them.
+			 */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2177,7 +2185,7 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		if (RelpersistenceTsTemp(classForm->relpersistence))
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 852138f..68eb5f5 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -2866,6 +2867,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * Returns 0 if this global temporary table is not initialized in current
+	 * backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 3e4ec53..e9092cd 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -22,6 +22,7 @@
 #include "access/subtrans.h"
 #include "access/syncscan.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -150,6 +151,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
@@ -222,6 +224,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* For global temporary table shared hashtable */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 4fc6ffb..6ea2e1f 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -65,6 +65,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -5070,3 +5071,78 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temporary table.
+ */
+int
+list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct *arrayP = procArray;
+	TransactionId result = InvalidTransactionId;
+	int			index;
+	uint8			flags = 0;
+	int			i = 0;
+
+	/* return 0 if feature is disabled */
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+		*n = 0;
+	}
+
+	/* Disable in standby node */
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		uint8           statusFlags = ProcGlobal->statusFlags[index];
+
+		if (statusFlags & flags)
+			continue;
+
+		/* Fetch all backend that is belonging to MyDatabaseId */
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->backend_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->backend_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->backend_gtt_frozenxid, result))
+				result = proc->backend_gtt_frozenxid;
+
+			/* save backend pid and backend level oldest relfrozenxid */
+			if (max_size > 0)
+			{
+				pids[i] = proc->pid;
+				xids[i] = proc->backend_gtt_frozenxid;
+				i++;
+			}
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (max_size > 0)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 975d547..09b3fa6 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -175,7 +175,9 @@ static const char *const BuiltinTrancheNames[] = {
 	/* LWTRANCHE_PARALLEL_APPEND: */
 	"ParallelAppend",
 	/* LWTRANCHE_PER_XACT_PREDICATE_LIST: */
-	"PerXactPredicateList"
+	"PerXactPredicateList",
+	/* LWTRANCHE_GTT_CTL */
+	"GlobalTempTableControl"
 };
 
 StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 897045e..79da250 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -393,6 +393,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;	/* init backend level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
@@ -575,6 +576,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;	/* init backend level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 64cdaa4..ef980e5 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -982,6 +982,13 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/*
+			 * For global temporary table ,each backend has its own storage,
+			 * also only sees its own storage. Use Backendid to identify them.
+			 */
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 7e41bc5..4e0aa9e 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -108,6 +108,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -5095,12 +5096,26 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								/* For global temporary table, get statistic data from localhash */
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -5348,15 +5363,28 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6779,6 +6807,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;
@@ -6796,6 +6825,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6807,6 +6844,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;
@@ -6822,6 +6861,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7740,6 +7787,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))
@@ -7752,6 +7801,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7764,6 +7822,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.
@@ -7783,6 +7843,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 6bba5f8..fa81808 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -3113,6 +3114,19 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		/* For global temporary table, get statistic data from localhash */
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index ff7395c..847a3a1 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -66,6 +66,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1144,6 +1145,28 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+
+				/* For global temporary table, get relstat data from localhash */
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+
+				/* And put them to local relcache */
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1198,6 +1221,8 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			/* The state of the global temporary table's index may need to be set */
+			gtt_fix_index_backend_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -1325,7 +1350,22 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+			/*
+			 * For global temporary table, get the latest relfilenode
+			 * from localhash and put it in relcache.
+			 */
+			if (OidIsValid(newrelnode) &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2267,6 +2307,9 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		/* The state of the global temporary table's index may need to be set */
+		gtt_fix_index_backend_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3494,6 +3537,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3603,28 +3650,38 @@ void
 RelationSetNewRelfilenode(Relation relation, char persistence)
 {
 	Oid			newrelfilenode;
-	Relation	pg_class;
-	HeapTuple	tuple;
+	Relation	pg_class = NULL;
+	HeapTuple	tuple = NULL;
 	Form_pg_class classform;
 	MultiXactId minmulti = InvalidMultiXactId;
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
+	/*
+	 * For global temporary table, storage information for the table is
+	 * maintained locally, not in catalog.
+	 */
+	bool		update_catalog = !RELATION_IS_GLOBAL_TEMP(relation);
 
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
 
-	/*
-	 * Get a writable copy of the pg_class tuple for the given relation.
-	 */
-	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+	memset(&classform, 0, sizeof(classform));
 
-	tuple = SearchSysCacheCopy1(RELOID,
-								ObjectIdGetDatum(RelationGetRelid(relation)));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "could not find tuple for relation %u",
-			 RelationGetRelid(relation));
-	classform = (Form_pg_class) GETSTRUCT(tuple);
+	if (update_catalog)
+	{
+		/*
+		 * Get a writable copy of the pg_class tuple for the given relation.
+		 */
+		pg_class = table_open(RelationRelationId, RowExclusiveLock);
+
+		tuple = SearchSysCacheCopy1(RELOID,
+									ObjectIdGetDatum(RelationGetRelid(relation)));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u",
+				 RelationGetRelid(relation));
+		classform = (Form_pg_class) GETSTRUCT(tuple);
+	}
 
 	/*
 	 * Schedule unlinking of the old storage at transaction commit.
@@ -3650,7 +3707,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;
@@ -3670,6 +3727,18 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 			break;
 	}
 
+	/* For global temporary table */
+	if (!update_catalog)
+	{
+		Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+		Assert(RELATION_IS_GLOBAL_TEMP(relation));
+		Assert(!RelationIsMapped(relation));
+
+		/* Make cache invalid and set new relnode to local cache. */
+		CacheInvalidateRelcache(relation);
+		relation->rd_node.relNode = relnode;
+	}
 	/*
 	 * If we're dealing with a mapped index, pg_class.relfilenode doesn't
 	 * change; instead we have to send the update to the relation mapper.
@@ -3679,7 +3748,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	 * possibly-inaccurate values of relpages etc, but those will be fixed up
 	 * later.
 	 */
-	if (RelationIsMapped(relation))
+	else if (RelationIsMapped(relation))
 	{
 		/* This case is only supported for indexes */
 		Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3725,9 +3794,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 		CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 	}
 
-	heap_freetuple(tuple);
+	if (update_catalog)
+	{
+		heap_freetuple(tuple);
 
-	table_close(pg_class, RowExclusiveLock);
+		table_close(pg_class, RowExclusiveLock);
+	}
 
 	/*
 	 * Make the pg_class row change or relation map change visible.  This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 0c5dc4d..346f06d 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -41,6 +41,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "commands/prepare.h"
 #include "commands/trigger.h"
@@ -147,6 +148,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temporary table feature.
+ * table schema are still saved in catalog.
+ *
+ * num > 0 means allows the database to manage multiple active tables at the same time.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2094,6 +2107,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."),
@@ -2623,6 +2645,16 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"vacuum_gtt_defer_check_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("The defer check age of GTT, used to check expired data after vacuum."),
+			NULL
+		},
+		&vacuum_gtt_defer_check_age,
+		10000, 0, 1000000,
+		NULL, NULL, NULL
+	},
+
 	/*
 	 * See also CheckRequiredParameterValues() if this parameter changes
 	 */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index da6cc05..7241f74 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2441,6 +2441,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
 		dopt->no_unlogged_table_data)
 		return;
 
+	/* Don't dump data in global temporary table/sequence */
+	if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		return;
+
 	/* Check that the data is not explicitly excluded */
 	if (simple_oid_list_member(&tabledata_exclude_oids,
 							   tbinfo->dobj.catId.oid))
@@ -15809,6 +15813,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
 		char	   *foreign = "";
+		char	   *table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15862,9 +15867,15 @@ dumpTableSchema(Archive *fout, const 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);
 
@@ -16229,13 +16240,22 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		}
 
 		/*
+		 * Transaction information for the global temporary table is not stored
+		 * in the pg_class.
+		 */
+		if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			Assert(tbinfo->frozenxid == 0);
+			Assert(tbinfo->minmxid == 0);
+		}
+		/*
 		 * In binary_upgrade mode, arrange to restore the old relfrozenxid and
 		 * relminmxid of all vacuumable relations.  (While vacuum.c processes
 		 * TOAST tables semi-independently, here we see them only as children
 		 * of other relations; so this "if" lacks RELKIND_TOASTVALUE, and the
 		 * child toast table is handled below.)
 		 */
-		if (dopt->binary_upgrade &&
+		else if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
 			 tbinfo->relkind == RELKIND_MATVIEW))
 		{
@@ -17260,6 +17280,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	PQExpBuffer query = createPQExpBuffer();
 	PQExpBuffer delqry = createPQExpBuffer();
 	char	   *qseqname;
+	bool	   global_temp_seq = false;
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
@@ -17269,9 +17290,12 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
-						  "FROM pg_catalog.pg_sequence "
-						  "WHERE seqrelid = '%u'::oid",
+						  "seqcache, seqcycle, "
+						  "c.relpersistence "
+						  "FROM pg_catalog.pg_sequence s, "
+						  "pg_catalog.pg_class c "
+						  "WHERE seqrelid = '%u'::oid "
+						  "and s.seqrelid = c.oid",
 						  tbinfo->dobj.catId.oid);
 	}
 	else if (fout->remoteVersion >= 80400)
@@ -17316,6 +17340,9 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
 
+	if (fout->remoteVersion >= 140000)
+		global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
 	if (strcmp(seqtype, "smallint") == 0)
@@ -17393,9 +17420,13 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	}
 	else
 	{
-		appendPQExpBuffer(query,
-						  "CREATE SEQUENCE %s\n",
-						  fmtQualifiedDumpable(tbinfo));
+		appendPQExpBuffer(query, "CREATE ");
+
+		if (global_temp_seq)
+			appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+		appendPQExpBuffer(query, "SEQUENCE %s\n",
+								  fmtQualifiedDumpable(tbinfo));
 
 		if (strcmp(seqtype, "bigint") != 0)
 			appendPQExpBuffer(query, "    AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 43fc297..ff33906 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -86,7 +86,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -166,7 +166,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 5d9a26c..2de11d5 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -447,8 +449,17 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
 			 "         ON c.relnamespace = n.oid "
 			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
+			 CppAsString2(RELKIND_MATVIEW) ") AND ");
+
+	if (skip_gtt)
+	{
+		/* exclude global temp tables */
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+			"    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND ");
+	}
+
 	/* exclude possible orphaned temp tables */
+	snprintf(query + strlen(query), sizeof(query) - strlen(query),
 			 "    ((n.nspname !~ '^pg_temp_' AND "
 			 "      n.nspname !~ '^pg_toast_temp_' AND "
 			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index e23b8ca..729a9c6 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -407,7 +407,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -638,7 +638,10 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+			/* exclude global temp tables */
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -649,7 +652,10 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+		/* exclude global temp tables */
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 919a784..486d01f 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -388,7 +388,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 440249f..f5c81f3 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3905,7 +3905,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index a053bc1..b7612b2 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1057,6 +1057,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2488,6 +2490,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2696,6 +2701,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 6ce480b..a0ccfb3 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -59,7 +59,8 @@ extern Relation heap_create(const char *relname,
 							bool mapped_relation,
 							bool allow_system_table_mods,
 							TransactionId *relfrozenxid,
-							MultiXactId *relminmxid);
+							MultiXactId *relminmxid,
+							bool skip_create_storage);
 
 extern Oid	heap_create_with_catalog(const char *relname,
 									 Oid relnamespace,
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 3e37729..67652c2 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -175,6 +175,7 @@ DECLARE_INDEX(pg_class_tblspc_relfilenode_index, 3455, on pg_class using btree(r
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index cc7d90d..a5aa72c 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5650,6 +5650,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '9874',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '9875',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '9876',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '9877',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 0ab32b4..92e9f8b 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,7 +22,7 @@
 /* GUC variables */
 extern int	wal_skip_threshold;
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationPreTruncate(Relation rel);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..d48162c
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,46 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern int		vacuum_gtt_defer_check_age;
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Oid relid,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void force_enable_gtt_index(Relation index);
+extern void gtt_fix_index_backend_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+extern void gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index 40544dd..7b66d80 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 5dbe5ba..b215561 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -119,4 +119,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 359b749..e724f7e 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -399,6 +399,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index a8f052e..4b4ed1a 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -189,6 +189,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_SHARED_TIDBITMAP,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_PER_XACT_PREDICATE_LIST,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 2fd1ff0..7ccb4dd 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -157,6 +157,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId backend_gtt_frozenxid;	/* backend level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index b01fa52..8efffa5 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -94,4 +94,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 5004ee4..e03f02d 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -283,6 +283,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 9a3a03e..22637fc 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	struct SMgrRelationData *rd_smgr;	/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -306,6 +306,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -577,11 +578,13 @@ typedef struct ViewOptions
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -589,6 +592,7 @@ typedef struct ViewOptions
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -601,6 +605,30 @@ typedef struct ViewOptions
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP_ON_CURRENT_SESSION
+ *		Test a rel is either local temp relation of this session
+ *		or global temp relation.
+ */
+#define RELATION_IS_TEMP_ON_CURRENT_SESSION(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is local temp relation or global temporary relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RelpersistenceTsTemp
+ *		Test a relpersistence is local temp relation or global temporary relation.
+ */
+#define RelpersistenceTsTemp(relpersistence) \
+	(relpersistence == RELPERSISTENCE_TEMP || \
+	 relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -644,6 +672,19 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/* For global temporary table */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/* Get on commit clause value only for global temporary table */
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+/* Get relpersistence for relation */
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..ca2d135
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,14 @@
+reset search_path;
+select pg_sleep(5);
+ pg_sleep 
+----------
+ 
+(1 row)
+
+drop schema gtt cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
+drop cascades to table gtt.gtt_with_seq
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..0ac9e22
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,381 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+ n 
+---
+ 9
+(1 row)
+
+commit;
+-- 0 row
+select * from gtt6;
+ n 
+---
+(0 rows)
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temporary table
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temporary table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  could not create global temporary table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR:  insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count 
+-------
+     1
+(1 row)
+
+select count(*) from orders;
+ count 
+-------
+     0
+(1 row)
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  2 |  3
+(2 rows)
+
+commit;
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+select * from gtt_s_2 order by c1;
+ c1 | c2 
+----+----
+  1 |  2
+  2 |  4
+(2 rows)
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 1)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 300)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 33 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test_new
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.gtt6
+drop cascades to table gtt_function.foo
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to sequence gtt_function.seq_1
+drop cascades to table gtt_function.gtt_s_1
+drop cascades to table gtt_function.gtt_s_2
+drop cascades to table gtt_function.gt1
+drop cascades to table gtt_function.gtt_test1
+drop cascades to table gtt_function.gtt_test2
+drop cascades to table gtt_function.gtt_test3
+drop cascades to table gtt_function.gtt_test4
+drop cascades to table gtt_function.gtt_test5
+drop cascades to table gtt_function.gtt_test6
+drop cascades to table gtt_function.gtt_test7
+drop cascades to table gtt_function.gtt_test8
+drop cascades to table gtt_function.gtt_test9
+drop cascades to table gtt_function.gtt_test10
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..0646aae
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,90 @@
+set search_path=gtt,sys;
+select nextval('gtt_with_seq_c2_seq');
+ nextval 
+---------
+       1
+(1 row)
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..0fccf6b
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,343 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+rollback;
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+commit;
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          114688 |                 614400
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |            49152 |         49152 |                  49152
+(2 rows)
+
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2000
+(1 row)
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+ c1 | c2 
+----+----
+  1 |  1
+(1 row)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..8c0c376
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,10 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..4420fdb
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,80 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+ gtt_stats  | gtt_pkey  |        1 |         0 |             0
+(2 rows)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+reindex table gtt;
+reindex index gtt_pkey;
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+ gtt_stats  | gtt_pkey  |       30 |     10000 |             0
+(2 rows)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 9b59a7b..e3474fe 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1359,6 +1359,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 312c11a..d44fa62 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -129,3 +129,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..2c8e586
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,8 @@
+
+
+reset search_path;
+
+select pg_sleep(5);
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..fd8b4d3
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,253 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+commit;
+-- 0 row
+select * from gtt6;
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+commit;
+select * from gtt_s_1 order by c1;
+select * from gtt_s_2 order by c1;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d05745e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,44 @@
+
+
+set search_path=gtt,sys;
+
+select nextval('gtt_with_seq_c2_seq');
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..81a6039
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,154 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+rollback;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+commit;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+select count(*) from gtt_t_kenyon;
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..dbe84d1
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,19 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..d61b0ff
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,46 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+reindex table gtt;
+
+reindex index gtt_pkey;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
#308Pavel Stehule
pavel.stehule@gmail.com
In reply to: wenjing (#307)
Re: [Proposal] Global temporary tables

po 29. 3. 2021 v 13:45 odesílatel wenjing <wjzeng2012@gmail.com> napsal:

HI all

I fixed the document description error and the regression test bug
mentioned by Pavel.
This patch(V45) is base on 30aaab26e52144097a1a5bbb0bb66ea1ebc0cb81
Please give me feedback.

Yes, it is working.

So please, can you write some user documentation?

Show quoted text

Wenjing

#309wenjing
wjzeng2012@gmail.com
In reply to: Pavel Stehule (#308)
2 attachment(s)
Re: [Proposal] Global temporary tables

HI Pavel

I added user documentation.
Please give me feedback.

Wenjing

Attachments:

0002-gtt-v46-doc.patchapplication/octet-stream; name=0002-gtt-v46-doc.patchDownload
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index b6cf9adcb2..c620d1a044 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -169,32 +169,61 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     <listitem>
      <para>
       If specified, the table is created as a temporary table.
-      Temporary tables are automatically dropped at the end of a
-      session, or optionally at the end of the current transaction
-      (see <literal>ON COMMIT</literal> below).  The default
+      Optionally, <literal>GLOBAL</literal> or <literal>LOCAL</literal>
+      can be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
+      This means that <productname>PostgreSQL</productname> supports two types of temporary tables.
+      One is the Global Temporary table, and the other is the Local Temporary table.
+      The Local Temporary table is created by default.
+     </para>
+
+    <variablelist>
+     <varlistentry>
+      <term><literal>Global Temporary Table</literal></term>
+      <listitem>
+       <para>
+        The Global Temporary Table are defined just once and automatically exist
+        (starting with empty contents) in every session that needs them.
+	This means that the definition of temporary table is persistent
+        and shared between sessions. When a temporary table is created
+        and write some data, another newly created session also sees this table,
+        and found this table is empty.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>Local Temporary Table</literal></term>
+     <listitem>
+     <para>
+      The Local Temporary Table are automatically dropped at the end of a
+      session, The default
       search_path includes the temporary schema first and so identically
       named existing permanent tables are not chosen for new plans
       while the temporary table exists, unless they are referenced
       with schema-qualified names. Any indexes created on a temporary
       table are automatically temporary as well.
      </para>
+     </listitem>
+     </varlistentry>
+    </variablelist>
 
-     <para>
-      The <link linkend="autovacuum">autovacuum daemon</link> cannot
-      access and therefore cannot vacuum or analyze temporary tables.
-      For this reason, appropriate vacuum and analyze operations should be
-      performed via session SQL commands.  For example, if a temporary
-      table is going to be used in complex queries, it is wise to run
-      <command>ANALYZE</command> on the temporary table after it is populated.
-     </para>
+    <para>
+     The <link linkend="autovacuum">autovacuum daemon</link> cannot
+     access and therefore cannot vacuum or analyze temporary tables.
+     For this reason, appropriate vacuum and analyze operations should be
+     performed via session SQL commands.  For example, if a temporary
+     table is going to be used in complex queries, it is wise to run
+     <command>ANALYZE</command> on the temporary table after it is populated.
+    </para>
+    <para>
+     The data stored in a temporary table can be cleaned up at the
+     end of the current transaction. (see <literal>ON COMMIT</literal> below).
+    </para>
+    <para>
+     The Temporary Table resembles the SQL standard, but has some differences.
+     see <xref linkend="sql-createtable-compatibility"/> below.
+    </para>
 
-     <para>
-      Optionally, <literal>GLOBAL</literal> or <literal>LOCAL</literal>
-      can be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
-      This presently makes no difference in <productname>PostgreSQL</productname>
-      and is deprecated; see
-      <xref linkend="sql-createtable-compatibility"/> below.
-     </para>
     </listitem>
    </varlistentry>
 
@@ -2125,13 +2154,18 @@ CREATE TABLE cities_partdef
    <title>Temporary Tables</title>
 
    <para>
-    Although the syntax of <literal>CREATE TEMPORARY TABLE</literal>
-    resembles that of the SQL standard, the effect is not the same.  In the
-    standard,
-    temporary tables are defined just once and automatically exist (starting
-    with empty contents) in every session that needs them.
-    <productname>PostgreSQL</productname> instead
-    requires each session to issue its own <literal>CREATE TEMPORARY
+    Although the syntax of <literal>CREATE GLOBAL/LOCAL TEMPORARY TABLE</literal>
+    resembles that of the SQL standard, the effect is not the same. The
+    SQL standard distinguishes between global and local temporary
+    tables. The Global Temporary Talbe follows SQL standards very well,
+    but Local Temporary Talbe does not.
+   </para>
+
+   <para>
+    First, in the standard, temporary tables are defined just once and
+    automatically exist (starting with empty contents) in every session
+    that needs them. The Local Temporary Table instead
+    requires each session to issue its own <literal>CREATE LOCAL TEMPORARY
     TABLE</literal> command for each temporary table to be used.  This allows
     different sessions to use the same temporary table name for different
     purposes, whereas the standard's approach constrains all instances of a
@@ -2139,29 +2173,13 @@ CREATE TABLE cities_partdef
    </para>
 
    <para>
-    The standard's definition of the behavior of temporary tables is
-    widely ignored.  <productname>PostgreSQL</productname>'s behavior
-    on this point is similar to that of several other SQL databases.
-   </para>
-
-   <para>
-    The SQL standard also distinguishes between global and local temporary
-    tables, where a local temporary table has a separate set of contents for
+    Second, a local temporary table has a separate set of contents for
     each SQL module within each session, though its definition is still shared
     across sessions.  Since <productname>PostgreSQL</productname> does not
     support SQL modules, this distinction is not relevant in
     <productname>PostgreSQL</productname>.
    </para>
 
-   <para>
-    For compatibility's sake, <productname>PostgreSQL</productname> will
-    accept the <literal>GLOBAL</literal> and <literal>LOCAL</literal> keywords
-    in a temporary table declaration, but they currently have no effect.
-    Use of these keywords is discouraged, since future versions of
-    <productname>PostgreSQL</productname> might adopt a more
-    standard-compliant interpretation of their meaning.
-   </para>
-
    <para>
     The <literal>ON COMMIT</literal> clause for temporary tables
     also resembles the SQL standard, but has some differences.
@@ -2169,7 +2187,8 @@ CREATE TABLE cities_partdef
     default behavior is <literal>ON COMMIT DELETE ROWS</literal>.  However, the
     default behavior in <productname>PostgreSQL</productname> is
     <literal>ON COMMIT PRESERVE ROWS</literal>.  The <literal>ON COMMIT
-    DROP</literal> option does not exist in SQL.
+    DROP</literal> option does not exist in SQL and is not supported by
+    Global Temporary Table.
    </para>
   </refsect2>
 
-- 
2.27.0

0001-gtt-v46.patchapplication/octet-stream; name=0001-gtt-v46.patchDownload
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 5554275e64..1d3e992cb8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -168,6 +168,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * In order to avoid consistency problems, the global temporary table
+	 * uses ShareUpdateExclusiveLock.
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temporary table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},
 	/* list terminator */
 	{{NULL}}
 };
@@ -1817,6 +1830,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1961,11 +1976,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
-	/*
-	 * autovacuum_enabled, autovacuum_analyze_threshold and
-	 * autovacuum_analyze_scale_factor are supported for partitioned tables.
-	 */
-
 	return default_reloptions(reloptions, validate, RELOPT_KIND_PARTITIONED);
 }
 
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index 8dcd53c457..3bef4a163c 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1024,7 +1024,7 @@ gistproperty(Oid index_oid, int attno,
 XLogRecPtr
 gistGetFakeLSN(Relation rel)
 {
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	if (RELATION_IS_TEMP(rel))
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 0752fb38a9..5c85d777f4 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -151,7 +151,7 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	if (!RELATION_IS_TEMP(index))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 7a9a640989..67fcc75438 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -591,7 +591,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
@@ -644,7 +644,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 9f9ba5d308..b4ccc86757 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -63,6 +63,7 @@
 #include "access/xlog.h"
 #include "catalog/index.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -511,6 +512,14 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
 	Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
+	/*
+	 * not every AM requires these to be valid, but regular heap does.
+	 * Transaction information for the global temp table will be stored
+	 * in the local hash table, not the catalog.
+	 */
+	Assert(RELATION_IS_GLOBAL_TEMP(rel) ^ TransactionIdIsNormal(rel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(rel) ^ MultiXactIdIsValid(rel->rd_rel->relminmxid));
+
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
 	{
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index ef48679cc2..df0e843e87 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -677,6 +678,14 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		/*
+		 * If a global temporary table storage file is not initialized in the
+		 * current backend, its index does not have a root page, just returns 0.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 5fcd004e1b..58b994cef5 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -212,7 +212,8 @@ Boot_CreateStmt:
 												   mapped_relation,
 												   true,
 												   &relfrozenxid,
-												   &relminmxid);
+												   &relminmxid,
+												   false);
 						elog(DEBUG4, "bootstrap relation created");
 					}
 					else
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index e36a9602c1..be0b600ca5 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -43,6 +43,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/README.gtt b/src/backend/catalog/README.gtt
new file mode 100644
index 0000000000..721f25a86d
--- /dev/null
+++ b/src/backend/catalog/README.gtt
@@ -0,0 +1,166 @@
+Global Temporary Table(GTT)
+==============
+
+Feature description
+--------------------------------
+
+Previously, temporary tables are defined once and automatically
+created (starting with empty contents) in every session before using them.
+
+The temporary table implementation in PostgreSQL, known as Local temp tables(LTT),
+did not fully comply with the SQL standard. This version added the support of
+Global Temporary Table .
+
+The metadata of Global Temporary Table is persistent and shared among sessions.
+The data stored in the Global temporary table is independent of sessions. This
+means, when a session creates a Global Temporary Table and writes some data.
+Other sessions cannot see those data, but they have an empty Global Temporary
+Table with same schema.
+
+Like local temporary table, Global Temporary Table supports ON COMMIT PRESERVE ROWS
+or ON COMMIT DELETE ROWS clause, so that data in the temporary table can be
+cleaned up or reserved automatically when a session exits or a transaction COMMITs.
+
+Unlike Local Temporary Table, Global Temporary Table does not support
+ON COMMIT DROP clauses.
+
+In following paragraphs, we use GTT for Global Temporary Table and LTT for
+local temporary table.
+
+Main design ideas
+-----------------------------------------
+
+STORAGE & BUFFER
+
+In general, GTT and LTT use the same storage and buffer design and
+implementation. The storage files for both types of temporary tables are named
+as t_backendid_relfilenode, and the local buffer is used to cache the data.
+
+The schema of GTTs is shared among sessions while their data are not. We build
+a new mechanisms to manage those non-shared data and their statistics.
+Here is the summary of changes:
+
+1) CATALOG
+GTTs store session-specific data. The storage information of GTTs'data, their
+transaction information, and their statistics are not stored in the catalog.
+
+2) STORAGE INFO & STATISTICS & TRANSACTION
+In order to maintain durability and availability of GTTs'session-specific data,
+their storage information, statistics, and transaction information is managed
+in a local hash table tt_storage_local_hash.
+
+3) DDL
+A shared hash table active_gtt_shared_hash is added to track the state of the
+GTT in a different session. This information is recorded in the hash table
+during the DDL execution of the GTT.
+
+4) LOCK
+The data stored in a GTT can only be modified or accessed by owning session.
+The statements that only modify data in a GTT do not need a high level of table
+locking.
+Changes to the GTT's metadata affect all sessions.
+The operations making those changes include truncate GTT, Vacuum/Cluster GTT,
+and Lock GTT.
+
+Detailed design
+-----------------------------------------
+
+1. CATALOG
+1.1 relpersistence
+define RELPERSISTENCEGLOBALTEMP 'g'
+Mark Global Temporary Table in pg_class relpersistence to 'g'. The relpersistence
+of indexes created on the GTT, sequences on GTT and toast tables on GTT are
+also set to 'g'
+
+1.2 on commit clause
+LTT's status associated with on commit DELETE ROWS and on commit PRESERVE ROWS
+is not stored in catalog. Instead, GTTs need a bool value on_commit_delete_rows
+in reloptions which is shared among sessions.
+
+1.3 gram.y
+GTT is already supported in syntax tree. We remove the warning message
+"GLOBAL is deprecated in temporary table creation" and mark
+relpersistence = RELPERSISTENCEGLOBALTEMP.
+
+2. STORAGE INFO & STATISTICS DATA & TRANSACTION INFO
+2.1. gtt_storage_local_hash
+Each backend creates a local hashtable gtt_storage_local_hash to track a GTT's
+storage file information, statistics, and transaction information.
+
+2.2 GTT storage file info track
+1) When one session inserts data into a GTT for the first time, record the
+storage info to gtt_storage_local_hash.
+2) Use beforeshmemexit to ensure that all files of session GTT are deleted when
+the session exits.
+
+2.3 statistics info
+1) relpages reltuples relallvisible
+2) The statistics of each column from pg_statistic
+All the above information is stored in gtt_storage_local_hash.
+When doing vacuum or analyze, GTT's statistic is updated, which is used by
+the SQL planner.
+The statistics summarizes only data in the current session.
+
+2.3 transaction info track
+frozenxid minmulti from pg_class is stored to gtt_storage_local_hash.
+
+4 DDL
+4.1. active_gtt_shared_hash
+This is the hash table created in shared memory to trace the GTT files initialized
+in each session. Each hash entry contains a bitmap that records the backendid of
+the initialized GTT file. With this hash table, we know which backend/session
+is using this GTT. Such information is used during GTT's DDL operations.
+
+4.1 DROP GTT
+One GTT is allowed to be deleted when there is only one session using the table
+and the session is the current session.
+After holding the AccessExclusiveLock lock on GTT, active_gtt_shared_hash
+is checked to ensure that.
+
+4.2 ALTER GTT/DROP INDEX ON GTT
+Same as drop GTT.
+
+4.3 CREATE INDEX ON GTT
+1) create index on GTT statements build index based on local data in a session.
+2) After the index is created, record the index metadata to the catalog.
+3) Other sessions can enable or disable the local GTT index.
+
+5 LOCK
+
+5.1 TRUNCATE GTT
+The truncate GTT command uses RowExclusiveLock, not AccessExclusiveLock, because
+this command only cleans up local data and local buffers in current session.
+
+5.2 CLUSTER GTT/VACUUM FULL GTT
+Same as truncate GTT.
+
+5.3 Lock GTT
+A lock GTT statement does not hold any table locks.
+
+6 MVCC commit log(clog) cleanup
+
+The GTT storage file contains transaction information. Queries for GTT data rely
+on transaction information such as clog. The transaction information required by
+each session may be completely different. We need to ensure that the transaction
+information of the GTT data is not cleaned up during its lifetime and that
+transaction resources are recycled at the instance level.
+
+6.1 The session level GTT oldest frozenxid
+1) To manage all GTT transaction information, add session level oldest frozenxid
+in each session. When one GTT is created or removed, record the session level
+oldest frozenxid and store it in MyProc.
+2) When vacuum advances the database's frozenxid, session level oldest frozenxid
+should be considered. This is acquired by searching all of MyProc. This way,
+we can avoid the clog required by GTTs to be cleaned.
+
+6.2 vacuum GTT
+Users can perform vacuum over a GTT to clean up local data in the GTT.
+
+6.3 autovacuum GTT
+Autovacuum skips all GTTs, because the data in GTTs is only visible in current
+session.
+
+7 OTHERS
+Parallel query
+Planner does not produce parallel query plans for SQL related to GTT. Because
+GTT private data cannot be accessed across processes.
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 245d536372..a38b594898 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -441,6 +441,7 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index ba03e8aa8f..3dd9d03831 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -62,6 +62,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,
@@ -305,7 +307,8 @@ heap_create(const char *relname,
 			bool mapped_relation,
 			bool allow_system_table_mods,
 			TransactionId *relfrozenxid,
-			MultiXactId *relminmxid)
+			MultiXactId *relminmxid,
+			bool skip_create_storage)
 {
 	bool		create_storage;
 	Relation	rel;
@@ -370,7 +373,9 @@ heap_create(const char *relname,
 	 * storage is already created, so don't do it here.  Also don't create it
 	 * for relkinds without physical storage.
 	 */
-	if (!RELKIND_HAS_STORAGE(relkind) || OidIsValid(relfilenode))
+	if (!RELKIND_HAS_STORAGE(relkind) ||
+		OidIsValid(relfilenode) ||
+		skip_create_storage)
 		create_storage = false;
 	else
 	{
@@ -428,7 +433,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -999,6 +1004,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -1037,8 +1043,21 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/*
+	 * The transaction information of the global temporary table is stored
+	 * in the local hash table, not in catalog.
+	 */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1304,7 +1323,8 @@ heap_create_with_catalog(const char *relname,
 							   mapped_relation,
 							   allow_system_table_mods,
 							   &relfrozenxid,
-							   &relminmxid);
+							   &relminmxid,
+							   false);
 
 	Assert(relid == RelationGetRelid(new_rel_desc));
 
@@ -1411,6 +1431,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1996,6 +2017,19 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/*
+	 * Only when other sessions are not using this Global temporary table,
+	 * is it allowed to DROP it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot drop global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3261,7 +3295,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3273,7 +3307,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3319,8 +3353,16 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		/*
+		 * Truncate global temporary table only clears local data,
+		 * so only low-level locks need to be held.
+		 */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3353,6 +3395,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3361,23 +3404,47 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	/* For global temporary table only */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		/*
+		 * If this GTT is not initialized in current backend, there is
+		 * no needs to anything.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(rel)))
+			return;
+
+		/*
+		 * Truncate GTT only clears local data, so only low-level locks
+		 * need to be held.
+		 */
+		lockmode = RowExclusiveLock;
+	}
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	/*
+	 * After the data is cleaned up on the GTT, the transaction information
+	 * for the data(stored in local hash table) is also need reset.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(RelationGetRelid(rel), 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index a628b3281c..a793be7710 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -55,6 +55,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "commands/defrem.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -732,6 +733,29 @@ index_create(Relation heapRelation,
 	char		relkind;
 	TransactionId relfrozenxid;
 	MultiXactId relminmxid;
+	bool		skip_create_storage = false;
+
+	/* For global temporary table only */
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temporary table with concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot reindex global temporary tables concurrently")));
+
+		/*
+		 * For the case that some backend is applied relcache message to create
+		 * an index on a global temporary table, if this table in the current
+		 * backend are not initialized, the creation of index storage on the
+		 * table are also skipped.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+		{
+			skip_create_storage = true;
+			flags |= INDEX_CREATE_SKIP_BUILD;
+		}
+	}
 
 	/* constraint flags can only be set when a constraint is requested */
 	Assert((constr_flags == 0) ||
@@ -939,7 +963,8 @@ index_create(Relation heapRelation,
 								mapped_relation,
 								allow_system_table_mods,
 								&relfrozenxid,
-								&relminmxid);
+								&relminmxid,
+								skip_create_storage);
 
 	Assert(relfrozenxid == InvalidTransactionId);
 	Assert(relminmxid == InvalidMultiXactId);
@@ -2272,7 +2297,7 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 * lock (see comments in RemoveRelations), and a non-concurrent DROP is
 	 * more efficient.
 	 */
-	Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+	Assert(!RelpersistenceTsTemp(get_rel_persistence(indexId)) ||
 		   (!concurrent && !concurrent_lock_mode));
 
 	/*
@@ -2304,6 +2329,20 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/*
+	 * Allow to drop index on global temporary table when only current
+	 * backend use it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation) &&
+		is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+	{
+		ereport(ERROR,
+			(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+			 errmsg("cannot drop index %s or global temporary table %s",
+					RelationGetRelationName(userIndexRelation), RelationGetRelationName(userHeapRelation)),
+			 errhint("Because the index is created on the global temporary table and other backend attached it.")));
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2912,6 +2951,7 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = RELATION_IS_GLOBAL_TEMP(rel);
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -3006,20 +3046,37 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
-		{
-			rd_rel->relpages = (int32) relpages;
-			dirty = true;
-		}
-		if (rd_rel->reltuples != (float4) reltuples)
+		/* For global temporary table */
+		if (is_gtt)
 		{
-			rd_rel->reltuples = (float4) reltuples;
-			dirty = true;
+			/* Update GTT'statistics into local relcache */
+			rel->rd_rel->relpages = (int32) relpages;
+			rel->rd_rel->reltuples = (float4) reltuples;
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+
+			/* Update GTT'statistics into local hashtable */
+			up_gtt_relstats(RelationGetRelid(rel), relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+		else
 		{
-			rd_rel->relallvisible = (int32) relallvisible;
-			dirty = true;
+			if (rd_rel->relpages != (int32) relpages)
+			{
+				rd_rel->relpages = (int32) relpages;
+				dirty = true;
+			}
+
+			if (rd_rel->reltuples != (float4) reltuples)
+			{
+				rd_rel->reltuples = (float4) reltuples;
+				dirty = true;
+			}
+
+			if (rd_rel->relallvisible != (int32) relallvisible)
+			{
+				rd_rel->relallvisible = (int32) relallvisible;
+				dirty = true;
+			}
 		}
 	}
 
@@ -3132,6 +3189,26 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, progress_index, progress_vals);
 	}
 
+	/* For build index on global temporary table */
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		/*
+		 * If the storage for the index in this session is not initialized,
+		 * it needs to be created.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			/* Before create init storage, fix the local Relcache first */
+			force_enable_gtt_index(indexRelation);
+
+			Assert(gtt_storage_attached(RelationGetRelid(heapRelation)));
+
+			/* Init storage for index */
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3687,6 +3764,20 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	if (!OidIsValid(heapId))
 		return;
 
+	/*
+	 * For reindex on global temporary table, If the storage for the index
+	 * in current backend is not initialized, nothing is done.
+	 */
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		!gtt_storage_attached(indexId))
+	{
+		/* Suppress use of the target index while rebuilding it */
+		SetReindexProcessing(heapId, indexId);
+		/* Re-allow use of target index */
+		ResetReindexProcessing();
+		return;
+	}
+
 	if ((params->options & REINDEXOPT_MISSING_OK) != 0)
 		heapRelation = try_table_open(heapId, ShareLock);
 	else
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 005e029c38..5e59b47969 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -655,6 +655,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/* Do not allow create global temporary table in temporary schemas */
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp table in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index cba7a9ada0..9cf6802f9b 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,6 +27,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -61,6 +62,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			temprelOid;			/* InvalidOid if not a global temporary rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -115,7 +117,7 @@ AddPendingSync(const RelFileNode *rnode)
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -126,7 +128,12 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/*
+		 * Global temporary table and local temporary table use same
+		 * design on storage module.
+		 */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -154,6 +161,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
@@ -165,6 +173,21 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		AddPendingSync(&rnode);
 	}
 
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		Assert(rel && RELATION_IS_GLOBAL_TEMP(rel));
+
+		/*
+		 * Remember the reloid of global temporary table, which is used for
+		 * transaction commit or rollback.
+		 * see smgrDoPendingDeletes.
+		 */
+		pending->temprelOid = RelationGetRelid(rel);
+
+		/* Remember global temporary table storage info to localhash */
+		remember_gtt_storage_info(rnode, rel);
+	}
+
 	return srel;
 }
 
@@ -201,11 +224,20 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/*
+	 * Remember the reloid of global temporary table, which is used for
+	 * transaction commit or rollback.
+	 * see smgrDoPendingDeletes.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->temprelOid = RelationGetRelid(rel);
+
 	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
@@ -602,6 +634,7 @@ smgrDoPendingDeletes(bool isCommit)
 	int			nrels = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -631,14 +664,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->temprelOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -648,12 +685,21 @@ smgrDoPendingDeletes(bool isCommit)
 
 	if (nrels > 0)
 	{
+		int	i;
+
 		smgrdounlinkall(srels, nrels, false);
 
-		for (int i = 0; i < nrels; i++)
+		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* Delete global temporary table info in localhash */
+			if (gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000000..ec10a2df03
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1648 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  The body implementation of Global Temparary table.
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *	  See src/backend/catalog/GTT_README for Global temparary table's
+ *	  requirements and design.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_info_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+int		vacuum_gtt_defer_check_age = 0;
+
+/*
+ * The Global temporary table's shared hash table data structure
+ */
+typedef struct gtt_ctl_data
+{
+	LWLock		lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+/* record this global temporary table in which backends are being used */
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+/*
+ * The Global temporary table's local hash table data structure
+ */
+/* Record the storage information and statistical information of the global temporary table */
+typedef struct
+{
+	Oid			relfilenode;
+	Oid			spcnode;
+
+	/* pg_class relstat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+
+	/* pg_statistic column stat */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+
+	Oid			oldrelid;			/* remember the source of relid, before the switch relfilenode. */
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_free_statistics(gtt_relfilenode *rnode);
+static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok);
+static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok);
+static Bitmapset *copy_active_gtt_bitmap(Oid relid);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+/*
+ * Calculate shared hash table entry size for GTT.
+ */
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	/* hash entry header size */
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	/*
+	 * hash entry data size
+	 * this is a bitmap in shared memory, each backend have a bit.
+	 */
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+/*
+ * Calculate shared hash table max size for GTT.
+ */
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	/* shared hash header size */
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	/* hash entry size */
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	/* max size */
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+/*
+ * Initialization shared hash table for GTT.
+ */
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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();
+	}
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(gtt_fnode);
+	info.entrysize = action_gtt_shared_hash_entry_size();
+	active_gtt_shared_hash =
+		ShmemInitHash("active gtt shared hash",
+						gtt_shared_ctl->max_entry,
+						gtt_shared_ctl->max_entry,
+						&info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+/*
+ * Record GTT relid to shared hash table, which means that current backend is using this GTT.
+ */
+static void
+gtt_storage_checkin(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			found;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *)&(fnode), 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_global_temporary_table.")));
+	}
+
+	if (!found)
+	{
+		int			wordnum;
+
+		/* init bitmap */
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	/* record itself in bitmap */
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+/*
+ * Remove the GTT relid record from the shared hash table which means that current backend is
+ * not use this GTT.
+ */
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode				fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	if (!skiplock)
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		if (!skiplock)
+			LWLockRelease(&gtt_shared_ctl->lock);
+
+		if (isCommit)
+			elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+	/* remove itself from bitmap */
+	bms_del_member(entry->map, MyBackendId);
+
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+
+	if (!skiplock)
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+	return;
+}
+
+/*
+ * Gets usage information for a GTT from shared hash table.
+ * The information is in the form of bitmap.
+ * Quickly copy the entire bitmap from shared memory and return it.
+ * that to avoid holding locks for a long time.
+ */
+static Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	Bitmapset	*map_copy = NULL;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		return NULL;
+	}
+
+	Assert(entry->map);
+
+	/* copy the entire bitmap */
+	if (!bms_is_empty(entry->map))
+		map_copy = bms_copy(entry->map);
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return map_copy;
+}
+
+/*
+ * Check if there are other backends using this GTT besides the current backend.
+ */
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool		in_use = false;
+	int			num_use = 0;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		return false;
+	}
+
+	Assert(entry->map);
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+	/* how many backend are using this GTT */
+	num_use = bms_num_members(entry->map);
+	if (num_use == 0)
+		in_use = false;
+	else if (num_use == 1)
+	{
+		/* check if this is itself */
+		if(bms_is_member(MyBackendId, entry->map))
+			in_use = false;
+		else
+			in_use = true;
+	}
+	else
+		in_use = true;
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return in_use;
+}
+
+/*
+ * Record GTT information to local hash.
+ * They include GTT storage info, transaction info and statistical info.
+ */
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+	gtt_local_hash_entry	*entry;
+	MemoryContext			oldcontext;
+	gtt_relfilenode			*new_node = NULL;
+	Oid						relid = RelationGetRelid(rel);
+	int 					natts = 0;
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	/* First time through: initialize the hash table */
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		HASHCTL		ctl;
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_info_context =
+			AllocSetContextCreate(CacheMemoryContext,
+								"gtt info context",
+								ALLOCSET_DEFAULT_SIZES);
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		ctl.hcxt = gtt_info_context;
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+	}
+
+	Assert(CacheMemoryContext);
+	Assert(gtt_info_context);
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	entry = gtt_search_by_relid(relid, true);
+	if (!entry)
+	{
+		bool		found = false;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash,
+						(void *) &relid, HASH_ENTER, &found);
+
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+		entry->oldrelid = InvalidOid;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			/* record the on commit clause */
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			gtt_storage_checkin(relid);
+		}
+	}
+
+	/* record storage info relstat columnstats and transaction info to relfilenode list */
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	new_node->natts = 0;
+	new_node->attnum = NULL;
+	new_node->att_stat_tups = NULL;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* init column stats structure */
+	natts = RelationGetNumberOfAttributes(rel);
+	new_node->attnum = palloc0(sizeof(int) * natts);
+	new_node->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+	new_node->natts = natts;
+
+	/* only heap have transaction info */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+
+		/**/
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+
+	/* Registration callbacks are used to trigger cleanup during process exit */
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+/*
+ * Remove GTT information from local hash when transaction commit/rollback.
+ */
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	gtt_relfilenode *d_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+		return;
+	}
+
+	d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+		else if (entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+			gtt_relfilenode *gttnode2 = NULL;
+
+			/*
+			 * For cluster GTT rollback.
+			 * We need to roll back the exchange relfilenode operation.
+			 */
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+			Assert(gttnode2->relfilenode == rnode.relNode);
+			Assert(list_length(entry->relfilenode_list) == 1);
+			/* rollback switch relfilenode */
+			gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+									   entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+									   false);
+			/* clean up footprint */
+			entry2->oldrelid = InvalidOid;
+
+			/* temp relfilenode need free */
+			d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+			Assert(d_rnode);
+		}
+		else
+		{
+			/* rollback transaction */
+			if (entry->relfilenode_list == NIL)
+			{
+				if (entry->relkind == RELKIND_RELATION ||
+					entry->relkind == RELKIND_SEQUENCE)
+					gtt_storage_checkout(relid, false, isCommit);
+
+				hash_search(gtt_storage_local_hash,
+						(void *) &(relid), HASH_REMOVE, NULL);
+			}
+
+			return;
+		}
+	}
+
+	/* Clean up transaction info from Local order list and MyProc */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+
+		/* this is valid relfrozenxid */
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	/* delete relfilenode from rel entry */
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	gtt_free_statistics(d_rnode);
+
+	if (entry->relfilenode_list == NIL)
+	{
+		/* this means we truncate this GTT at current backend */
+
+		/* tell shared hash that current backend will no longer use this GTT */
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(relid, false, isCommit);
+
+		if (isCommit && entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+
+			/* commit transaction at cluster GTT, need clean up footprint */
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			entry2->oldrelid = InvalidOid;
+		}
+
+		hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_REMOVE, NULL);
+	}
+
+	return;
+}
+
+/*
+ * Check if current backend is using this GTT.
+ */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool found = false;
+	gtt_local_hash_entry *entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (!OidIsValid(relid))
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry)
+		found = true;
+
+	return found;
+}
+
+/*
+ * When backend exit, bulk cleaning all GTT storage and local buffer of this backend.
+ */
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS				status;
+	gtt_local_hash_entry		*entry;
+	SMgrRelation	*srels = NULL;
+	Oid				*relids = NULL;
+	char			*relkinds = NULL;
+	int			nrels = 0,
+				nfiles = 0,
+				maxrels = 0,
+				maxfiles = 0,
+				i = 0;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	/* Search all relfilenode for GTT in current backend */
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel;
+			RelFileNode		rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel = smgropen(rnode, MyBackendId);
+
+			if (maxfiles == 0)
+			{
+				maxfiles = 32;
+				srels = palloc(sizeof(SMgrRelation) * maxfiles);
+			}
+			else if (maxfiles <= nfiles)
+			{
+				maxfiles *= 2;
+				srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+			}
+
+			srels[nfiles++] = srel;
+		}
+
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			relids  = palloc(sizeof(Oid) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			relids  = repalloc(relids , sizeof(Oid) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		relkinds[nrels] = entry->relkind;
+		relids[nrels] = entry->relid;
+		nrels++;
+	}
+
+	/* drop local buffer and storage */
+	if (nfiles > 0)
+	{
+		smgrdounlinkall(srels, nfiles, false);
+		for (i = 0; i < nfiles; i++)
+			smgrclose(srels[i]);
+
+		pfree(srels);
+	}
+
+	if (nrels)
+	{
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+		for (i = 0; i < nrels; i++)
+		{
+			/* tell shared hash */
+			if (relkinds[i] == RELKIND_RELATION ||
+				relkinds[i] == RELKIND_SEQUENCE)
+				gtt_storage_checkout(relids[i], true, false);
+		}
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+		pfree(relids);
+		pfree(relkinds);
+	}
+
+	/* set to global area */
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;
+
+	return;
+}
+
+/*
+ * Update GTT relstats(relpage/reltuple/relallvisible)
+ * to local hash.
+ */
+void
+up_gtt_relstats(Oid relid,
+					BlockNumber num_pages,
+					double num_tuples,
+					BlockNumber num_all_visible_pages,
+					TransactionId relfrozenxid,
+					TransactionId relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (!OidIsValid(relid))
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (num_pages > 0 &&
+		gtt_rnode->relpages != (int32)num_pages)
+		gtt_rnode->relpages = (int32)num_pages;
+
+	if (num_tuples > 0 &&
+		gtt_rnode->reltuples != (float4)num_tuples)
+		gtt_rnode->reltuples = (float4)num_tuples;
+
+	/* only heap contain transaction information and relallvisible */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (num_all_visible_pages > 0 &&
+			gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+		{
+			gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+		}
+
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNextTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			/* set to local order list */
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			/* set to global area */
+			set_gtt_session_relfrozenxid();
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search GTT relstats(relpage/reltuple/relallvisible)
+ * from local has.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update GTT info(definition is same as pg_statistic)
+ * to local hash.
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	MemoryContext			oldcontext;
+	int						i = 0;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return;
+
+	Assert(entry->relid == reloid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (gtt_rnode->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	/* switch context to gtt_info_context for store tuple at heap_form_tuple */
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == 0)
+		{
+			gtt_rnode->attnum[i] = attnum;
+			break;
+		}
+		else if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			heap_freetuple(gtt_rnode->att_stat_tups[i]);
+			gtt_rnode->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < gtt_rnode->natts);
+	Assert(gtt_rnode->att_stat_tups[i] == NULL);
+	gtt_rnode->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search GTT statistic info(definition is same as pg_statistic)
+ * from local hash.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	int						i = 0;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return NULL;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return NULL;
+
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			return gtt_rnode->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Insert a RelfrozenXID into the list and keep the list in order.
+ */
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell		*cell;
+	int				i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Remove a RelfrozenXID from order list gtt_session_relfrozenxid_list.
+ */
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+/*
+ * Update of backend Level oldest relfrozenxid to MyProc.
+ * This makes each backend's oldest RelFrozenxID globally visible.
+ */
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->backend_gtt_frozenxid != gtt_frozenxid)
+		MyProc->backend_gtt_frozenxid = gtt_frozenxid;
+}
+
+/*
+ * Get GTT column level data statistics.
+ */
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo 	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate *tupstore;
+	HeapTuple		tuple;
+	Relation		rel = NULL;
+	Oid				reloid = PG_GETARG_OID(0);
+	int				attnum = PG_GETARG_INT32(1);
+	char			rel_persistence;
+	TupleDesc	  	tupdesc;
+	MemoryContext 	oldcontext;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	/* get data from local hash */
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, NoLock);
+	relation_close(pg_tatistic, AccessShareLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get GTT table level data statistics.
+ */
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate	*tupstore;
+	TupleDesc		tupdesc;
+	MemoryContext	oldcontext;
+	HeapTuple		tuple;
+	Oid				reloid = PG_GETARG_OID(0);
+	Oid				relnode = 0;
+	char			rel_persistence;
+	BlockNumber		relpages = 0;
+	BlockNumber		relallvisible = 0;
+	uint32			relfrozenxid = 0;
+	uint32			relminmxid = 0;
+	double			reltuples = 0;
+	Relation		rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[6];
+		bool	isnull[6];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get a list of backend pids that are currently using this GTT.
+ */
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	PGPROC			*proc = NULL;
+	Bitmapset		*map = NULL;
+	Tuplestorestate *tupstore;
+	TupleDesc		tupdesc;
+	MemoryContext	oldcontext;
+	HeapTuple		tuple;
+	Oid				reloid = PG_GETARG_OID(0);
+	char			rel_persistence;
+	Relation		rel = NULL;
+	pid_t			pid = 0;
+	int				backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	/* get data from share hash */
+	map = copy_active_gtt_bitmap(reloid);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			/* backendid map to process pid */
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get backend level oldest relfrozenxid of each backend using GTT in current database.
+ */
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate *tupstore;
+	int				*pids = NULL;
+	uint32			*xids = NULL;
+	TupleDesc		tupdesc;
+	MemoryContext	oldcontext;
+	HeapTuple		tuple;
+	int				num_xid = MaxBackends + 1;
+	int				i = 0;
+	int				j = 0;
+	uint32			oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+
+	/* Get backend level oldest relfrozenxid in all backend that in MyDatabaseId use GTT */
+	oldest = list_all_backend_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+/*
+ * In order to build the GTT index, force enable GTT'index.
+ */
+void
+force_enable_gtt_index(Relation index)
+{
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(RelationGetRelid(index)));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+/*
+ * Fix the local state of the GTT's index.
+ */
+void
+gtt_fix_index_backend_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid heapOid = index->rd_index->indrelid;
+
+	/* Must be GTT */
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	/*
+	 * If this GTT is not initialized in the current backend,
+	 * its index status is temporarily set to invalid(local relcache).
+	 */
+	if (gtt_storage_attached(heapOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+/*
+ * During the SQL initialization of the executor (InitPlan)
+ * Initialize storage of GTT GTT'indexes and build empty index.
+ */
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int			i;
+	Oid			toastrelid;
+
+	if (!(operation == CMD_UTILITY || operation == CMD_INSERT))
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	/* Each GTT is initialized once in each backend */
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	/* init heap storage */
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+
+		index_build(relation, index, info, true, false);
+	}
+
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+
+		/* init index storage */
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid			indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+			/* build empty index */
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+/*
+ * Release the data structure memory used to store GTT storage info.
+ */
+static void
+gtt_free_statistics(gtt_relfilenode *rnode)
+{
+	int i;
+
+	Assert(rnode);
+
+	for (i = 0; i < rnode->natts; i++)
+	{
+		if (rnode->att_stat_tups[i])
+		{
+			heap_freetuple(rnode->att_stat_tups[i]);
+			rnode->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (rnode->attnum)
+		pfree(rnode->attnum);
+
+	if (rnode->att_stat_tups)
+		pfree(rnode->att_stat_tups);
+
+	pfree(rnode);
+
+	return;
+}
+
+/*
+ * Get the current relfilenode of this GTT.
+ */
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
+/*
+ * For cluster GTT.
+ * Exchange new and old relfilenode, leave footprints ensure rollback capability.
+ */
+void
+gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint)
+{
+	gtt_local_hash_entry	*entry1;
+	gtt_local_hash_entry	*entry2;
+	gtt_relfilenode			*gtt_rnode1 = NULL;
+	gtt_relfilenode			*gtt_rnode2 = NULL;
+	MemoryContext			oldcontext;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry1 = gtt_search_by_relid(rel1, false);
+	gtt_rnode1 = gtt_search_relfilenode(entry1, relfilenode1, false);
+
+	entry2 = gtt_search_by_relid(rel2, false);
+	gtt_rnode2 = gtt_search_relfilenode(entry2, relfilenode2, false);
+
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+	entry1->relfilenode_list = list_delete_ptr(entry1->relfilenode_list, gtt_rnode1);
+	entry2->relfilenode_list = lappend(entry2->relfilenode_list, gtt_rnode1);
+
+	entry2->relfilenode_list = list_delete_ptr(entry2->relfilenode_list, gtt_rnode2);
+	entry1->relfilenode_list = lappend(entry1->relfilenode_list, gtt_rnode2);
+	MemoryContextSwitchTo(oldcontext);
+
+	if (footprint)
+	{
+		entry1->oldrelid = rel2;
+		entry2->oldrelid = rel1;
+	}
+
+	return;
+}
+
+/*
+ * Get a relfilenode used by this GTT during the transaction life cycle.
+ */
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok)
+{
+	gtt_relfilenode		*rnode = NULL;
+	ListCell			*lc;
+
+	Assert(entry);
+
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == relfilenode)
+		{
+			rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (!missing_ok && rnode == NULL)
+		elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+	return rnode;
+}
+
+/*
+ * Get one GTT info from local hash.
+ */
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry == NULL && !missing_ok)
+		elog(ERROR, "relid %u not found in local hash", relid);
+
+	return entry;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 451db2ee0a..95e25af49e 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index cffcd54302..e97b30ae99 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -104,7 +105,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -185,6 +186,17 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -587,14 +599,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]));
 		}
 
 		/*
@@ -1638,7 +1651,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;
@@ -1740,31 +1753,48 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/*
+		 * For global temporary table,
+		 * Update column statistic to localhash, not catalog.
+		 */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 6487a9e3fc..9fa0d136ed 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/toasting.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
@@ -73,6 +74,12 @@ static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
 							bool verbose, bool *pSwapToastByContent,
 							TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
 static List *get_tables_to_cluster(MemoryContext cluster_context);
+static void gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables);
 
 
 /*---------------------------------------------------------------------------
@@ -390,6 +397,18 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap) &&
+		!gtt_storage_attached(RelationGetRelid(OldHeap)))
+	{
+		relation_close(OldHeap, AccessExclusiveLock);
+		pgstat_progress_end_command();
+		return;
+	}
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
@@ -774,6 +793,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	BlockNumber num_pages;
 	int			elevel = verbose ? INFO : DEBUG2;
 	PGRUsage	ru0;
+	bool		is_gtt = false;
+	uint32		gtt_relfrozenxid = 0;
+	uint32		gtt_relminmxid = 0;
 
 	pg_rusage_init(&ru0);
 
@@ -787,6 +809,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	else
 		OldIndex = NULL;
 
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		is_gtt = true;
+
 	/*
 	 * Their tuple descriptors should be exactly alike, but here we only need
 	 * assume that they have the same number of columns.
@@ -854,20 +879,38 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 						  &OldestXmin, &FreezeXid, NULL, &MultiXactCutoff,
 						  NULL);
 
-	/*
-	 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
-	 * backwards, so take the max.
-	 */
-	if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
-		TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
-		FreezeXid = OldHeap->rd_rel->relfrozenxid;
+	if (is_gtt)
+	{
+		/* Gets transaction information for global temporary table from localhash. */
+		get_gtt_relstats(OIDOldHeap,
+					NULL, NULL, NULL,
+					&gtt_relfrozenxid, &gtt_relminmxid);
+
+		if (TransactionIdIsValid(gtt_relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, gtt_relfrozenxid))
+			FreezeXid = gtt_relfrozenxid;
+
+		if (MultiXactIdIsValid(gtt_relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, gtt_relminmxid))
+			MultiXactCutoff = gtt_relminmxid;
+	}
+	else
+	{
+		/*
+		 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
+		 * backwards, so take the max.
+		 */
+		if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
+			FreezeXid = OldHeap->rd_rel->relfrozenxid;
 
-	/*
-	 * MultiXactCutoff, similarly, shouldn't go backwards either.
-	 */
-	if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
-		MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
-		MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+		/*
+		 * MultiXactCutoff, similarly, shouldn't go backwards either.
+		 */
+		if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
+			MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+	}
 
 	/*
 	 * Decide whether to use an indexscan or seqscan-and-optional-sort to scan
@@ -935,6 +978,15 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	table_close(OldHeap, NoLock);
 	table_close(NewHeap, NoLock);
 
+	/* Update relstats of global temporary table to localhash. */
+	if (is_gtt)
+	{
+		up_gtt_relstats(RelationGetRelid(NewHeap), num_pages, num_tuples, 0,
+						InvalidTransactionId, InvalidMultiXactId);
+		CommandCounterIncrement();
+		return;
+	}
+
 	/* Update pg_class to reflect the correct values of pages and tuples. */
 	relRelation = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1371,10 +1423,22 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 	 * Swap the contents of the heap relations (including any toast tables).
 	 * Also set old heap's relfrozenxid to frozenXid.
 	 */
-	swap_relation_files(OIDOldHeap, OIDNewHeap,
+	if (newrelpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		Assert(!is_system_catalog);
+		/* For global temporary table modify data in localhash, not pg_class */
+		gtt_swap_relation_files(OIDOldHeap, OIDNewHeap,
+								(OIDOldHeap == RelationRelationId),
+								swap_toast_by_content, is_internal,
+								frozenXid, cutoffMulti, mapped_tables);
+	}
+	else
+	{
+		swap_relation_files(OIDOldHeap, OIDNewHeap,
 						(OIDOldHeap == RelationRelationId),
 						swap_toast_by_content, is_internal,
 						frozenXid, cutoffMulti, mapped_tables);
+	}
 
 	/*
 	 * If it's a system catalog, queue a sinval message to flush all catcaches
@@ -1582,3 +1646,146 @@ get_tables_to_cluster(MemoryContext cluster_context)
 
 	return rvs;
 }
+
+/*
+ * For global temporary table, storage information is stored in localhash,
+ * This function like swap_relation_files except that update storage information,
+ * in the localhash, not pg_class.
+ */
+static void
+gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables)
+{
+	Relation	relRelation;
+	Oid			relfilenode1,
+				relfilenode2;
+	Relation	rel1;
+	Relation	rel2;
+
+	relRelation = table_open(RelationRelationId, RowExclusiveLock);
+
+	rel1 = relation_open(r1, AccessExclusiveLock);
+	rel2 = relation_open(r2, AccessExclusiveLock);
+
+	relfilenode1 = gtt_fetch_current_relfilenode(r1);
+	relfilenode2 = gtt_fetch_current_relfilenode(r2);
+
+	Assert(OidIsValid(relfilenode1) && OidIsValid(relfilenode2));
+	gtt_switch_rel_relfilenode(r1, relfilenode1, r2, relfilenode2, true);
+
+	CacheInvalidateRelcache(rel1);
+	CacheInvalidateRelcache(rel2);
+
+	InvokeObjectPostAlterHookArg(RelationRelationId, r1, 0,
+								 InvalidOid, is_internal);
+	InvokeObjectPostAlterHookArg(RelationRelationId, r2, 0,
+								 InvalidOid, true);
+
+	if (rel1->rd_rel->reltoastrelid || rel2->rd_rel->reltoastrelid)
+	{
+		if (swap_toast_by_content)
+		{
+			if (rel1->rd_rel->reltoastrelid && rel2->rd_rel->reltoastrelid)
+			{
+				gtt_swap_relation_files(rel1->rd_rel->reltoastrelid,
+									rel2->rd_rel->reltoastrelid,
+									target_is_pg_class,
+									swap_toast_by_content,
+									is_internal,
+									frozenXid,
+									cutoffMulti,
+									mapped_tables);
+			}
+			else
+				elog(ERROR, "cannot swap toast files by content when there's only one");
+		}
+		else
+		{
+			ObjectAddress baseobject,
+						toastobject;
+			long		count;
+
+			if (IsSystemRelation(rel1))
+				elog(ERROR, "cannot swap toast files by links for system catalogs");
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel1->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel2->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+
+			/* Register new dependencies */
+			baseobject.classId = RelationRelationId;
+			baseobject.objectSubId = 0;
+			toastobject.classId = RelationRelationId;
+			toastobject.objectSubId = 0;
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r1;
+				toastobject.objectId = rel1->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r2;
+				toastobject.objectId = rel2->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+		}
+	}
+
+	if (swap_toast_by_content &&
+		rel1->rd_rel->relkind == RELKIND_TOASTVALUE &&
+		rel2->rd_rel->relkind == RELKIND_TOASTVALUE)
+	{
+		Oid			toastIndex1,
+					toastIndex2;
+
+		/* Get valid index for each relation */
+		toastIndex1 = toast_get_valid_index(r1,
+											AccessExclusiveLock);
+		toastIndex2 = toast_get_valid_index(r2,
+											AccessExclusiveLock);
+
+		gtt_swap_relation_files(toastIndex1,
+							toastIndex2,
+							target_is_pg_class,
+							swap_toast_by_content,
+							is_internal,
+							InvalidTransactionId,
+							InvalidMultiXactId,
+							mapped_tables);
+	}
+
+	relation_close(rel1, NoLock);
+	relation_close(rel2, NoLock);
+
+	table_close(relRelation, RowExclusiveLock);
+
+	RelationCloseSmgrByOid(r1);
+	RelationCloseSmgrByOid(r2);
+
+	CommandCounterIncrement();
+}
+
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 8265b981eb..ef9d710bfe 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -290,7 +290,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP_ON_CURRENT_SESSION(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, whereClause,
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 40a54ad0bd..7895e7d99b 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -30,6 +30,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/copyfrom_internal.h"
 #include "commands/progress.h"
@@ -659,6 +660,9 @@ CopyFrom(CopyFromState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	/* Check and init global temporary table storage in current backend */
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	/*
 	 * Set up a ModifyTableState so we can let FDW(s) init themselves for
 	 * foreign-table result relation(s).
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 166374cc0c..a40edd3090 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -570,7 +570,7 @@ DefineIndex(Oid relationId,
 	 * is more efficient.  Do this before any use of the concurrent option is
 	 * done.
 	 */
-	if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+	if (stmt->concurrent && !RelpersistenceTsTemp(get_rel_persistence(relationId)))
 		concurrent = true;
 	else
 		concurrent = false;
@@ -2608,7 +2608,7 @@ ReindexIndex(RangeVar *indexRelation, ReindexParams *params, bool isTopLevel)
 	if (relkind == RELKIND_PARTITIONED_INDEX)
 		ReindexPartitions(indOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 persistence != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(persistence))
 		ReindexRelationConcurrently(indOid, params);
 	else
 	{
@@ -2717,7 +2717,7 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 	if (get_rel_relkind(heapOid) == RELKIND_PARTITIONED_TABLE)
 		ReindexPartitions(heapOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(get_rel_persistence(heapOid)))
 	{
 		result = ReindexRelationConcurrently(heapOid, params);
 
@@ -3132,7 +3132,7 @@ ReindexMultipleInternal(List *relids, ReindexParams *params)
 			   relkind != RELKIND_PARTITIONED_TABLE);
 
 		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			relpersistence != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(relpersistence))
 		{
 			ReindexParams newparams = *params;
 
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 34f2270ced..b8ea2efb5f 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -57,7 +57,10 @@ LockTableCommand(LockStmt *lockstmt)
 										  RangeVarCallbackForLockTable,
 										  (void *) &lockstmt->mode);
 
-		if (get_rel_relkind(reloid) == RELKIND_VIEW)
+		/* Lock table command ignores global temporary table. */
+		if (get_rel_persistence(reloid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+		else if (get_rel_relkind(reloid) == RELKIND_VIEW)
 			LockViewRecurse(reloid, lockstmt->mode, lockstmt->nowait, NIL);
 		else if (recurse)
 			LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait);
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 0415df9ccb..39f82bc278 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -275,8 +278,6 @@ ResetSequence(Oid seq_relid)
 	Buffer		buf;
 	HeapTupleData seqdatatuple;
 	HeapTuple	tuple;
-	HeapTuple	pgstuple;
-	Form_pg_sequence pgsform;
 	int64		startv;
 
 	/*
@@ -287,12 +288,7 @@ ResetSequence(Oid seq_relid)
 	init_sequence(seq_relid, &elm, &seq_rel);
 	(void) read_seq_tuple(seq_rel, &buf, &seqdatatuple);
 
-	pgstuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seq_relid));
-	if (!HeapTupleIsValid(pgstuple))
-		elog(ERROR, "cache lookup failed for sequence %u", seq_relid);
-	pgsform = (Form_pg_sequence) GETSTRUCT(pgstuple);
-	startv = pgsform->seqstart;
-	ReleaseSysCache(pgstuple);
+	startv = get_seqence_start_value(seq_relid);
 
 	/*
 	 * Copy the existing sequence tuple.
@@ -451,6 +447,15 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 
 	init_sequence(relid, &elm, &seqrel);
 
+	if (RELATION_IS_GLOBAL_TEMP(seqrel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(seqrel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary sequence %s when other backend attached it.",
+						RelationGetRelationName(seqrel))));
+	}
+
 	rel = table_open(SequenceRelationId, RowExclusiveLock);
 	seqtuple = SearchSysCacheCopy1(SEQRELID,
 								   ObjectIdGetDatum(relid));
@@ -611,7 +616,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +941,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1153,6 +1158,14 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	/* Initializes the storage for sequence which the global temporary table belongs. */
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1953,3 +1966,51 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+/*
+ * Get the startValue of the sequence from syscache.
+ */
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+/*
+ * Initialize sequence which global temporary table belongs.
+ */
+void
+gtt_init_seq(Relation rel)
+{
+	Datum		value[SEQ_COL_LASTCOL] = {0};
+	bool		null[SEQ_COL_LASTCOL] = {false};
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 096a6f2891..fb758ad064 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -602,6 +603,7 @@ static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
 static void ATExecAlterCollationRefreshVersion(Relation rel, List *coll);
 static char GetAttributeCompression(Form_pg_attribute att, char *compression);
+static OnCommitAction gtt_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -647,6 +649,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -658,7 +661,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * Check consistency of arguments
 	 */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !RelpersistenceTsTemp(stmt->relation->relpersistence))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -688,7 +691,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	if (RelpersistenceTsTemp(stmt->relation->relpersistence)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -789,6 +792,56 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* For global temporary table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* Check parent table */
+		if (inheritOids)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			table_close(relation, NoLock);
+		}
+
+		/* Check oncommit clause and save to reloptions */
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temporary table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1423,7 +1476,7 @@ RemoveRelations(DropStmt *drop)
 		 * relation persistence cannot be known without its OID.
 		 */
 		if (drop->concurrent &&
-			get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(get_rel_persistence(relOid)))
 		{
 			Assert(list_length(drop->objects) == 1 &&
 				   drop->removeType == OBJECT_INDEX);
@@ -1633,7 +1686,16 @@ ExecuteTruncate(TruncateStmt *stmt)
 		Relation	rel;
 		bool		recurse = rv->inh;
 		Oid			myrelid;
-		LOCKMODE	lockmode = AccessExclusiveLock;
+		LOCKMODE	lockmode;
+
+		/*
+		 * Truncate global temp table only cleans up the data in current backend,
+		 * only low-level locks are required.
+		 */
+		if (rv->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+		else
+			lockmode = AccessExclusiveLock;
 
 		myrelid = RangeVarGetRelidExtended(rv, lockmode,
 										   0, RangeVarCallbackForTruncate,
@@ -1964,6 +2026,14 @@ ExecuteTruncateGuts(List *explicit_rels,
 			continue;
 		}
 
+		/*
+		 * Skip the global temporary table that is not initialized for storage
+		 * in current backend.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			continue;
+
 		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
@@ -4032,6 +4102,16 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temporary table only current backend use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -5406,6 +5486,42 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot rewrite temporary tables of other sessions")));
 
+			if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+			{
+				if (tab->chgPersistence)
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot change global temporary table persistence setting")));
+
+				/*
+				 * The storage for the global temporary table needs to be initialized
+				 * before rewrite table.
+				 */
+				if(!gtt_storage_attached(tab->relid))
+				{
+					ResultRelInfo *resultRelInfo;
+					MemoryContext oldcontext;
+					MemoryContext ctx_alter_gtt;
+
+					ctx_alter_gtt = AllocSetContextCreate(CurrentMemoryContext,
+											"gtt alter table", ALLOCSET_DEFAULT_SIZES);
+					oldcontext = MemoryContextSwitchTo(ctx_alter_gtt);
+
+					resultRelInfo = makeNode(ResultRelInfo);
+					InitResultRelInfo(resultRelInfo, OldHeap,
+									1, NULL, 0);
+					if (resultRelInfo->ri_RelationDesc->rd_rel->relhasindex &&
+						resultRelInfo->ri_IndexRelationDescs == NULL)
+						ExecOpenIndices(resultRelInfo, false);
+
+					init_gtt_storage(CMD_UTILITY, resultRelInfo);
+					ExecCloseIndices(resultRelInfo);
+
+					MemoryContextSwitchTo(oldcontext);
+					MemoryContextDelete(ctx_alter_gtt);
+				}
+			}
+
 			/*
 			 * Select destination tablespace (same as original unless user
 			 * requested a change)
@@ -8933,6 +9049,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -13514,6 +13636,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -13712,6 +13837,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temporary table");
+
 	/* Check first if relation can be moved to new tablespace */
 	if (!CheckRelationTableSpaceMove(rel, newTableSpace))
 	{
@@ -14016,7 +14144,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,
@@ -15544,6 +15672,7 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
 	switch (rel->rd_rel->relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -18504,3 +18633,40 @@ GetAttributeCompression(Form_pg_attribute att, char *compression)
 
 	return cmethod;
 }
+
+/*
+ * Parse the on commit clause for the temporary table
+ */
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			bool	res = false;
+			char	*sval = defGetString(def);
+
+			/* It has to be a Boolean value */
+			if (!parse_bool(sval, &res))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
+
+			if (res)
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 39df05c735..0a53f57c12 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"
@@ -1301,6 +1302,22 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = RELATION_IS_GLOBAL_TEMP(relation);
+
+	 /* For global temporary table */
+	if (is_gtt)
+	{
+		/* Store relation statistics and transaction information to the localhash */
+		up_gtt_relstats(RelationGetRelid(relation),
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+
+		/* Update relation statistics to local relcache */
+		relation->rd_rel->relpages = (int32) num_pages;
+		relation->rd_rel->reltuples = (float4) num_tuples;
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1314,17 +1331,23 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (!is_gtt &&
+		pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (!is_gtt &&
+		pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (!is_gtt &&
+		pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1369,7 +1392,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNextTransactionId(),
@@ -1380,7 +1404,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1488,6 +1513,13 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/*
+		 * The relfrozenxid for a global temporary talble is stored in localhash,
+		 * not pg_class, See list_all_session_gtt_frozenxids()
+		 */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1545,6 +1577,43 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/* If enable global temporary table */
+	if (max_active_gtt > 0)
+	{
+		TransactionId	safe_age;
+		/*  */
+		TransactionId	oldest_gtt_frozenxid =
+			list_all_backend_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid))
+		{
+			safe_age = oldest_gtt_frozenxid + vacuum_gtt_defer_check_age;
+			if (safe_age < FirstNormalTransactionId)
+				safe_age += FirstNormalTransactionId;
+
+			/*
+			 * We tolerate that the minimum age of gtt is less than
+			 * the minimum age of conventional tables, otherwise it will
+			 * throw warning message.
+			 */
+			if (TransactionIdIsNormal(safe_age) &&
+				TransactionIdPrecedes(safe_age, newFrozenXid))
+			{
+				ereport(WARNING,
+					(errmsg("global temp table oldest relfrozenxid %u is the oldest in the entire db",
+							oldest_gtt_frozenxid),
+					 errdetail("The oldest relfrozenxid in pg_class is %u", newFrozenXid),
+					 errhint("If they differ greatly, please consider cleaning up the data in global temp table.")));
+			}
+
+			/*
+			 * We need to ensure that the clog required by gtt is not cleand.
+			 */
+			if (TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+				newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1896,6 +1965,19 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	/*
+	 * Skip those global temporary table that are not initialized in
+	 * current backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel) &&
+		!gtt_storage_attached(RelationGetRelid(rel)))
+	{
+		relation_close(rel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index f2642dba6c..e088101ccd 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -530,6 +530,12 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	/* Global temporary table are not sensible. */
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b2e2df8773..88fb86265a 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -782,6 +782,10 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		/* This is one kind of temp table */
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 99780ebb96..0f0c318d98 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
@@ -533,6 +534,9 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	/* Init storage for partitioned global temporary table in current backend */
+	init_gtt_storage(mtstate->operation, leaf_part_rri);
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index c5a2a9a054..e473524f16 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -38,6 +38,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -632,6 +633,9 @@ ExecInsert(ModifyTableState *mtstate,
 		resultRelInfo->ri_IndexRelationDescs == NULL)
 		ExecOpenIndices(resultRelInfo, onconflict != ONCONFLICT_NONE);
 
+	/* Init storage for global temporary table in current backend */
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	/*
 	 * BEFORE ROW INSERT Triggers.
 	 *
@@ -2786,6 +2790,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		i++;
 	}
 
+
 	/*
 	 * Now we may initialize the subplan.
 	 */
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index edba5e49a8..2ae3a6d4a8 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -48,7 +48,7 @@
 #include "partitioning/partprune.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/lsyscache.h"
-
+#include "utils/rel.h"
 
 /* results of subquery_is_pushdown_safe */
 typedef struct pushdown_safety_info
@@ -619,7 +619,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			if (RelpersistenceTsTemp(get_rel_persistence(rte->relid)))
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 1868c4eff4..d4a958f8e3 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -5917,7 +5917,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
-	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+	if (RELATION_IS_TEMP(heap) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 345c7425f6..e3a3836b3d 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -28,6 +28,7 @@
 #include "catalog/catalog.h"
 #include "catalog/heap.h"
 #include "catalog/index.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
@@ -230,6 +231,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temporary table in current backend */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 862f18a92f..8c314b584a 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2892,6 +2892,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 73494002ad..eb528bdcf9 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3410,17 +3410,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; }
@@ -11675,19 +11669,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 d451f055f7..63e67f494a 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -82,6 +82,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);
 
 
 /*
@@ -3665,3 +3666,53 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * Like function isQueryUsingTempRelation_walker
+ * return true if any relation underlying
+ * the query is a global temporary table.
+ */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* Check if the query uses global temporary table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 9dd30370da..a67823df93 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -457,6 +457,13 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	/*
+	 * If a sequence is bound to a global temporary table, then the sequence
+	 * must been "global temporary"
+	 */
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
@@ -3357,6 +3364,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		cxt.isforeign = false;
 	}
 	cxt.relation = stmt->relation;
+	/* Sets the table persistence to the context */
+	cxt.relation->relpersistence = RelationGetRelPersistence(rel);
 	cxt.rel = rel;
 	cxt.inhRelations = NIL;
 	cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index a799544738..fbd0351283 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2158,6 +2158,14 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/*
+			 * Aotuvacuum cannot vacuum the private data stored in each backend
+			 * that belongs to global temporary table, so skip them.
+			 */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2224,7 +2232,7 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		if (RelpersistenceTsTemp(classForm->relpersistence))
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 0c5b87864b..51bee4eea0 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -2944,6 +2945,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * Returns 0 if this global temporary table is not initialized in current
+	 * backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 47847563ef..1fb33ce4a3 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -23,6 +23,7 @@
 #include "access/syncscan.h"
 #include "access/twophase.h"
 #include "access/xlogprefetch.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -152,6 +153,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
@@ -225,6 +227,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* For global temporary table shared hashtable */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index bf776286de..b949cdc362 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -65,6 +65,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -5078,3 +5079,78 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temporary table.
+ */
+int
+list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct *arrayP = procArray;
+	TransactionId result = InvalidTransactionId;
+	int			index;
+	uint8			flags = 0;
+	int			i = 0;
+
+	/* return 0 if feature is disabled */
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+		*n = 0;
+	}
+
+	/* Disable in standby node */
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		uint8           statusFlags = ProcGlobal->statusFlags[index];
+
+		if (statusFlags & flags)
+			continue;
+
+		/* Fetch all backend that is belonging to MyDatabaseId */
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->backend_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->backend_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->backend_gtt_frozenxid, result))
+				result = proc->backend_gtt_frozenxid;
+
+			/* save backend pid and backend level oldest relfrozenxid */
+			if (max_size > 0)
+			{
+				pids[i] = proc->pid;
+				xids[i] = proc->backend_gtt_frozenxid;
+				i++;
+			}
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (max_size > 0)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 975d547f34..09b3fa63de 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -175,7 +175,9 @@ static const char *const BuiltinTrancheNames[] = {
 	/* LWTRANCHE_PARALLEL_APPEND: */
 	"ParallelAppend",
 	/* LWTRANCHE_PER_XACT_PREDICATE_LIST: */
-	"PerXactPredicateList"
+	"PerXactPredicateList",
+	/* LWTRANCHE_GTT_CTL */
+	"GlobalTempTableControl"
 };
 
 StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 692f21ef6a..b4f572c590 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -393,6 +393,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;	/* init backend level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
@@ -578,6 +579,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;	/* init backend level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index da1a879f1f..d4d40a0302 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -982,6 +982,13 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/*
+			 * For global temporary table ,each backend has its own storage,
+			 * also only sees its own storage. Use Backendid to identify them.
+			 */
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 0963e2701c..70c5e52ff1 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -108,6 +108,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -5115,12 +5116,26 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								/* For global temporary table, get statistic data from localhash */
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -5368,15 +5383,28 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6799,6 +6827,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;
@@ -6816,6 +6845,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6827,6 +6864,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;
@@ -6842,6 +6881,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7760,6 +7807,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))
@@ -7772,6 +7821,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7784,6 +7842,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.
@@ -7803,6 +7863,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 6bba5f8ec4..fa81808df6 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -3113,6 +3114,19 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		/* For global temporary table, get statistic data from localhash */
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 29702d6eab..76d1cc3872 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -66,6 +66,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1117,6 +1118,28 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+
+				/* For global temporary table, get relstat data from localhash */
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+
+				/* And put them to local relcache */
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1171,6 +1194,8 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			/* The state of the global temporary table's index may need to be set */
+			gtt_fix_index_backend_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -1298,7 +1323,22 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+			/*
+			 * For global temporary table, get the latest relfilenode
+			 * from localhash and put it in relcache.
+			 */
+			if (OidIsValid(newrelnode) &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2240,6 +2280,9 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		/* The state of the global temporary table's index may need to be set */
+		gtt_fix_index_backend_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3467,6 +3510,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3576,28 +3623,38 @@ void
 RelationSetNewRelfilenode(Relation relation, char persistence)
 {
 	Oid			newrelfilenode;
-	Relation	pg_class;
-	HeapTuple	tuple;
+	Relation	pg_class = NULL;
+	HeapTuple	tuple = NULL;
 	Form_pg_class classform;
 	MultiXactId minmulti = InvalidMultiXactId;
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
+	/*
+	 * For global temporary table, storage information for the table is
+	 * maintained locally, not in catalog.
+	 */
+	bool		update_catalog = !RELATION_IS_GLOBAL_TEMP(relation);
 
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
 
-	/*
-	 * Get a writable copy of the pg_class tuple for the given relation.
-	 */
-	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+	memset(&classform, 0, sizeof(classform));
 
-	tuple = SearchSysCacheCopy1(RELOID,
-								ObjectIdGetDatum(RelationGetRelid(relation)));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "could not find tuple for relation %u",
-			 RelationGetRelid(relation));
-	classform = (Form_pg_class) GETSTRUCT(tuple);
+	if (update_catalog)
+	{
+		/*
+		 * Get a writable copy of the pg_class tuple for the given relation.
+		 */
+		pg_class = table_open(RelationRelationId, RowExclusiveLock);
+
+		tuple = SearchSysCacheCopy1(RELOID,
+									ObjectIdGetDatum(RelationGetRelid(relation)));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u",
+				 RelationGetRelid(relation));
+		classform = (Form_pg_class) GETSTRUCT(tuple);
+	}
 
 	/*
 	 * Schedule unlinking of the old storage at transaction commit.
@@ -3623,7 +3680,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;
@@ -3643,6 +3700,18 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 			break;
 	}
 
+	/* For global temporary table */
+	if (!update_catalog)
+	{
+		Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+		Assert(RELATION_IS_GLOBAL_TEMP(relation));
+		Assert(!RelationIsMapped(relation));
+
+		/* Make cache invalid and set new relnode to local cache. */
+		CacheInvalidateRelcache(relation);
+		relation->rd_node.relNode = relnode;
+	}
 	/*
 	 * If we're dealing with a mapped index, pg_class.relfilenode doesn't
 	 * change; instead we have to send the update to the relation mapper.
@@ -3652,7 +3721,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	 * possibly-inaccurate values of relpages etc, but those will be fixed up
 	 * later.
 	 */
-	if (RelationIsMapped(relation))
+	else if (RelationIsMapped(relation))
 	{
 		/* This case is only supported for indexes */
 		Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3698,9 +3767,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 		CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 	}
 
-	heap_freetuple(tuple);
+	if (update_catalog)
+	{
+		heap_freetuple(tuple);
 
-	table_close(pg_class, RowExclusiveLock);
+		table_close(pg_class, RowExclusiveLock);
+	}
 
 	/*
 	 * Make the pg_class row change or relation map change visible.  This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index b130874bdc..cd53576768 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -45,6 +45,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "commands/prepare.h"
 #include "commands/trigger.h"
@@ -152,6 +153,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temporary table feature.
+ * table schema are still saved in catalog.
+ *
+ * num > 0 means allows the database to manage multiple active tables at the same time.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2151,6 +2164,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 "
@@ -2699,6 +2721,16 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"vacuum_gtt_defer_check_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("The defer check age of GTT, used to check expired data after vacuum."),
+			NULL
+		},
+		&vacuum_gtt_defer_check_age,
+		10000, 0, 1000000,
+		NULL, NULL, NULL
+	},
+
 	/*
 	 * See also CheckRequiredParameterValues() if this parameter changes
 	 */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 391947340f..0a46ac2f0f 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2522,6 +2522,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
 		dopt->no_unlogged_table_data)
 		return;
 
+	/* Don't dump data in global temporary table/sequence */
+	if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		return;
+
 	/* Check that the data is not explicitly excluded */
 	if (simple_oid_list_member(&tabledata_exclude_oids,
 							   tbinfo->dobj.catId.oid))
@@ -15922,6 +15926,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
 		char	   *foreign = "";
+		char	   *table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15975,9 +15980,15 @@ dumpTableSchema(Archive *fout, const 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);
 
@@ -16341,6 +16352,15 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 			}
 		}
 
+		/*
+		 * Transaction information for the global temporary table is not stored
+		 * in the pg_class.
+		 */
+		if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			Assert(tbinfo->frozenxid == 0);
+			Assert(tbinfo->minmxid == 0);
+		}
 		/*
 		 * In binary_upgrade mode, arrange to restore the old relfrozenxid and
 		 * relminmxid of all vacuumable relations.  (While vacuum.c processes
@@ -16348,7 +16368,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		 * of other relations; so this "if" lacks RELKIND_TOASTVALUE, and the
 		 * child toast table is handled below.)
 		 */
-		if (dopt->binary_upgrade &&
+		else if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
 			 tbinfo->relkind == RELKIND_MATVIEW))
 		{
@@ -17373,6 +17393,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	PQExpBuffer query = createPQExpBuffer();
 	PQExpBuffer delqry = createPQExpBuffer();
 	char	   *qseqname;
+	bool	   global_temp_seq = false;
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
@@ -17382,9 +17403,12 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
-						  "FROM pg_catalog.pg_sequence "
-						  "WHERE seqrelid = '%u'::oid",
+						  "seqcache, seqcycle, "
+						  "c.relpersistence "
+						  "FROM pg_catalog.pg_sequence s, "
+						  "pg_catalog.pg_class c "
+						  "WHERE seqrelid = '%u'::oid "
+						  "and s.seqrelid = c.oid",
 						  tbinfo->dobj.catId.oid);
 	}
 	else if (fout->remoteVersion >= 80400)
@@ -17429,6 +17453,9 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
 
+	if (fout->remoteVersion >= 140000)
+		global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
 	if (strcmp(seqtype, "smallint") == 0)
@@ -17506,9 +17533,13 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	}
 	else
 	{
-		appendPQExpBuffer(query,
-						  "CREATE SEQUENCE %s\n",
-						  fmtQualifiedDumpable(tbinfo));
+		appendPQExpBuffer(query, "CREATE ");
+
+		if (global_temp_seq)
+			appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+		appendPQExpBuffer(query, "SEQUENCE %s\n",
+								  fmtQualifiedDumpable(tbinfo));
 
 		if (strcmp(seqtype, "bigint") != 0)
 			appendPQExpBuffer(query, "    AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 1c1c908664..3bcd6791fc 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -87,7 +87,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -176,7 +176,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 5d9a26cf82..2de11d5d70 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -447,8 +449,17 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
 			 "         ON c.relnamespace = n.oid "
 			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
+			 CppAsString2(RELKIND_MATVIEW) ") AND ");
+
+	if (skip_gtt)
+	{
+		/* exclude global temp tables */
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+			"    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND ");
+	}
+
 	/* exclude possible orphaned temp tables */
+	snprintf(query + strlen(query), sizeof(query) - strlen(query),
 			 "    ((n.nspname !~ '^pg_temp_' AND "
 			 "      n.nspname !~ '^pg_toast_temp_' AND "
 			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index e23b8ca88d..729a9c61e8 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -407,7 +407,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -638,7 +638,10 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+			/* exclude global temp tables */
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -649,7 +652,10 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+		/* exclude global temp tables */
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 919a7849fd..486d01fcae 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -388,7 +388,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index fdc2a89085..da0f0f3d6e 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -4067,7 +4067,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index d34271e3b8..d0fec3959e 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1069,6 +1069,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2502,6 +2504,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2710,6 +2715,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 6ce480b49c..a0ccfb3d77 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -59,7 +59,8 @@ extern Relation heap_create(const char *relname,
 							bool mapped_relation,
 							bool allow_system_table_mods,
 							TransactionId *relfrozenxid,
-							MultiXactId *relminmxid);
+							MultiXactId *relminmxid,
+							bool skip_create_storage);
 
 extern Oid	heap_create_with_catalog(const char *relname,
 									 Oid relnamespace,
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 3e37729436..67652c21b5 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -175,6 +175,7 @@ DECLARE_INDEX(pg_class_tblspc_relfilenode_index, 3455, on pg_class using btree(r
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index f4957653ae..b119d27dab 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5669,6 +5669,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '9874',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '9875',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '9876',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '9877',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 0ab32b44e9..92e9f8ba48 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,7 +22,7 @@
 /* GUC variables */
 extern int	wal_skip_threshold;
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationPreTruncate(Relation rel);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000000..d48162c6b8
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,46 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern int		vacuum_gtt_defer_check_age;
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Oid relid,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void force_enable_gtt_index(Relation index);
+extern void gtt_fix_index_backend_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+extern void gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index 40544dd4c7..7b66d808fc 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 8336c2c5a2..bddcfe7256 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index c86ccdaf60..6b395551c1 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -399,6 +399,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index a8f052e484..4b4ed1a13a 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -189,6 +189,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_SHARED_TIDBITMAP,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_PER_XACT_PREDICATE_LIST,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 2fd1ff09a7..7ccb4dd46a 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -157,6 +157,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId backend_gtt_frozenxid;	/* backend level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index b01fa52139..8efffa55ac 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -94,4 +94,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 7894940741..762b0f0f75 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -283,6 +283,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 34e25eb597..e7f92ecf49 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	struct SMgrRelationData *rd_smgr;	/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -306,6 +306,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -577,11 +578,13 @@ typedef struct ViewOptions
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -589,6 +592,7 @@ typedef struct ViewOptions
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -601,6 +605,30 @@ typedef struct ViewOptions
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP_ON_CURRENT_SESSION
+ *		Test a rel is either local temp relation of this session
+ *		or global temp relation.
+ */
+#define RELATION_IS_TEMP_ON_CURRENT_SESSION(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is local temp relation or global temporary relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RelpersistenceTsTemp
+ *		Test a relpersistence is local temp relation or global temporary relation.
+ */
+#define RelpersistenceTsTemp(relpersistence) \
+	(relpersistence == RELPERSISTENCE_TEMP || \
+	 relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -646,6 +674,19 @@ typedef struct ViewOptions
 	 (relation)->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&	\
 	 !IsCatalogRelation(relation))
 
+/* For global temporary table */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/* Get on commit clause value only for global temporary table */
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+/* Get relpersistence for relation */
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000000..ca2d135056
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,14 @@
+reset search_path;
+select pg_sleep(5);
+ pg_sleep 
+----------
+ 
+(1 row)
+
+drop schema gtt cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
+drop cascades to table gtt.gtt_with_seq
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000000..0ac9e228d2
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,381 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+ n 
+---
+ 9
+(1 row)
+
+commit;
+-- 0 row
+select * from gtt6;
+ n 
+---
+(0 rows)
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temporary table
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temporary table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  could not create global temporary table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR:  insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count 
+-------
+     1
+(1 row)
+
+select count(*) from orders;
+ count 
+-------
+     0
+(1 row)
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  2 |  3
+(2 rows)
+
+commit;
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+select * from gtt_s_2 order by c1;
+ c1 | c2 
+----+----
+  1 |  2
+  2 |  4
+(2 rows)
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 1)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 300)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 33 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test_new
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.gtt6
+drop cascades to table gtt_function.foo
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to sequence gtt_function.seq_1
+drop cascades to table gtt_function.gtt_s_1
+drop cascades to table gtt_function.gtt_s_2
+drop cascades to table gtt_function.gt1
+drop cascades to table gtt_function.gtt_test1
+drop cascades to table gtt_function.gtt_test2
+drop cascades to table gtt_function.gtt_test3
+drop cascades to table gtt_function.gtt_test4
+drop cascades to table gtt_function.gtt_test5
+drop cascades to table gtt_function.gtt_test6
+drop cascades to table gtt_function.gtt_test7
+drop cascades to table gtt_function.gtt_test8
+drop cascades to table gtt_function.gtt_test9
+drop cascades to table gtt_function.gtt_test10
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000000..0646aaed73
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,90 @@
+set search_path=gtt,sys;
+select nextval('gtt_with_seq_c2_seq');
+ nextval 
+---------
+       1
+(1 row)
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000000..0fccf6b81c
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,343 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+rollback;
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+commit;
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          114688 |                 614400
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |            49152 |         49152 |                  49152
+(2 rows)
+
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2000
+(1 row)
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+ c1 | c2 
+----+----
+  1 |  1
+(1 row)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000000..8c0c376429
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,10 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000000..4420fdbd8f
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,80 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+ gtt_stats  | gtt_pkey  |        1 |         0 |             0
+(2 rows)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+reindex table gtt;
+reindex index gtt_pkey;
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+ gtt_stats  | gtt_pkey  |       30 |     10000 |             0
+(2 rows)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 186e6c966c..0e931b5c4e 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1359,6 +1359,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index a091300857..61170dc454 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -129,3 +129,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000000..2c8e5868d1
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,8 @@
+
+
+reset search_path;
+
+select pg_sleep(5);
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000000..fd8b4d3e3b
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,253 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+commit;
+-- 0 row
+select * from gtt6;
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+commit;
+select * from gtt_s_1 order by c1;
+select * from gtt_s_2 order by c1;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000000..d05745e313
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,44 @@
+
+
+set search_path=gtt,sys;
+
+select nextval('gtt_with_seq_c2_seq');
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000000..81a60392c2
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,154 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+rollback;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+commit;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+select count(*) from gtt_t_kenyon;
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000000..dbe84d1b80
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,19 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000000..d61b0ff828
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,46 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+reindex table gtt;
+
+reindex index gtt_pkey;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
-- 
2.27.0

#310shawn wang
shawn.wang.pg@gmail.com
In reply to: wenjing (#309)
1 attachment(s)
Re: [Proposal] Global temporary tables

wenjing <wjzeng2012@gmail.com> 于2021年4月15日周四 下午3:26写道:

HI Pavel

I added user documentation.
Please give me feedback.

Wenjing

Hi, Wenjing,

I have checked your documentation section and fixed a spelling mistake,
adjusted some sentences for you.
All the modified content is in the new patch, and please check it.

Regards

Shawn

Attachments:

0002-gtt-v47-doc.patchapplication/octet-stream; name=0002-gtt-v47-doc.patchDownload
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index b6cf9adcb2..9f9f697fd8 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -169,32 +169,61 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     <listitem>
      <para>
       If specified, the table is created as a temporary table.
-      Temporary tables are automatically dropped at the end of a
-      session, or optionally at the end of the current transaction
-      (see <literal>ON COMMIT</literal> below).  The default
+      <productname>PostgreSQL</productname> supports two types of temporary tables,
+      Global Temporary table, and Local Temporary table.
+      Optionally, <literal>GLOBAL</literal> or <literal>LOCAL</literal>
+      can be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
+      The default type is the Local Temporary table.
+     </para>
+
+    <variablelist>
+     <varlistentry>
+      <term><literal>Global Temporary Table</literal></term>
+      <listitem>
+       <para>
+        The Global Temporary Table are defined just once and automatically exist
+        (starting with empty contents) in every session that needs them.
+        This means that the definition of a temporary table is persistent
+        and shared between sessions. When a temporary table is created
+        and writes some data, another newly created session also sees this table,
+        and found this table is empty.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>Local Temporary Table</literal></term>
+     <listitem>
+     <para>
+      The Local Temporary Table are automatically dropped at the end of a
+      session, The default
       search_path includes the temporary schema first and so identically
       named existing permanent tables are not chosen for new plans
       while the temporary table exists, unless they are referenced
       with schema-qualified names. Any indexes created on a temporary
       table are automatically temporary as well.
      </para>
+     </listitem>
+     </varlistentry>
+    </variablelist>
 
-     <para>
-      The <link linkend="autovacuum">autovacuum daemon</link> cannot
-      access and therefore cannot vacuum or analyze temporary tables.
-      For this reason, appropriate vacuum and analyze operations should be
-      performed via session SQL commands.  For example, if a temporary
-      table is going to be used in complex queries, it is wise to run
-      <command>ANALYZE</command> on the temporary table after it is populated.
-     </para>
+    <para>
+     The <link linkend="autovacuum">autovacuum daemon</link> cannot
+     access and therefore cannot vacuum or analyze temporary tables.
+     For this reason, appropriate vacuum and analyze operations should be
+     performed via session SQL commands.  For example, if a temporary
+     table is going to be used in complex queries, it is wise to run
+     <command>ANALYZE</command> on the temporary table after it is populated.
+    </para>
+    <para>
+     The data stored in a temporary table can be cleaned up at the
+     end of the current transaction. (see <literal>ON COMMIT</literal> below).
+    </para>
+    <para>
+     The Temporary Table resembles the SQL standard, but has some differences.
+     see <xref linkend="sql-createtable-compatibility"/> below.
+    </para>
 
-     <para>
-      Optionally, <literal>GLOBAL</literal> or <literal>LOCAL</literal>
-      can be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
-      This presently makes no difference in <productname>PostgreSQL</productname>
-      and is deprecated; see
-      <xref linkend="sql-createtable-compatibility"/> below.
-     </para>
     </listitem>
    </varlistentry>
 
@@ -2125,13 +2154,15 @@ CREATE TABLE cities_partdef
    <title>Temporary Tables</title>
 
    <para>
-    Although the syntax of <literal>CREATE TEMPORARY TABLE</literal>
-    resembles that of the SQL standard, the effect is not the same.  In the
-    standard,
-    temporary tables are defined just once and automatically exist (starting
-    with empty contents) in every session that needs them.
-    <productname>PostgreSQL</productname> instead
-    requires each session to issue its own <literal>CREATE TEMPORARY
+    Although the syntax of <literal>CREATE [ GLOBAL | LOCAL ] TEMPORARY TABLE</literal>
+    same as that of the SQL standard, Local Temporary Table does not.
+   </para>
+
+   <para>
+    First, in the standard, temporary tables are defined just once and
+    automatically exist (starting with empty contents) in every session
+    that needs them. The Local Temporary Table instead
+    requires each session to issue its own <literal>CREATE LOCAL TEMPORARY
     TABLE</literal> command for each temporary table to be used.  This allows
     different sessions to use the same temporary table name for different
     purposes, whereas the standard's approach constrains all instances of a
@@ -2139,37 +2170,22 @@ CREATE TABLE cities_partdef
    </para>
 
    <para>
-    The standard's definition of the behavior of temporary tables is
-    widely ignored.  <productname>PostgreSQL</productname>'s behavior
-    on this point is similar to that of several other SQL databases.
-   </para>
-
-   <para>
-    The SQL standard also distinguishes between global and local temporary
-    tables, where a local temporary table has a separate set of contents for
+    Second, a local temporary table has a separate set of contents for
     each SQL module within each session, though its definition is still shared
     across sessions.  Since <productname>PostgreSQL</productname> does not
     support SQL modules, this distinction is not relevant in
     <productname>PostgreSQL</productname>.
    </para>
 
-   <para>
-    For compatibility's sake, <productname>PostgreSQL</productname> will
-    accept the <literal>GLOBAL</literal> and <literal>LOCAL</literal> keywords
-    in a temporary table declaration, but they currently have no effect.
-    Use of these keywords is discouraged, since future versions of
-    <productname>PostgreSQL</productname> might adopt a more
-    standard-compliant interpretation of their meaning.
-   </para>
-
    <para>
     The <literal>ON COMMIT</literal> clause for temporary tables
-    also resembles the SQL standard, but has some differences.
+    has some differences with the SQL standard.
     If the <literal>ON COMMIT</literal> clause is omitted, SQL specifies that the
     default behavior is <literal>ON COMMIT DELETE ROWS</literal>.  However, the
     default behavior in <productname>PostgreSQL</productname> is
     <literal>ON COMMIT PRESERVE ROWS</literal>.  The <literal>ON COMMIT
-    DROP</literal> option does not exist in SQL.
+    DROP</literal> option does not exist in SQL and is not supported by
+    Global Temporary Table.
    </para>
   </refsect2>
 
#311wenjing
wjzeng2012@gmail.com
In reply to: shawn wang (#310)
2 attachment(s)
Re: [Proposal] Global temporary tables

shawn wang <shawn.wang.pg@gmail.com> 于2021年4月15日周四 下午4:49写道:

wenjing <wjzeng2012@gmail.com> 于2021年4月15日周四 下午3:26写道:

HI Pavel

I added user documentation.
Please give me feedback.

Wenjing

Hi, Wenjing,

I have checked your documentation section and fixed a spelling mistake,
adjusted some sentences for you.
All the modified content is in the new patch, and please check it.

Thank you for your comments.
I made some repairs and fixed a bug.
Looking forward to your feedback.

Wenjing

Show quoted text

Regards

Shawn

Attachments:

0002-gtt-v48-doc.patchapplication/octet-stream; name=0002-gtt-v48-doc.patchDownload
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index a8c5e4028af..40261a44bcd 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -169,32 +169,67 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     <listitem>
      <para>
       If specified, the table is created as a temporary table.
-      Temporary tables are automatically dropped at the end of a
-      session, or optionally at the end of the current transaction
-      (see <literal>ON COMMIT</literal> below).  The default
-      search_path includes the temporary schema first and so identically
-      named existing permanent tables are not chosen for new plans
+      Optionally, <literal>GLOBAL</literal> or <literal>LOCAL</literal>
+      can be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
+      They represent two types of temporary tables supported by <productname>PostgreSQL</productname>:
+      global temporary table and local temporary table. Without specified
+      GLOBAL or LOCAL, a local temporary table is created by default.
+     </para>
+
+    <para>
+     Both types of temporary tables’ data are truncated at the
+     end of a session or optionally at the end of the current transaction.
+     (see <literal>ON COMMIT</literal> below). For global temporary table,
+     its schema is reserved and reused by future sessions or transactions.
+     For local temporary table, both its data and its schema are dropped.
+    </para>
+
+    <variablelist>
+     <varlistentry>
+      <term><literal>Global Temporary Table</literal></term>
+      <listitem>
+       <para>
+        Global temporary table are defined just once and automatically exist
+        (starting with empty contents) in every session that needs them.
+        The schema definition of temporary tables is persistent and shared among sessions.
+        However, the data in temporary tables are kept private to sessions themselves,
+        even though they use same name and same schema.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>Local Temporary Table</literal></term>
+     <listitem>
+     <para>
+      Local temporary table are automatically dropped at the end of a
+      session (include schema and data). Future sessions need to create
+      their own temporary tables when they are used.
+     </para>
+     <para>
+      The default search_path includes the temporary schema first and so
+      identically named existing permanent tables are not chosen for new plans
       while the temporary table exists, unless they are referenced
       with schema-qualified names. Any indexes created on a temporary
       table are automatically temporary as well.
      </para>
+     </listitem>
+     </varlistentry>
+    </variablelist>
 
-     <para>
-      The <link linkend="autovacuum">autovacuum daemon</link> cannot
-      access and therefore cannot vacuum or analyze temporary tables.
-      For this reason, appropriate vacuum and analyze operations should be
-      performed via session SQL commands.  For example, if a temporary
-      table is going to be used in complex queries, it is wise to run
-      <command>ANALYZE</command> on the temporary table after it is populated.
-     </para>
+    <para>
+     The <link linkend="autovacuum">autovacuum daemon</link> cannot
+     access and therefore cannot vacuum or analyze temporary tables.
+     For this reason, appropriate vacuum and analyze operations should be
+     performed via session SQL commands.  For example, if a temporary
+     table is going to be used in complex queries, it is wise to run
+     <command>ANALYZE</command> on the temporary table after it is populated.
+    </para>
+    <para>
+     The Temporary table resembles the SQL standard, but has some differences.
+     see <xref linkend="sql-createtable-compatibility"/> below.
+    </para>
 
-     <para>
-      Optionally, <literal>GLOBAL</literal> or <literal>LOCAL</literal>
-      can be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
-      This presently makes no difference in <productname>PostgreSQL</productname>
-      and is deprecated; see
-      <xref linkend="sql-createtable-compatibility"/> below.
-     </para>
     </listitem>
    </varlistentry>
 
@@ -2125,13 +2160,17 @@ CREATE TABLE cities_partdef
    <title>Temporary Tables</title>
 
    <para>
-    Although the syntax of <literal>CREATE TEMPORARY TABLE</literal>
-    resembles that of the SQL standard, the effect is not the same.  In the
-    standard,
-    temporary tables are defined just once and automatically exist (starting
-    with empty contents) in every session that needs them.
-    <productname>PostgreSQL</productname> instead
-    requires each session to issue its own <literal>CREATE TEMPORARY
+    Although the syntax of <literal>CREATE GLOBAL/LOCAL TEMPORARY TABLE</literal>
+    resembles that of the SQL standard, the effect is not the same.
+    The global temporary table follows the SQL standards while local temporary
+    table does not.
+   </para>
+
+   <para>
+    First, in the standard, both global and local temporary tables are defined just
+    once and automatically exist (starting with empty contents) in every session
+    that needs them. For local temporary tables, <productname>PostgreSQL</productname>
+    instead requires each session to issue its own <literal>CREATE LOCAL TEMPORARY
     TABLE</literal> command for each temporary table to be used.  This allows
     different sessions to use the same temporary table name for different
     purposes, whereas the standard's approach constrains all instances of a
@@ -2139,29 +2178,14 @@ CREATE TABLE cities_partdef
    </para>
 
    <para>
-    The standard's definition of the behavior of temporary tables is
-    widely ignored.  <productname>PostgreSQL</productname>'s behavior
-    on this point is similar to that of several other SQL databases.
-   </para>
-
-   <para>
-    The SQL standard also distinguishes between global and local temporary
+    Second, the SQL standard distinguishes between global and local temporary
     tables, where a local temporary table has a separate set of contents for
     each SQL module within each session, though its definition is still shared
-    across sessions.  Since <productname>PostgreSQL</productname> does not
+    across sessions. Since <productname>PostgreSQL</productname> does not
     support SQL modules, this distinction is not relevant in
     <productname>PostgreSQL</productname>.
    </para>
 
-   <para>
-    For compatibility's sake, <productname>PostgreSQL</productname> will
-    accept the <literal>GLOBAL</literal> and <literal>LOCAL</literal> keywords
-    in a temporary table declaration, but they currently have no effect.
-    Use of these keywords is discouraged, since future versions of
-    <productname>PostgreSQL</productname> might adopt a more
-    standard-compliant interpretation of their meaning.
-   </para>
-
    <para>
     The <literal>ON COMMIT</literal> clause for temporary tables
     also resembles the SQL standard, but has some differences.
@@ -2169,7 +2193,8 @@ CREATE TABLE cities_partdef
     default behavior is <literal>ON COMMIT DELETE ROWS</literal>.  However, the
     default behavior in <productname>PostgreSQL</productname> is
     <literal>ON COMMIT PRESERVE ROWS</literal>.  The <literal>ON COMMIT
-    DROP</literal> option does not exist in SQL.
+    DROP</literal> option does not exist in SQL and is not supported by
+    global temporary table.
    </para>
   </refsect2>
 
-- 
2.24.3 (Apple Git-128)

0001-gtt-v48.patchapplication/octet-stream; name=0001-gtt-v48.patchDownload
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 5554275e64..1d3e992cb8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -168,6 +168,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * In order to avoid consistency problems, the global temporary table
+	 * uses ShareUpdateExclusiveLock.
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temporary table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},
 	/* list terminator */
 	{{NULL}}
 };
@@ -1817,6 +1830,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1961,11 +1976,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
-	/*
-	 * autovacuum_enabled, autovacuum_analyze_threshold and
-	 * autovacuum_analyze_scale_factor are supported for partitioned tables.
-	 */
-
 	return default_reloptions(reloptions, validate, RELOPT_KIND_PARTITIONED);
 }
 
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index 8dcd53c457..3bef4a163c 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1024,7 +1024,7 @@ gistproperty(Oid index_oid, int attno,
 XLogRecPtr
 gistGetFakeLSN(Relation rel)
 {
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	if (RELATION_IS_TEMP(rel))
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 0752fb38a9..5c85d777f4 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -151,7 +151,7 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	if (!RELATION_IS_TEMP(index))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 7a9a640989..67fcc75438 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -591,7 +591,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
@@ -644,7 +644,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 c3fc12d76c..3c7f969724 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -63,6 +63,7 @@
 #include "access/xlog.h"
 #include "catalog/index.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -511,6 +512,14 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
 	Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
+	/*
+	 * not every AM requires these to be valid, but regular heap does.
+	 * Transaction information for the global temp table will be stored
+	 * in the local hash table, not the catalog.
+	 */
+	Assert(RELATION_IS_GLOBAL_TEMP(rel) ^ TransactionIdIsNormal(rel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(rel) ^ MultiXactIdIsValid(rel->rd_rel->relminmxid));
+
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
 	{
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 706e16ae94..1cbd83d802 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -677,6 +678,14 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		/*
+		 * If a global temporary table storage file is not initialized in the
+		 * current backend, its index does not have a root page, just returns 0.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 5fcd004e1b..58b994cef5 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -212,7 +212,8 @@ Boot_CreateStmt:
 												   mapped_relation,
 												   true,
 												   &relfrozenxid,
-												   &relminmxid);
+												   &relminmxid,
+												   false);
 						elog(DEBUG4, "bootstrap relation created");
 					}
 					else
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 69f9dd51a7..9ed7dd3b3d 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -43,6 +43,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/README.gtt b/src/backend/catalog/README.gtt
new file mode 100644
index 0000000000..721f25a86d
--- /dev/null
+++ b/src/backend/catalog/README.gtt
@@ -0,0 +1,166 @@
+Global Temporary Table(GTT)
+==============
+
+Feature description
+--------------------------------
+
+Previously, temporary tables are defined once and automatically
+created (starting with empty contents) in every session before using them.
+
+The temporary table implementation in PostgreSQL, known as Local temp tables(LTT),
+did not fully comply with the SQL standard. This version added the support of
+Global Temporary Table .
+
+The metadata of Global Temporary Table is persistent and shared among sessions.
+The data stored in the Global temporary table is independent of sessions. This
+means, when a session creates a Global Temporary Table and writes some data.
+Other sessions cannot see those data, but they have an empty Global Temporary
+Table with same schema.
+
+Like local temporary table, Global Temporary Table supports ON COMMIT PRESERVE ROWS
+or ON COMMIT DELETE ROWS clause, so that data in the temporary table can be
+cleaned up or reserved automatically when a session exits or a transaction COMMITs.
+
+Unlike Local Temporary Table, Global Temporary Table does not support
+ON COMMIT DROP clauses.
+
+In following paragraphs, we use GTT for Global Temporary Table and LTT for
+local temporary table.
+
+Main design ideas
+-----------------------------------------
+
+STORAGE & BUFFER
+
+In general, GTT and LTT use the same storage and buffer design and
+implementation. The storage files for both types of temporary tables are named
+as t_backendid_relfilenode, and the local buffer is used to cache the data.
+
+The schema of GTTs is shared among sessions while their data are not. We build
+a new mechanisms to manage those non-shared data and their statistics.
+Here is the summary of changes:
+
+1) CATALOG
+GTTs store session-specific data. The storage information of GTTs'data, their
+transaction information, and their statistics are not stored in the catalog.
+
+2) STORAGE INFO & STATISTICS & TRANSACTION
+In order to maintain durability and availability of GTTs'session-specific data,
+their storage information, statistics, and transaction information is managed
+in a local hash table tt_storage_local_hash.
+
+3) DDL
+A shared hash table active_gtt_shared_hash is added to track the state of the
+GTT in a different session. This information is recorded in the hash table
+during the DDL execution of the GTT.
+
+4) LOCK
+The data stored in a GTT can only be modified or accessed by owning session.
+The statements that only modify data in a GTT do not need a high level of table
+locking.
+Changes to the GTT's metadata affect all sessions.
+The operations making those changes include truncate GTT, Vacuum/Cluster GTT,
+and Lock GTT.
+
+Detailed design
+-----------------------------------------
+
+1. CATALOG
+1.1 relpersistence
+define RELPERSISTENCEGLOBALTEMP 'g'
+Mark Global Temporary Table in pg_class relpersistence to 'g'. The relpersistence
+of indexes created on the GTT, sequences on GTT and toast tables on GTT are
+also set to 'g'
+
+1.2 on commit clause
+LTT's status associated with on commit DELETE ROWS and on commit PRESERVE ROWS
+is not stored in catalog. Instead, GTTs need a bool value on_commit_delete_rows
+in reloptions which is shared among sessions.
+
+1.3 gram.y
+GTT is already supported in syntax tree. We remove the warning message
+"GLOBAL is deprecated in temporary table creation" and mark
+relpersistence = RELPERSISTENCEGLOBALTEMP.
+
+2. STORAGE INFO & STATISTICS DATA & TRANSACTION INFO
+2.1. gtt_storage_local_hash
+Each backend creates a local hashtable gtt_storage_local_hash to track a GTT's
+storage file information, statistics, and transaction information.
+
+2.2 GTT storage file info track
+1) When one session inserts data into a GTT for the first time, record the
+storage info to gtt_storage_local_hash.
+2) Use beforeshmemexit to ensure that all files of session GTT are deleted when
+the session exits.
+
+2.3 statistics info
+1) relpages reltuples relallvisible
+2) The statistics of each column from pg_statistic
+All the above information is stored in gtt_storage_local_hash.
+When doing vacuum or analyze, GTT's statistic is updated, which is used by
+the SQL planner.
+The statistics summarizes only data in the current session.
+
+2.3 transaction info track
+frozenxid minmulti from pg_class is stored to gtt_storage_local_hash.
+
+4 DDL
+4.1. active_gtt_shared_hash
+This is the hash table created in shared memory to trace the GTT files initialized
+in each session. Each hash entry contains a bitmap that records the backendid of
+the initialized GTT file. With this hash table, we know which backend/session
+is using this GTT. Such information is used during GTT's DDL operations.
+
+4.1 DROP GTT
+One GTT is allowed to be deleted when there is only one session using the table
+and the session is the current session.
+After holding the AccessExclusiveLock lock on GTT, active_gtt_shared_hash
+is checked to ensure that.
+
+4.2 ALTER GTT/DROP INDEX ON GTT
+Same as drop GTT.
+
+4.3 CREATE INDEX ON GTT
+1) create index on GTT statements build index based on local data in a session.
+2) After the index is created, record the index metadata to the catalog.
+3) Other sessions can enable or disable the local GTT index.
+
+5 LOCK
+
+5.1 TRUNCATE GTT
+The truncate GTT command uses RowExclusiveLock, not AccessExclusiveLock, because
+this command only cleans up local data and local buffers in current session.
+
+5.2 CLUSTER GTT/VACUUM FULL GTT
+Same as truncate GTT.
+
+5.3 Lock GTT
+A lock GTT statement does not hold any table locks.
+
+6 MVCC commit log(clog) cleanup
+
+The GTT storage file contains transaction information. Queries for GTT data rely
+on transaction information such as clog. The transaction information required by
+each session may be completely different. We need to ensure that the transaction
+information of the GTT data is not cleaned up during its lifetime and that
+transaction resources are recycled at the instance level.
+
+6.1 The session level GTT oldest frozenxid
+1) To manage all GTT transaction information, add session level oldest frozenxid
+in each session. When one GTT is created or removed, record the session level
+oldest frozenxid and store it in MyProc.
+2) When vacuum advances the database's frozenxid, session level oldest frozenxid
+should be considered. This is acquired by searching all of MyProc. This way,
+we can avoid the clog required by GTTs to be cleaned.
+
+6.2 vacuum GTT
+Users can perform vacuum over a GTT to clean up local data in the GTT.
+
+6.3 autovacuum GTT
+Autovacuum skips all GTTs, because the data in GTTs is only visible in current
+session.
+
+7 OTHERS
+Parallel query
+Planner does not produce parallel query plans for SQL related to GTT. Because
+GTT private data cannot be accessed across processes.
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 245d536372..a38b594898 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -441,6 +441,7 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index ba03e8aa8f..3dd9d03831 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -62,6 +62,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,
@@ -305,7 +307,8 @@ heap_create(const char *relname,
 			bool mapped_relation,
 			bool allow_system_table_mods,
 			TransactionId *relfrozenxid,
-			MultiXactId *relminmxid)
+			MultiXactId *relminmxid,
+			bool skip_create_storage)
 {
 	bool		create_storage;
 	Relation	rel;
@@ -370,7 +373,9 @@ heap_create(const char *relname,
 	 * storage is already created, so don't do it here.  Also don't create it
 	 * for relkinds without physical storage.
 	 */
-	if (!RELKIND_HAS_STORAGE(relkind) || OidIsValid(relfilenode))
+	if (!RELKIND_HAS_STORAGE(relkind) ||
+		OidIsValid(relfilenode) ||
+		skip_create_storage)
 		create_storage = false;
 	else
 	{
@@ -428,7 +433,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -999,6 +1004,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -1037,8 +1043,21 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/*
+	 * The transaction information of the global temporary table is stored
+	 * in the local hash table, not in catalog.
+	 */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1304,7 +1323,8 @@ heap_create_with_catalog(const char *relname,
 							   mapped_relation,
 							   allow_system_table_mods,
 							   &relfrozenxid,
-							   &relminmxid);
+							   &relminmxid,
+							   false);
 
 	Assert(relid == RelationGetRelid(new_rel_desc));
 
@@ -1411,6 +1431,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1996,6 +2017,19 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/*
+	 * Only when other sessions are not using this Global temporary table,
+	 * is it allowed to DROP it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot drop global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3261,7 +3295,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3273,7 +3307,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3319,8 +3353,16 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		/*
+		 * Truncate global temporary table only clears local data,
+		 * so only low-level locks need to be held.
+		 */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3353,6 +3395,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3361,23 +3404,47 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	/* For global temporary table only */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		/*
+		 * If this GTT is not initialized in current backend, there is
+		 * no needs to anything.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(rel)))
+			return;
+
+		/*
+		 * Truncate GTT only clears local data, so only low-level locks
+		 * need to be held.
+		 */
+		lockmode = RowExclusiveLock;
+	}
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	/*
+	 * After the data is cleaned up on the GTT, the transaction information
+	 * for the data(stored in local hash table) is also need reset.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(RelationGetRelid(rel), 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index a628b3281c..a793be7710 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -55,6 +55,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "commands/defrem.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -732,6 +733,29 @@ index_create(Relation heapRelation,
 	char		relkind;
 	TransactionId relfrozenxid;
 	MultiXactId relminmxid;
+	bool		skip_create_storage = false;
+
+	/* For global temporary table only */
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temporary table with concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot reindex global temporary tables concurrently")));
+
+		/*
+		 * For the case that some backend is applied relcache message to create
+		 * an index on a global temporary table, if this table in the current
+		 * backend are not initialized, the creation of index storage on the
+		 * table are also skipped.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+		{
+			skip_create_storage = true;
+			flags |= INDEX_CREATE_SKIP_BUILD;
+		}
+	}
 
 	/* constraint flags can only be set when a constraint is requested */
 	Assert((constr_flags == 0) ||
@@ -939,7 +963,8 @@ index_create(Relation heapRelation,
 								mapped_relation,
 								allow_system_table_mods,
 								&relfrozenxid,
-								&relminmxid);
+								&relminmxid,
+								skip_create_storage);
 
 	Assert(relfrozenxid == InvalidTransactionId);
 	Assert(relminmxid == InvalidMultiXactId);
@@ -2272,7 +2297,7 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 * lock (see comments in RemoveRelations), and a non-concurrent DROP is
 	 * more efficient.
 	 */
-	Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+	Assert(!RelpersistenceTsTemp(get_rel_persistence(indexId)) ||
 		   (!concurrent && !concurrent_lock_mode));
 
 	/*
@@ -2304,6 +2329,20 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/*
+	 * Allow to drop index on global temporary table when only current
+	 * backend use it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation) &&
+		is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+	{
+		ereport(ERROR,
+			(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+			 errmsg("cannot drop index %s or global temporary table %s",
+					RelationGetRelationName(userIndexRelation), RelationGetRelationName(userHeapRelation)),
+			 errhint("Because the index is created on the global temporary table and other backend attached it.")));
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2912,6 +2951,7 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = RELATION_IS_GLOBAL_TEMP(rel);
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -3006,20 +3046,37 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
-		{
-			rd_rel->relpages = (int32) relpages;
-			dirty = true;
-		}
-		if (rd_rel->reltuples != (float4) reltuples)
+		/* For global temporary table */
+		if (is_gtt)
 		{
-			rd_rel->reltuples = (float4) reltuples;
-			dirty = true;
+			/* Update GTT'statistics into local relcache */
+			rel->rd_rel->relpages = (int32) relpages;
+			rel->rd_rel->reltuples = (float4) reltuples;
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+
+			/* Update GTT'statistics into local hashtable */
+			up_gtt_relstats(RelationGetRelid(rel), relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+		else
 		{
-			rd_rel->relallvisible = (int32) relallvisible;
-			dirty = true;
+			if (rd_rel->relpages != (int32) relpages)
+			{
+				rd_rel->relpages = (int32) relpages;
+				dirty = true;
+			}
+
+			if (rd_rel->reltuples != (float4) reltuples)
+			{
+				rd_rel->reltuples = (float4) reltuples;
+				dirty = true;
+			}
+
+			if (rd_rel->relallvisible != (int32) relallvisible)
+			{
+				rd_rel->relallvisible = (int32) relallvisible;
+				dirty = true;
+			}
 		}
 	}
 
@@ -3132,6 +3189,26 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, progress_index, progress_vals);
 	}
 
+	/* For build index on global temporary table */
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		/*
+		 * If the storage for the index in this session is not initialized,
+		 * it needs to be created.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			/* Before create init storage, fix the local Relcache first */
+			force_enable_gtt_index(indexRelation);
+
+			Assert(gtt_storage_attached(RelationGetRelid(heapRelation)));
+
+			/* Init storage for index */
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3687,6 +3764,20 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	if (!OidIsValid(heapId))
 		return;
 
+	/*
+	 * For reindex on global temporary table, If the storage for the index
+	 * in current backend is not initialized, nothing is done.
+	 */
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		!gtt_storage_attached(indexId))
+	{
+		/* Suppress use of the target index while rebuilding it */
+		SetReindexProcessing(heapId, indexId);
+		/* Re-allow use of target index */
+		ResetReindexProcessing();
+		return;
+	}
+
 	if ((params->options & REINDEXOPT_MISSING_OK) != 0)
 		heapRelation = try_table_open(heapId, ShareLock);
 	else
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 005e029c38..5e59b47969 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -655,6 +655,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/* Do not allow create global temporary table in temporary schemas */
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp table in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index cba7a9ada0..9cf6802f9b 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,6 +27,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -61,6 +62,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			temprelOid;			/* InvalidOid if not a global temporary rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -115,7 +117,7 @@ AddPendingSync(const RelFileNode *rnode)
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -126,7 +128,12 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/*
+		 * Global temporary table and local temporary table use same
+		 * design on storage module.
+		 */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -154,6 +161,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
@@ -165,6 +173,21 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		AddPendingSync(&rnode);
 	}
 
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		Assert(rel && RELATION_IS_GLOBAL_TEMP(rel));
+
+		/*
+		 * Remember the reloid of global temporary table, which is used for
+		 * transaction commit or rollback.
+		 * see smgrDoPendingDeletes.
+		 */
+		pending->temprelOid = RelationGetRelid(rel);
+
+		/* Remember global temporary table storage info to localhash */
+		remember_gtt_storage_info(rnode, rel);
+	}
+
 	return srel;
 }
 
@@ -201,11 +224,20 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/*
+	 * Remember the reloid of global temporary table, which is used for
+	 * transaction commit or rollback.
+	 * see smgrDoPendingDeletes.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->temprelOid = RelationGetRelid(rel);
+
 	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
@@ -602,6 +634,7 @@ smgrDoPendingDeletes(bool isCommit)
 	int			nrels = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -631,14 +664,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->temprelOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -648,12 +685,21 @@ smgrDoPendingDeletes(bool isCommit)
 
 	if (nrels > 0)
 	{
+		int	i;
+
 		smgrdounlinkall(srels, nrels, false);
 
-		for (int i = 0; i < nrels; i++)
+		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* Delete global temporary table info in localhash */
+			if (gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000000..6aec275c73
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1651 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  The body implementation of Global Temparary table.
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *	  See src/backend/catalog/GTT_README for Global temparary table's
+ *	  requirements and design.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_info_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+int		vacuum_gtt_defer_check_age = 0;
+
+/*
+ * The Global temporary table's shared hash table data structure
+ */
+typedef struct gtt_ctl_data
+{
+	LWLock		lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+/* record this global temporary table in which backends are being used */
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+/*
+ * The Global temporary table's local hash table data structure
+ */
+/* Record the storage information and statistical information of the global temporary table */
+typedef struct
+{
+	Oid			relfilenode;
+	Oid			spcnode;
+
+	/* pg_class relstat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+
+	/* pg_statistic column stat */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+
+	Oid			oldrelid;			/* remember the source of relid, before the switch relfilenode. */
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_free_statistics(gtt_relfilenode *rnode);
+static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok);
+static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok);
+static Bitmapset *copy_active_gtt_bitmap(Oid relid);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+/*
+ * Calculate shared hash table entry size for GTT.
+ */
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	/* hash entry header size */
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	/*
+	 * hash entry data size
+	 * this is a bitmap in shared memory, each backend have a bit.
+	 */
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+/*
+ * Calculate shared hash table max size for GTT.
+ */
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	/* shared hash header size */
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	/* hash entry size */
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	/* max size */
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+/*
+ * Initialization shared hash table for GTT.
+ */
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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();
+	}
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(gtt_fnode);
+	info.entrysize = action_gtt_shared_hash_entry_size();
+	active_gtt_shared_hash =
+		ShmemInitHash("active gtt shared hash",
+						gtt_shared_ctl->max_entry,
+						gtt_shared_ctl->max_entry,
+						&info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+/*
+ * Record GTT relid to shared hash table, which means that current backend is using this GTT.
+ */
+static void
+gtt_storage_checkin(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			found;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *)&(fnode), 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_global_temporary_table.")));
+	}
+
+	if (!found)
+	{
+		int			wordnum;
+
+		/* init bitmap */
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	/* record itself in bitmap */
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+/*
+ * Remove the GTT relid record from the shared hash table which means that current backend is
+ * not use this GTT.
+ */
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode				fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	if (!skiplock)
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		if (!skiplock)
+			LWLockRelease(&gtt_shared_ctl->lock);
+
+		if (isCommit)
+			elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+	/* remove itself from bitmap */
+	bms_del_member(entry->map, MyBackendId);
+
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+
+	if (!skiplock)
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+	return;
+}
+
+/*
+ * Gets usage information for a GTT from shared hash table.
+ * The information is in the form of bitmap.
+ * Quickly copy the entire bitmap from shared memory and return it.
+ * that to avoid holding locks for a long time.
+ */
+static Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	Bitmapset	*map_copy = NULL;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		return NULL;
+	}
+
+	Assert(entry->map);
+
+	/* copy the entire bitmap */
+	if (!bms_is_empty(entry->map))
+		map_copy = bms_copy(entry->map);
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return map_copy;
+}
+
+/*
+ * Check if there are other backends using this GTT besides the current backend.
+ */
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool		in_use = false;
+	int			num_use = 0;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		return false;
+	}
+
+	Assert(entry->map);
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+	/* how many backend are using this GTT */
+	num_use = bms_num_members(entry->map);
+	if (num_use == 0)
+		in_use = false;
+	else if (num_use == 1)
+	{
+		/* check if this is itself */
+		if(bms_is_member(MyBackendId, entry->map))
+			in_use = false;
+		else
+			in_use = true;
+	}
+	else
+		in_use = true;
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return in_use;
+}
+
+/*
+ * Record GTT information to local hash.
+ * They include GTT storage info, transaction info and statistical info.
+ */
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+	gtt_local_hash_entry	*entry;
+	MemoryContext			oldcontext;
+	gtt_relfilenode			*new_node = NULL;
+	Oid						relid = RelationGetRelid(rel);
+	int 					natts = 0;
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	/* First time through: initialize the hash table */
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		HASHCTL		ctl;
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_info_context =
+			AllocSetContextCreate(CacheMemoryContext,
+								"gtt info context",
+								ALLOCSET_DEFAULT_SIZES);
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		ctl.hcxt = gtt_info_context;
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+	}
+
+	Assert(CacheMemoryContext);
+	Assert(gtt_info_context);
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	entry = gtt_search_by_relid(relid, true);
+	if (!entry)
+	{
+		bool		found = false;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash,
+						(void *) &relid, HASH_ENTER, &found);
+
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+		entry->oldrelid = InvalidOid;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			/* record the on commit clause */
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			gtt_storage_checkin(relid);
+		}
+	}
+
+	/* record storage info relstat columnstats and transaction info to relfilenode list */
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	new_node->natts = 0;
+	new_node->attnum = NULL;
+	new_node->att_stat_tups = NULL;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* init column stats structure */
+	natts = RelationGetNumberOfAttributes(rel);
+	new_node->attnum = palloc0(sizeof(int) * natts);
+	new_node->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+	new_node->natts = natts;
+
+	/* only heap have transaction info */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+
+		/**/
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+
+	/* Registration callbacks are used to trigger cleanup during process exit */
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+/*
+ * Remove GTT information from local hash when transaction commit/rollback.
+ */
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	gtt_relfilenode *d_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+		return;
+	}
+
+	d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+		else if (entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+			gtt_relfilenode *gttnode2 = NULL;
+
+			/*
+			 * For cluster GTT rollback.
+			 * We need to roll back the exchange relfilenode operation.
+			 */
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+			Assert(gttnode2->relfilenode == rnode.relNode);
+			Assert(list_length(entry->relfilenode_list) == 1);
+			/* rollback switch relfilenode */
+			gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+									   entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+									   false);
+			/* clean up footprint */
+			entry2->oldrelid = InvalidOid;
+
+			/* temp relfilenode need free */
+			d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+			Assert(d_rnode);
+		}
+		else
+		{
+			/* rollback transaction */
+			if (entry->relfilenode_list == NIL)
+			{
+				if (entry->relkind == RELKIND_RELATION ||
+					entry->relkind == RELKIND_SEQUENCE)
+					gtt_storage_checkout(relid, false, isCommit);
+
+				hash_search(gtt_storage_local_hash,
+						(void *) &(relid), HASH_REMOVE, NULL);
+			}
+
+			return;
+		}
+	}
+
+	/* Clean up transaction info from Local order list and MyProc */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+
+		/* this is valid relfrozenxid */
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	/* delete relfilenode from rel entry */
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	gtt_free_statistics(d_rnode);
+
+	if (entry->relfilenode_list == NIL)
+	{
+		/* this means we truncate this GTT at current backend */
+
+		/* tell shared hash that current backend will no longer use this GTT */
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(relid, false, isCommit);
+
+		if (isCommit && entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+
+			/* commit transaction at cluster GTT, need clean up footprint */
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			entry2->oldrelid = InvalidOid;
+		}
+
+		hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_REMOVE, NULL);
+	}
+
+	return;
+}
+
+/*
+ * Check if current backend is using this GTT.
+ */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool found = false;
+	gtt_local_hash_entry *entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (!OidIsValid(relid))
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry)
+		found = true;
+
+	return found;
+}
+
+/*
+ * When backend exit, bulk cleaning all GTT storage and local buffer of this backend.
+ */
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS				status;
+	gtt_local_hash_entry		*entry;
+	SMgrRelation	*srels = NULL;
+	Oid				*relids = NULL;
+	char			*relkinds = NULL;
+	int			nrels = 0,
+				nfiles = 0,
+				maxrels = 0,
+				maxfiles = 0,
+				i = 0;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	/* Search all relfilenode for GTT in current backend */
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel;
+			RelFileNode		rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel = smgropen(rnode, MyBackendId);
+
+			if (maxfiles == 0)
+			{
+				maxfiles = 32;
+				srels = palloc(sizeof(SMgrRelation) * maxfiles);
+			}
+			else if (maxfiles <= nfiles)
+			{
+				maxfiles *= 2;
+				srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+			}
+
+			srels[nfiles++] = srel;
+		}
+
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			relids  = palloc(sizeof(Oid) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			relids  = repalloc(relids , sizeof(Oid) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		relkinds[nrels] = entry->relkind;
+		relids[nrels] = entry->relid;
+		nrels++;
+	}
+
+	/* drop local buffer and storage */
+	if (nfiles > 0)
+	{
+		/* Need to ensure we have a usable transaction. */
+                AbortOutOfAnyTransaction();
+
+		smgrdounlinkall(srels, nfiles, false);
+		for (i = 0; i < nfiles; i++)
+			smgrclose(srels[i]);
+
+		pfree(srels);
+	}
+
+	if (nrels)
+	{
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+		for (i = 0; i < nrels; i++)
+		{
+			/* tell shared hash */
+			if (relkinds[i] == RELKIND_RELATION ||
+				relkinds[i] == RELKIND_SEQUENCE)
+				gtt_storage_checkout(relids[i], true, false);
+		}
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+		pfree(relids);
+		pfree(relkinds);
+	}
+
+	/* set to global area */
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;
+
+	return;
+}
+
+/*
+ * Update GTT relstats(relpage/reltuple/relallvisible)
+ * to local hash.
+ */
+void
+up_gtt_relstats(Oid relid,
+					BlockNumber num_pages,
+					double num_tuples,
+					BlockNumber num_all_visible_pages,
+					TransactionId relfrozenxid,
+					TransactionId relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (!OidIsValid(relid))
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (num_pages > 0 &&
+		gtt_rnode->relpages != (int32)num_pages)
+		gtt_rnode->relpages = (int32)num_pages;
+
+	if (num_tuples > 0 &&
+		gtt_rnode->reltuples != (float4)num_tuples)
+		gtt_rnode->reltuples = (float4)num_tuples;
+
+	/* only heap contain transaction information and relallvisible */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (num_all_visible_pages > 0 &&
+			gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+		{
+			gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+		}
+
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNextTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			/* set to local order list */
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			/* set to global area */
+			set_gtt_session_relfrozenxid();
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search GTT relstats(relpage/reltuple/relallvisible)
+ * from local has.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update GTT info(definition is same as pg_statistic)
+ * to local hash.
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	MemoryContext			oldcontext;
+	int						i = 0;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return;
+
+	Assert(entry->relid == reloid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (gtt_rnode->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	/* switch context to gtt_info_context for store tuple at heap_form_tuple */
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == 0)
+		{
+			gtt_rnode->attnum[i] = attnum;
+			break;
+		}
+		else if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			heap_freetuple(gtt_rnode->att_stat_tups[i]);
+			gtt_rnode->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < gtt_rnode->natts);
+	Assert(gtt_rnode->att_stat_tups[i] == NULL);
+	gtt_rnode->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search GTT statistic info(definition is same as pg_statistic)
+ * from local hash.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	int						i = 0;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return NULL;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return NULL;
+
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			return gtt_rnode->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Insert a RelfrozenXID into the list and keep the list in order.
+ */
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell		*cell;
+	int				i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Remove a RelfrozenXID from order list gtt_session_relfrozenxid_list.
+ */
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+/*
+ * Update of backend Level oldest relfrozenxid to MyProc.
+ * This makes each backend's oldest RelFrozenxID globally visible.
+ */
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->backend_gtt_frozenxid != gtt_frozenxid)
+		MyProc->backend_gtt_frozenxid = gtt_frozenxid;
+}
+
+/*
+ * Get GTT column level data statistics.
+ */
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo 	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate *tupstore;
+	HeapTuple		tuple;
+	Relation		rel = NULL;
+	Oid				reloid = PG_GETARG_OID(0);
+	int				attnum = PG_GETARG_INT32(1);
+	char			rel_persistence;
+	TupleDesc	  	tupdesc;
+	MemoryContext 	oldcontext;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	/* get data from local hash */
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, NoLock);
+	relation_close(pg_tatistic, AccessShareLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get GTT table level data statistics.
+ */
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate	*tupstore;
+	TupleDesc		tupdesc;
+	MemoryContext	oldcontext;
+	HeapTuple		tuple;
+	Oid				reloid = PG_GETARG_OID(0);
+	Oid				relnode = 0;
+	char			rel_persistence;
+	BlockNumber		relpages = 0;
+	BlockNumber		relallvisible = 0;
+	uint32			relfrozenxid = 0;
+	uint32			relminmxid = 0;
+	double			reltuples = 0;
+	Relation		rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[6];
+		bool	isnull[6];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get a list of backend pids that are currently using this GTT.
+ */
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	PGPROC			*proc = NULL;
+	Bitmapset		*map = NULL;
+	Tuplestorestate *tupstore;
+	TupleDesc		tupdesc;
+	MemoryContext	oldcontext;
+	HeapTuple		tuple;
+	Oid				reloid = PG_GETARG_OID(0);
+	char			rel_persistence;
+	Relation		rel = NULL;
+	pid_t			pid = 0;
+	int				backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	/* get data from share hash */
+	map = copy_active_gtt_bitmap(reloid);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			/* backendid map to process pid */
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get backend level oldest relfrozenxid of each backend using GTT in current database.
+ */
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate *tupstore;
+	int				*pids = NULL;
+	uint32			*xids = NULL;
+	TupleDesc		tupdesc;
+	MemoryContext	oldcontext;
+	HeapTuple		tuple;
+	int				num_xid = MaxBackends + 1;
+	int				i = 0;
+	int				j = 0;
+	uint32			oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+
+	/* Get backend level oldest relfrozenxid in all backend that in MyDatabaseId use GTT */
+	oldest = list_all_backend_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+/*
+ * In order to build the GTT index, force enable GTT'index.
+ */
+void
+force_enable_gtt_index(Relation index)
+{
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(RelationGetRelid(index)));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+/*
+ * Fix the local state of the GTT's index.
+ */
+void
+gtt_fix_index_backend_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid heapOid = index->rd_index->indrelid;
+
+	/* Must be GTT */
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	/*
+	 * If this GTT is not initialized in the current backend,
+	 * its index status is temporarily set to invalid(local relcache).
+	 */
+	if (gtt_storage_attached(heapOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+/*
+ * During the SQL initialization of the executor (InitPlan)
+ * Initialize storage of GTT GTT'indexes and build empty index.
+ */
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int			i;
+	Oid			toastrelid;
+
+	if (!(operation == CMD_UTILITY || operation == CMD_INSERT))
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	/* Each GTT is initialized once in each backend */
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	/* init heap storage */
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+
+		index_build(relation, index, info, true, false);
+	}
+
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+
+		/* init index storage */
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid			indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+			/* build empty index */
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+/*
+ * Release the data structure memory used to store GTT storage info.
+ */
+static void
+gtt_free_statistics(gtt_relfilenode *rnode)
+{
+	int i;
+
+	Assert(rnode);
+
+	for (i = 0; i < rnode->natts; i++)
+	{
+		if (rnode->att_stat_tups[i])
+		{
+			heap_freetuple(rnode->att_stat_tups[i]);
+			rnode->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (rnode->attnum)
+		pfree(rnode->attnum);
+
+	if (rnode->att_stat_tups)
+		pfree(rnode->att_stat_tups);
+
+	pfree(rnode);
+
+	return;
+}
+
+/*
+ * Get the current relfilenode of this GTT.
+ */
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
+/*
+ * For cluster GTT.
+ * Exchange new and old relfilenode, leave footprints ensure rollback capability.
+ */
+void
+gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint)
+{
+	gtt_local_hash_entry	*entry1;
+	gtt_local_hash_entry	*entry2;
+	gtt_relfilenode			*gtt_rnode1 = NULL;
+	gtt_relfilenode			*gtt_rnode2 = NULL;
+	MemoryContext			oldcontext;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry1 = gtt_search_by_relid(rel1, false);
+	gtt_rnode1 = gtt_search_relfilenode(entry1, relfilenode1, false);
+
+	entry2 = gtt_search_by_relid(rel2, false);
+	gtt_rnode2 = gtt_search_relfilenode(entry2, relfilenode2, false);
+
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+	entry1->relfilenode_list = list_delete_ptr(entry1->relfilenode_list, gtt_rnode1);
+	entry2->relfilenode_list = lappend(entry2->relfilenode_list, gtt_rnode1);
+
+	entry2->relfilenode_list = list_delete_ptr(entry2->relfilenode_list, gtt_rnode2);
+	entry1->relfilenode_list = lappend(entry1->relfilenode_list, gtt_rnode2);
+	MemoryContextSwitchTo(oldcontext);
+
+	if (footprint)
+	{
+		entry1->oldrelid = rel2;
+		entry2->oldrelid = rel1;
+	}
+
+	return;
+}
+
+/*
+ * Get a relfilenode used by this GTT during the transaction life cycle.
+ */
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok)
+{
+	gtt_relfilenode		*rnode = NULL;
+	ListCell			*lc;
+
+	Assert(entry);
+
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == relfilenode)
+		{
+			rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (!missing_ok && rnode == NULL)
+		elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+	return rnode;
+}
+
+/*
+ * Get one GTT info from local hash.
+ */
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry == NULL && !missing_ok)
+		elog(ERROR, "relid %u not found in local hash", relid);
+
+	return entry;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 70e578894f..a1cefcf108 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 8aa329a2a0..9c7b156fdd 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -104,7 +105,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -185,6 +186,17 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -587,14 +599,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]));
 		}
 
 		/*
@@ -1638,7 +1651,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;
@@ -1740,31 +1753,48 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/*
+		 * For global temporary table,
+		 * Update column statistic to localhash, not catalog.
+		 */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 6487a9e3fc..9fa0d136ed 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/toasting.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
@@ -73,6 +74,12 @@ static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
 							bool verbose, bool *pSwapToastByContent,
 							TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
 static List *get_tables_to_cluster(MemoryContext cluster_context);
+static void gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables);
 
 
 /*---------------------------------------------------------------------------
@@ -390,6 +397,18 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap) &&
+		!gtt_storage_attached(RelationGetRelid(OldHeap)))
+	{
+		relation_close(OldHeap, AccessExclusiveLock);
+		pgstat_progress_end_command();
+		return;
+	}
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
@@ -774,6 +793,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	BlockNumber num_pages;
 	int			elevel = verbose ? INFO : DEBUG2;
 	PGRUsage	ru0;
+	bool		is_gtt = false;
+	uint32		gtt_relfrozenxid = 0;
+	uint32		gtt_relminmxid = 0;
 
 	pg_rusage_init(&ru0);
 
@@ -787,6 +809,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	else
 		OldIndex = NULL;
 
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		is_gtt = true;
+
 	/*
 	 * Their tuple descriptors should be exactly alike, but here we only need
 	 * assume that they have the same number of columns.
@@ -854,20 +879,38 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 						  &OldestXmin, &FreezeXid, NULL, &MultiXactCutoff,
 						  NULL);
 
-	/*
-	 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
-	 * backwards, so take the max.
-	 */
-	if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
-		TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
-		FreezeXid = OldHeap->rd_rel->relfrozenxid;
+	if (is_gtt)
+	{
+		/* Gets transaction information for global temporary table from localhash. */
+		get_gtt_relstats(OIDOldHeap,
+					NULL, NULL, NULL,
+					&gtt_relfrozenxid, &gtt_relminmxid);
+
+		if (TransactionIdIsValid(gtt_relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, gtt_relfrozenxid))
+			FreezeXid = gtt_relfrozenxid;
+
+		if (MultiXactIdIsValid(gtt_relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, gtt_relminmxid))
+			MultiXactCutoff = gtt_relminmxid;
+	}
+	else
+	{
+		/*
+		 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
+		 * backwards, so take the max.
+		 */
+		if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
+			FreezeXid = OldHeap->rd_rel->relfrozenxid;
 
-	/*
-	 * MultiXactCutoff, similarly, shouldn't go backwards either.
-	 */
-	if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
-		MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
-		MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+		/*
+		 * MultiXactCutoff, similarly, shouldn't go backwards either.
+		 */
+		if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
+			MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+	}
 
 	/*
 	 * Decide whether to use an indexscan or seqscan-and-optional-sort to scan
@@ -935,6 +978,15 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	table_close(OldHeap, NoLock);
 	table_close(NewHeap, NoLock);
 
+	/* Update relstats of global temporary table to localhash. */
+	if (is_gtt)
+	{
+		up_gtt_relstats(RelationGetRelid(NewHeap), num_pages, num_tuples, 0,
+						InvalidTransactionId, InvalidMultiXactId);
+		CommandCounterIncrement();
+		return;
+	}
+
 	/* Update pg_class to reflect the correct values of pages and tuples. */
 	relRelation = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1371,10 +1423,22 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 	 * Swap the contents of the heap relations (including any toast tables).
 	 * Also set old heap's relfrozenxid to frozenXid.
 	 */
-	swap_relation_files(OIDOldHeap, OIDNewHeap,
+	if (newrelpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		Assert(!is_system_catalog);
+		/* For global temporary table modify data in localhash, not pg_class */
+		gtt_swap_relation_files(OIDOldHeap, OIDNewHeap,
+								(OIDOldHeap == RelationRelationId),
+								swap_toast_by_content, is_internal,
+								frozenXid, cutoffMulti, mapped_tables);
+	}
+	else
+	{
+		swap_relation_files(OIDOldHeap, OIDNewHeap,
 						(OIDOldHeap == RelationRelationId),
 						swap_toast_by_content, is_internal,
 						frozenXid, cutoffMulti, mapped_tables);
+	}
 
 	/*
 	 * If it's a system catalog, queue a sinval message to flush all catcaches
@@ -1582,3 +1646,146 @@ get_tables_to_cluster(MemoryContext cluster_context)
 
 	return rvs;
 }
+
+/*
+ * For global temporary table, storage information is stored in localhash,
+ * This function like swap_relation_files except that update storage information,
+ * in the localhash, not pg_class.
+ */
+static void
+gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables)
+{
+	Relation	relRelation;
+	Oid			relfilenode1,
+				relfilenode2;
+	Relation	rel1;
+	Relation	rel2;
+
+	relRelation = table_open(RelationRelationId, RowExclusiveLock);
+
+	rel1 = relation_open(r1, AccessExclusiveLock);
+	rel2 = relation_open(r2, AccessExclusiveLock);
+
+	relfilenode1 = gtt_fetch_current_relfilenode(r1);
+	relfilenode2 = gtt_fetch_current_relfilenode(r2);
+
+	Assert(OidIsValid(relfilenode1) && OidIsValid(relfilenode2));
+	gtt_switch_rel_relfilenode(r1, relfilenode1, r2, relfilenode2, true);
+
+	CacheInvalidateRelcache(rel1);
+	CacheInvalidateRelcache(rel2);
+
+	InvokeObjectPostAlterHookArg(RelationRelationId, r1, 0,
+								 InvalidOid, is_internal);
+	InvokeObjectPostAlterHookArg(RelationRelationId, r2, 0,
+								 InvalidOid, true);
+
+	if (rel1->rd_rel->reltoastrelid || rel2->rd_rel->reltoastrelid)
+	{
+		if (swap_toast_by_content)
+		{
+			if (rel1->rd_rel->reltoastrelid && rel2->rd_rel->reltoastrelid)
+			{
+				gtt_swap_relation_files(rel1->rd_rel->reltoastrelid,
+									rel2->rd_rel->reltoastrelid,
+									target_is_pg_class,
+									swap_toast_by_content,
+									is_internal,
+									frozenXid,
+									cutoffMulti,
+									mapped_tables);
+			}
+			else
+				elog(ERROR, "cannot swap toast files by content when there's only one");
+		}
+		else
+		{
+			ObjectAddress baseobject,
+						toastobject;
+			long		count;
+
+			if (IsSystemRelation(rel1))
+				elog(ERROR, "cannot swap toast files by links for system catalogs");
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel1->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel2->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+
+			/* Register new dependencies */
+			baseobject.classId = RelationRelationId;
+			baseobject.objectSubId = 0;
+			toastobject.classId = RelationRelationId;
+			toastobject.objectSubId = 0;
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r1;
+				toastobject.objectId = rel1->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r2;
+				toastobject.objectId = rel2->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+		}
+	}
+
+	if (swap_toast_by_content &&
+		rel1->rd_rel->relkind == RELKIND_TOASTVALUE &&
+		rel2->rd_rel->relkind == RELKIND_TOASTVALUE)
+	{
+		Oid			toastIndex1,
+					toastIndex2;
+
+		/* Get valid index for each relation */
+		toastIndex1 = toast_get_valid_index(r1,
+											AccessExclusiveLock);
+		toastIndex2 = toast_get_valid_index(r2,
+											AccessExclusiveLock);
+
+		gtt_swap_relation_files(toastIndex1,
+							toastIndex2,
+							target_is_pg_class,
+							swap_toast_by_content,
+							is_internal,
+							InvalidTransactionId,
+							InvalidMultiXactId,
+							mapped_tables);
+	}
+
+	relation_close(rel1, NoLock);
+	relation_close(rel2, NoLock);
+
+	table_close(relRelation, RowExclusiveLock);
+
+	RelationCloseSmgrByOid(r1);
+	RelationCloseSmgrByOid(r2);
+
+	CommandCounterIncrement();
+}
+
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 8265b981eb..ef9d710bfe 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -290,7 +290,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP_ON_CURRENT_SESSION(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, whereClause,
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 40a54ad0bd..7895e7d99b 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -30,6 +30,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/copyfrom_internal.h"
 #include "commands/progress.h"
@@ -659,6 +660,9 @@ CopyFrom(CopyFromState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	/* Check and init global temporary table storage in current backend */
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	/*
 	 * Set up a ModifyTableState so we can let FDW(s) init themselves for
 	 * foreign-table result relation(s).
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 166374cc0c..a40edd3090 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -570,7 +570,7 @@ DefineIndex(Oid relationId,
 	 * is more efficient.  Do this before any use of the concurrent option is
 	 * done.
 	 */
-	if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+	if (stmt->concurrent && !RelpersistenceTsTemp(get_rel_persistence(relationId)))
 		concurrent = true;
 	else
 		concurrent = false;
@@ -2608,7 +2608,7 @@ ReindexIndex(RangeVar *indexRelation, ReindexParams *params, bool isTopLevel)
 	if (relkind == RELKIND_PARTITIONED_INDEX)
 		ReindexPartitions(indOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 persistence != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(persistence))
 		ReindexRelationConcurrently(indOid, params);
 	else
 	{
@@ -2717,7 +2717,7 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 	if (get_rel_relkind(heapOid) == RELKIND_PARTITIONED_TABLE)
 		ReindexPartitions(heapOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(get_rel_persistence(heapOid)))
 	{
 		result = ReindexRelationConcurrently(heapOid, params);
 
@@ -3132,7 +3132,7 @@ ReindexMultipleInternal(List *relids, ReindexParams *params)
 			   relkind != RELKIND_PARTITIONED_TABLE);
 
 		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			relpersistence != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(relpersistence))
 		{
 			ReindexParams newparams = *params;
 
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 34f2270ced..b8ea2efb5f 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -57,7 +57,10 @@ LockTableCommand(LockStmt *lockstmt)
 										  RangeVarCallbackForLockTable,
 										  (void *) &lockstmt->mode);
 
-		if (get_rel_relkind(reloid) == RELKIND_VIEW)
+		/* Lock table command ignores global temporary table. */
+		if (get_rel_persistence(reloid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+		else if (get_rel_relkind(reloid) == RELKIND_VIEW)
 			LockViewRecurse(reloid, lockstmt->mode, lockstmt->nowait, NIL);
 		else if (recurse)
 			LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait);
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 0415df9ccb..39f82bc278 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -275,8 +278,6 @@ ResetSequence(Oid seq_relid)
 	Buffer		buf;
 	HeapTupleData seqdatatuple;
 	HeapTuple	tuple;
-	HeapTuple	pgstuple;
-	Form_pg_sequence pgsform;
 	int64		startv;
 
 	/*
@@ -287,12 +288,7 @@ ResetSequence(Oid seq_relid)
 	init_sequence(seq_relid, &elm, &seq_rel);
 	(void) read_seq_tuple(seq_rel, &buf, &seqdatatuple);
 
-	pgstuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seq_relid));
-	if (!HeapTupleIsValid(pgstuple))
-		elog(ERROR, "cache lookup failed for sequence %u", seq_relid);
-	pgsform = (Form_pg_sequence) GETSTRUCT(pgstuple);
-	startv = pgsform->seqstart;
-	ReleaseSysCache(pgstuple);
+	startv = get_seqence_start_value(seq_relid);
 
 	/*
 	 * Copy the existing sequence tuple.
@@ -451,6 +447,15 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 
 	init_sequence(relid, &elm, &seqrel);
 
+	if (RELATION_IS_GLOBAL_TEMP(seqrel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(seqrel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary sequence %s when other backend attached it.",
+						RelationGetRelationName(seqrel))));
+	}
+
 	rel = table_open(SequenceRelationId, RowExclusiveLock);
 	seqtuple = SearchSysCacheCopy1(SEQRELID,
 								   ObjectIdGetDatum(relid));
@@ -611,7 +616,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +941,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1153,6 +1158,14 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	/* Initializes the storage for sequence which the global temporary table belongs. */
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1953,3 +1966,51 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+/*
+ * Get the startValue of the sequence from syscache.
+ */
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+/*
+ * Initialize sequence which global temporary table belongs.
+ */
+void
+gtt_init_seq(Relation rel)
+{
+	Datum		value[SEQ_COL_LASTCOL] = {0};
+	bool		null[SEQ_COL_LASTCOL] = {false};
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 97cc9fd6ec..f66fb8bb78 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -602,6 +603,7 @@ static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
 static void ATExecAlterCollationRefreshVersion(Relation rel, List *coll);
 static char GetAttributeCompression(Form_pg_attribute att, char *compression);
+static OnCommitAction gtt_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -647,6 +649,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -658,7 +661,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * Check consistency of arguments
 	 */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !RelpersistenceTsTemp(stmt->relation->relpersistence))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -688,7 +691,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	if (RelpersistenceTsTemp(stmt->relation->relpersistence)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -789,6 +792,56 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* For global temporary table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* Check parent table */
+		if (inheritOids)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			table_close(relation, NoLock);
+		}
+
+		/* Check oncommit clause and save to reloptions */
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temporary table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1423,7 +1476,7 @@ RemoveRelations(DropStmt *drop)
 		 * relation persistence cannot be known without its OID.
 		 */
 		if (drop->concurrent &&
-			get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(get_rel_persistence(relOid)))
 		{
 			Assert(list_length(drop->objects) == 1 &&
 				   drop->removeType == OBJECT_INDEX);
@@ -1633,7 +1686,16 @@ ExecuteTruncate(TruncateStmt *stmt)
 		Relation	rel;
 		bool		recurse = rv->inh;
 		Oid			myrelid;
-		LOCKMODE	lockmode = AccessExclusiveLock;
+		LOCKMODE	lockmode;
+
+		/*
+		 * Truncate global temp table only cleans up the data in current backend,
+		 * only low-level locks are required.
+		 */
+		if (rv->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+		else
+			lockmode = AccessExclusiveLock;
 
 		myrelid = RangeVarGetRelidExtended(rv, lockmode,
 										   0, RangeVarCallbackForTruncate,
@@ -1964,6 +2026,14 @@ ExecuteTruncateGuts(List *explicit_rels,
 			continue;
 		}
 
+		/*
+		 * Skip the global temporary table that is not initialized for storage
+		 * in current backend.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			continue;
+
 		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
@@ -4032,6 +4102,16 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temporary table only current backend use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -5406,6 +5486,42 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot rewrite temporary tables of other sessions")));
 
+			if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+			{
+				if (tab->chgPersistence)
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot change global temporary table persistence setting")));
+
+				/*
+				 * The storage for the global temporary table needs to be initialized
+				 * before rewrite table.
+				 */
+				if(!gtt_storage_attached(tab->relid))
+				{
+					ResultRelInfo *resultRelInfo;
+					MemoryContext oldcontext;
+					MemoryContext ctx_alter_gtt;
+
+					ctx_alter_gtt = AllocSetContextCreate(CurrentMemoryContext,
+											"gtt alter table", ALLOCSET_DEFAULT_SIZES);
+					oldcontext = MemoryContextSwitchTo(ctx_alter_gtt);
+
+					resultRelInfo = makeNode(ResultRelInfo);
+					InitResultRelInfo(resultRelInfo, OldHeap,
+									1, NULL, 0);
+					if (resultRelInfo->ri_RelationDesc->rd_rel->relhasindex &&
+						resultRelInfo->ri_IndexRelationDescs == NULL)
+						ExecOpenIndices(resultRelInfo, false);
+
+					init_gtt_storage(CMD_UTILITY, resultRelInfo);
+					ExecCloseIndices(resultRelInfo);
+
+					MemoryContextSwitchTo(oldcontext);
+					MemoryContextDelete(ctx_alter_gtt);
+				}
+			}
+
 			/*
 			 * Select destination tablespace (same as original unless user
 			 * requested a change)
@@ -8933,6 +9049,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -13514,6 +13636,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -13712,6 +13837,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temporary table");
+
 	/* Check first if relation can be moved to new tablespace */
 	if (!CheckRelationTableSpaceMove(rel, newTableSpace))
 	{
@@ -14016,7 +14144,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,
@@ -15544,6 +15672,7 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
 	switch (rel->rd_rel->relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -18499,3 +18628,40 @@ GetAttributeCompression(Form_pg_attribute att, char *compression)
 
 	return cmethod;
 }
+
+/*
+ * Parse the on commit clause for the temporary table
+ */
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			bool	res = false;
+			char	*sval = defGetString(def);
+
+			/* It has to be a Boolean value */
+			if (!parse_bool(sval, &res))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
+
+			if (res)
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 39df05c735..0a53f57c12 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"
@@ -1301,6 +1302,22 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = RELATION_IS_GLOBAL_TEMP(relation);
+
+	 /* For global temporary table */
+	if (is_gtt)
+	{
+		/* Store relation statistics and transaction information to the localhash */
+		up_gtt_relstats(RelationGetRelid(relation),
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+
+		/* Update relation statistics to local relcache */
+		relation->rd_rel->relpages = (int32) num_pages;
+		relation->rd_rel->reltuples = (float4) num_tuples;
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1314,17 +1331,23 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (!is_gtt &&
+		pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (!is_gtt &&
+		pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (!is_gtt &&
+		pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1369,7 +1392,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNextTransactionId(),
@@ -1380,7 +1404,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1488,6 +1513,13 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/*
+		 * The relfrozenxid for a global temporary talble is stored in localhash,
+		 * not pg_class, See list_all_session_gtt_frozenxids()
+		 */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1545,6 +1577,43 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/* If enable global temporary table */
+	if (max_active_gtt > 0)
+	{
+		TransactionId	safe_age;
+		/*  */
+		TransactionId	oldest_gtt_frozenxid =
+			list_all_backend_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid))
+		{
+			safe_age = oldest_gtt_frozenxid + vacuum_gtt_defer_check_age;
+			if (safe_age < FirstNormalTransactionId)
+				safe_age += FirstNormalTransactionId;
+
+			/*
+			 * We tolerate that the minimum age of gtt is less than
+			 * the minimum age of conventional tables, otherwise it will
+			 * throw warning message.
+			 */
+			if (TransactionIdIsNormal(safe_age) &&
+				TransactionIdPrecedes(safe_age, newFrozenXid))
+			{
+				ereport(WARNING,
+					(errmsg("global temp table oldest relfrozenxid %u is the oldest in the entire db",
+							oldest_gtt_frozenxid),
+					 errdetail("The oldest relfrozenxid in pg_class is %u", newFrozenXid),
+					 errhint("If they differ greatly, please consider cleaning up the data in global temp table.")));
+			}
+
+			/*
+			 * We need to ensure that the clog required by gtt is not cleand.
+			 */
+			if (TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+				newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1896,6 +1965,19 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	/*
+	 * Skip those global temporary table that are not initialized in
+	 * current backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel) &&
+		!gtt_storage_attached(RelationGetRelid(rel)))
+	{
+		relation_close(rel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index f2642dba6c..e088101ccd 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -530,6 +530,12 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	/* Global temporary table are not sensible. */
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 8d0f3de76e..8c5f8c631e 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -784,6 +784,10 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		/* This is one kind of temp table */
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 99780ebb96..0f0c318d98 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
@@ -533,6 +534,9 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	/* Init storage for partitioned global temporary table in current backend */
+	init_gtt_storage(mtstate->operation, leaf_part_rri);
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index c5a2a9a054..e473524f16 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -38,6 +38,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -632,6 +633,9 @@ ExecInsert(ModifyTableState *mtstate,
 		resultRelInfo->ri_IndexRelationDescs == NULL)
 		ExecOpenIndices(resultRelInfo, onconflict != ONCONFLICT_NONE);
 
+	/* Init storage for global temporary table in current backend */
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	/*
 	 * BEFORE ROW INSERT Triggers.
 	 *
@@ -2786,6 +2790,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		i++;
 	}
 
+
 	/*
 	 * Now we may initialize the subplan.
 	 */
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 353454b183..b6c30e88e0 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -48,7 +48,7 @@
 #include "partitioning/partprune.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/lsyscache.h"
-
+#include "utils/rel.h"
 
 /* results of subquery_is_pushdown_safe */
 typedef struct pushdown_safety_info
@@ -619,7 +619,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			if (RelpersistenceTsTemp(get_rel_persistence(rte->relid)))
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 1868c4eff4..d4a958f8e3 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -5917,7 +5917,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
-	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+	if (RELATION_IS_TEMP(heap) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 345c7425f6..e3a3836b3d 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -28,6 +28,7 @@
 #include "catalog/catalog.h"
 #include "catalog/heap.h"
 #include "catalog/index.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
@@ -230,6 +231,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temporary table in current backend */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index e415bc3df0..2b0cbbf952 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2892,6 +2892,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 73494002ad..eb528bdcf9 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3410,17 +3410,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; }
@@ -11675,19 +11669,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 d451f055f7..63e67f494a 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -82,6 +82,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);
 
 
 /*
@@ -3665,3 +3666,53 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * Like function isQueryUsingTempRelation_walker
+ * return true if any relation underlying
+ * the query is a global temporary table.
+ */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* Check if the query uses global temporary table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 9dd30370da..a67823df93 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -457,6 +457,13 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	/*
+	 * If a sequence is bound to a global temporary table, then the sequence
+	 * must been "global temporary"
+	 */
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
@@ -3357,6 +3364,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		cxt.isforeign = false;
 	}
 	cxt.relation = stmt->relation;
+	/* Sets the table persistence to the context */
+	cxt.relation->relpersistence = RelationGetRelPersistence(rel);
 	cxt.rel = rel;
 	cxt.inhRelations = NIL;
 	cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index d516df0ac5..5a2dc7c3c4 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2158,6 +2158,14 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/*
+			 * Aotuvacuum cannot vacuum the private data stored in each backend
+			 * that belongs to global temporary table, so skip them.
+			 */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2224,7 +2232,7 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		if (RelpersistenceTsTemp(classForm->relpersistence))
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 0c5b87864b..51bee4eea0 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -2944,6 +2945,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * Returns 0 if this global temporary table is not initialized in current
+	 * backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 47847563ef..1fb33ce4a3 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -23,6 +23,7 @@
 #include "access/syncscan.h"
 #include "access/twophase.h"
 #include "access/xlogprefetch.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -152,6 +153,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
@@ -225,6 +227,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* For global temporary table shared hashtable */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 5ff8cab394..07385c09da 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -65,6 +65,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -5078,3 +5079,78 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temporary table.
+ */
+int
+list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct *arrayP = procArray;
+	TransactionId result = InvalidTransactionId;
+	int			index;
+	uint8			flags = 0;
+	int			i = 0;
+
+	/* return 0 if feature is disabled */
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+		*n = 0;
+	}
+
+	/* Disable in standby node */
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		uint8           statusFlags = ProcGlobal->statusFlags[index];
+
+		if (statusFlags & flags)
+			continue;
+
+		/* Fetch all backend that is belonging to MyDatabaseId */
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->backend_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->backend_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->backend_gtt_frozenxid, result))
+				result = proc->backend_gtt_frozenxid;
+
+			/* save backend pid and backend level oldest relfrozenxid */
+			if (max_size > 0)
+			{
+				pids[i] = proc->pid;
+				xids[i] = proc->backend_gtt_frozenxid;
+				i++;
+			}
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (max_size > 0)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 975d547f34..09b3fa63de 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -175,7 +175,9 @@ static const char *const BuiltinTrancheNames[] = {
 	/* LWTRANCHE_PARALLEL_APPEND: */
 	"ParallelAppend",
 	/* LWTRANCHE_PER_XACT_PREDICATE_LIST: */
-	"PerXactPredicateList"
+	"PerXactPredicateList",
+	/* LWTRANCHE_GTT_CTL */
+	"GlobalTempTableControl"
 };
 
 StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 692f21ef6a..b4f572c590 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -393,6 +393,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;	/* init backend level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
@@ -578,6 +579,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;	/* init backend level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index da1a879f1f..d4d40a0302 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -982,6 +982,13 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/*
+			 * For global temporary table ,each backend has its own storage,
+			 * also only sees its own storage. Use Backendid to identify them.
+			 */
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 3d4304cce7..19a3daf73b 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -108,6 +108,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -5115,12 +5116,26 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								/* For global temporary table, get statistic data from localhash */
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -5368,15 +5383,28 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6799,6 +6827,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;
@@ -6816,6 +6845,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6827,6 +6864,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;
@@ -6842,6 +6881,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7760,6 +7807,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))
@@ -7772,6 +7821,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7784,6 +7842,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.
@@ -7803,6 +7863,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 6bba5f8ec4..fa81808df6 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -3113,6 +3114,19 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		/* For global temporary table, get statistic data from localhash */
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 29702d6eab..76d1cc3872 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -66,6 +66,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1117,6 +1118,28 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+
+				/* For global temporary table, get relstat data from localhash */
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+
+				/* And put them to local relcache */
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1171,6 +1194,8 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			/* The state of the global temporary table's index may need to be set */
+			gtt_fix_index_backend_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -1298,7 +1323,22 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+			/*
+			 * For global temporary table, get the latest relfilenode
+			 * from localhash and put it in relcache.
+			 */
+			if (OidIsValid(newrelnode) &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2240,6 +2280,9 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		/* The state of the global temporary table's index may need to be set */
+		gtt_fix_index_backend_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3467,6 +3510,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3576,28 +3623,38 @@ void
 RelationSetNewRelfilenode(Relation relation, char persistence)
 {
 	Oid			newrelfilenode;
-	Relation	pg_class;
-	HeapTuple	tuple;
+	Relation	pg_class = NULL;
+	HeapTuple	tuple = NULL;
 	Form_pg_class classform;
 	MultiXactId minmulti = InvalidMultiXactId;
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
+	/*
+	 * For global temporary table, storage information for the table is
+	 * maintained locally, not in catalog.
+	 */
+	bool		update_catalog = !RELATION_IS_GLOBAL_TEMP(relation);
 
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
 
-	/*
-	 * Get a writable copy of the pg_class tuple for the given relation.
-	 */
-	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+	memset(&classform, 0, sizeof(classform));
 
-	tuple = SearchSysCacheCopy1(RELOID,
-								ObjectIdGetDatum(RelationGetRelid(relation)));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "could not find tuple for relation %u",
-			 RelationGetRelid(relation));
-	classform = (Form_pg_class) GETSTRUCT(tuple);
+	if (update_catalog)
+	{
+		/*
+		 * Get a writable copy of the pg_class tuple for the given relation.
+		 */
+		pg_class = table_open(RelationRelationId, RowExclusiveLock);
+
+		tuple = SearchSysCacheCopy1(RELOID,
+									ObjectIdGetDatum(RelationGetRelid(relation)));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u",
+				 RelationGetRelid(relation));
+		classform = (Form_pg_class) GETSTRUCT(tuple);
+	}
 
 	/*
 	 * Schedule unlinking of the old storage at transaction commit.
@@ -3623,7 +3680,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;
@@ -3643,6 +3700,18 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 			break;
 	}
 
+	/* For global temporary table */
+	if (!update_catalog)
+	{
+		Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+		Assert(RELATION_IS_GLOBAL_TEMP(relation));
+		Assert(!RelationIsMapped(relation));
+
+		/* Make cache invalid and set new relnode to local cache. */
+		CacheInvalidateRelcache(relation);
+		relation->rd_node.relNode = relnode;
+	}
 	/*
 	 * If we're dealing with a mapped index, pg_class.relfilenode doesn't
 	 * change; instead we have to send the update to the relation mapper.
@@ -3652,7 +3721,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	 * possibly-inaccurate values of relpages etc, but those will be fixed up
 	 * later.
 	 */
-	if (RelationIsMapped(relation))
+	else if (RelationIsMapped(relation))
 	{
 		/* This case is only supported for indexes */
 		Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3698,9 +3767,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 		CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 	}
 
-	heap_freetuple(tuple);
+	if (update_catalog)
+	{
+		heap_freetuple(tuple);
 
-	table_close(pg_class, RowExclusiveLock);
+		table_close(pg_class, RowExclusiveLock);
+	}
 
 	/*
 	 * Make the pg_class row change or relation map change visible.  This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index b130874bdc..cd53576768 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -45,6 +45,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "commands/prepare.h"
 #include "commands/trigger.h"
@@ -152,6 +153,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temporary table feature.
+ * table schema are still saved in catalog.
+ *
+ * num > 0 means allows the database to manage multiple active tables at the same time.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2151,6 +2164,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 "
@@ -2699,6 +2721,16 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"vacuum_gtt_defer_check_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("The defer check age of GTT, used to check expired data after vacuum."),
+			NULL
+		},
+		&vacuum_gtt_defer_check_age,
+		10000, 0, 1000000,
+		NULL, NULL, NULL
+	},
+
 	/*
 	 * See also CheckRequiredParameterValues() if this parameter changes
 	 */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index e397b76356..301bf4ea8d 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2522,6 +2522,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
 		dopt->no_unlogged_table_data)
 		return;
 
+	/* Don't dump data in global temporary table/sequence */
+	if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		return;
+
 	/* Check that the data is not explicitly excluded */
 	if (simple_oid_list_member(&tabledata_exclude_oids,
 							   tbinfo->dobj.catId.oid))
@@ -15922,6 +15926,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
 		char	   *foreign = "";
+		char	   *table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15975,9 +15980,15 @@ dumpTableSchema(Archive *fout, const 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);
 
@@ -16341,6 +16352,15 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 			}
 		}
 
+		/*
+		 * Transaction information for the global temporary table is not stored
+		 * in the pg_class.
+		 */
+		if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			Assert(tbinfo->frozenxid == 0);
+			Assert(tbinfo->minmxid == 0);
+		}
 		/*
 		 * In binary_upgrade mode, arrange to restore the old relfrozenxid and
 		 * relminmxid of all vacuumable relations.  (While vacuum.c processes
@@ -16348,7 +16368,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		 * of other relations; so this "if" lacks RELKIND_TOASTVALUE, and the
 		 * child toast table is handled below.)
 		 */
-		if (dopt->binary_upgrade &&
+		else if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
 			 tbinfo->relkind == RELKIND_MATVIEW))
 		{
@@ -17373,6 +17393,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	PQExpBuffer query = createPQExpBuffer();
 	PQExpBuffer delqry = createPQExpBuffer();
 	char	   *qseqname;
+	bool	   global_temp_seq = false;
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
@@ -17382,9 +17403,12 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
-						  "FROM pg_catalog.pg_sequence "
-						  "WHERE seqrelid = '%u'::oid",
+						  "seqcache, seqcycle, "
+						  "c.relpersistence "
+						  "FROM pg_catalog.pg_sequence s, "
+						  "pg_catalog.pg_class c "
+						  "WHERE seqrelid = '%u'::oid "
+						  "and s.seqrelid = c.oid",
 						  tbinfo->dobj.catId.oid);
 	}
 	else if (fout->remoteVersion >= 80400)
@@ -17429,6 +17453,9 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
 
+	if (fout->remoteVersion >= 140000)
+		global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
 	if (strcmp(seqtype, "smallint") == 0)
@@ -17506,9 +17533,13 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	}
 	else
 	{
-		appendPQExpBuffer(query,
-						  "CREATE SEQUENCE %s\n",
-						  fmtQualifiedDumpable(tbinfo));
+		appendPQExpBuffer(query, "CREATE ");
+
+		if (global_temp_seq)
+			appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+		appendPQExpBuffer(query, "SEQUENCE %s\n",
+								  fmtQualifiedDumpable(tbinfo));
 
 		if (strcmp(seqtype, "bigint") != 0)
 			appendPQExpBuffer(query, "    AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 1c1c908664..3bcd6791fc 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -87,7 +87,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -176,7 +176,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 5d9a26cf82..2de11d5d70 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -447,8 +449,17 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
 			 "         ON c.relnamespace = n.oid "
 			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
+			 CppAsString2(RELKIND_MATVIEW) ") AND ");
+
+	if (skip_gtt)
+	{
+		/* exclude global temp tables */
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+			"    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND ");
+	}
+
 	/* exclude possible orphaned temp tables */
+	snprintf(query + strlen(query), sizeof(query) - strlen(query),
 			 "    ((n.nspname !~ '^pg_temp_' AND "
 			 "      n.nspname !~ '^pg_toast_temp_' AND "
 			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index e23b8ca88d..729a9c61e8 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -407,7 +407,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -638,7 +638,10 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+			/* exclude global temp tables */
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -649,7 +652,10 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+		/* exclude global temp tables */
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 919a7849fd..486d01fcae 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -388,7 +388,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 3e39fdb545..f9c17d0d12 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -4067,7 +4067,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ed84b3789c..3b03966b68 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1070,6 +1070,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2503,6 +2505,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2711,6 +2716,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 6ce480b49c..a0ccfb3d77 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -59,7 +59,8 @@ extern Relation heap_create(const char *relname,
 							bool mapped_relation,
 							bool allow_system_table_mods,
 							TransactionId *relfrozenxid,
-							MultiXactId *relminmxid);
+							MultiXactId *relminmxid,
+							bool skip_create_storage);
 
 extern Oid	heap_create_with_catalog(const char *relname,
 									 Oid relnamespace,
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 3e37729436..67652c21b5 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -175,6 +175,7 @@ DECLARE_INDEX(pg_class_tblspc_relfilenode_index, 3455, on pg_class using btree(r
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index b1ee078a1d..346fd3c6c0 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5666,6 +5666,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '9874',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '9875',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '9876',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '9877',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 0ab32b44e9..92e9f8ba48 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,7 +22,7 @@
 /* GUC variables */
 extern int	wal_skip_threshold;
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationPreTruncate(Relation rel);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000000..d48162c6b8
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,46 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern int		vacuum_gtt_defer_check_age;
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Oid relid,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void force_enable_gtt_index(Relation index);
+extern void gtt_fix_index_backend_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+extern void gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index 40544dd4c7..7b66d808fc 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 8336c2c5a2..bddcfe7256 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index c86ccdaf60..6b395551c1 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -399,6 +399,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index a8f052e484..4b4ed1a13a 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -189,6 +189,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_SHARED_TIDBITMAP,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_PER_XACT_PREDICATE_LIST,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 2fd1ff09a7..7ccb4dd46a 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -157,6 +157,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId backend_gtt_frozenxid;	/* backend level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index b01fa52139..8efffa55ac 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -94,4 +94,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 7894940741..762b0f0f75 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -283,6 +283,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 34e25eb597..e7f92ecf49 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	struct SMgrRelationData *rd_smgr;	/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -306,6 +306,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -577,11 +578,13 @@ typedef struct ViewOptions
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -589,6 +592,7 @@ typedef struct ViewOptions
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -601,6 +605,30 @@ typedef struct ViewOptions
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP_ON_CURRENT_SESSION
+ *		Test a rel is either local temp relation of this session
+ *		or global temp relation.
+ */
+#define RELATION_IS_TEMP_ON_CURRENT_SESSION(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is local temp relation or global temporary relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RelpersistenceTsTemp
+ *		Test a relpersistence is local temp relation or global temporary relation.
+ */
+#define RelpersistenceTsTemp(relpersistence) \
+	(relpersistence == RELPERSISTENCE_TEMP || \
+	 relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -646,6 +674,19 @@ typedef struct ViewOptions
 	 (relation)->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&	\
 	 !IsCatalogRelation(relation))
 
+/* For global temporary table */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/* Get on commit clause value only for global temporary table */
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+/* Get relpersistence for relation */
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000000..ca2d135056
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,14 @@
+reset search_path;
+select pg_sleep(5);
+ pg_sleep 
+----------
+ 
+(1 row)
+
+drop schema gtt cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
+drop cascades to table gtt.gtt_with_seq
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000000..0ac9e228d2
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,381 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+ n 
+---
+ 9
+(1 row)
+
+commit;
+-- 0 row
+select * from gtt6;
+ n 
+---
+(0 rows)
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temporary table
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temporary table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  could not create global temporary table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR:  insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count 
+-------
+     1
+(1 row)
+
+select count(*) from orders;
+ count 
+-------
+     0
+(1 row)
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  2 |  3
+(2 rows)
+
+commit;
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+select * from gtt_s_2 order by c1;
+ c1 | c2 
+----+----
+  1 |  2
+  2 |  4
+(2 rows)
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 1)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 300)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 33 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test_new
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.gtt6
+drop cascades to table gtt_function.foo
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to sequence gtt_function.seq_1
+drop cascades to table gtt_function.gtt_s_1
+drop cascades to table gtt_function.gtt_s_2
+drop cascades to table gtt_function.gt1
+drop cascades to table gtt_function.gtt_test1
+drop cascades to table gtt_function.gtt_test2
+drop cascades to table gtt_function.gtt_test3
+drop cascades to table gtt_function.gtt_test4
+drop cascades to table gtt_function.gtt_test5
+drop cascades to table gtt_function.gtt_test6
+drop cascades to table gtt_function.gtt_test7
+drop cascades to table gtt_function.gtt_test8
+drop cascades to table gtt_function.gtt_test9
+drop cascades to table gtt_function.gtt_test10
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000000..0646aaed73
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,90 @@
+set search_path=gtt,sys;
+select nextval('gtt_with_seq_c2_seq');
+ nextval 
+---------
+       1
+(1 row)
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000000..0fccf6b81c
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,343 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+rollback;
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+commit;
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          114688 |                 614400
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |            49152 |         49152 |                  49152
+(2 rows)
+
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2000
+(1 row)
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+ c1 | c2 
+----+----
+  1 |  1
+(1 row)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000000..8c0c376429
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,10 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000000..4420fdbd8f
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,80 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+ gtt_stats  | gtt_pkey  |        1 |         0 |             0
+(2 rows)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+reindex table gtt;
+reindex index gtt_pkey;
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+ gtt_stats  | gtt_pkey  |       30 |     10000 |             0
+(2 rows)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 6dff5439e0..784f275906 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1359,6 +1359,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index a091300857..61170dc454 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -129,3 +129,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000000..2c8e5868d1
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,8 @@
+
+
+reset search_path;
+
+select pg_sleep(5);
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000000..fd8b4d3e3b
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,253 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+commit;
+-- 0 row
+select * from gtt6;
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+commit;
+select * from gtt_s_1 order by c1;
+select * from gtt_s_2 order by c1;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000000..d05745e313
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,44 @@
+
+
+set search_path=gtt,sys;
+
+select nextval('gtt_with_seq_c2_seq');
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000000..81a60392c2
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,154 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+rollback;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+commit;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+select count(*) from gtt_t_kenyon;
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000000..dbe84d1b80
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,19 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000000..d61b0ff828
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,46 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+reindex table gtt;
+
+reindex index gtt_pkey;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
-- 
2.27.0

#312Dilip Kumar
dilipbalaut@gmail.com
In reply to: wenjing (#311)
Re: [Proposal] Global temporary tables

On Thu, Apr 22, 2021 at 1:11 PM wenjing <wjzeng2012@gmail.com> wrote:

I have briefly looked into the design comments added by the patch. I
have a few questions.

+Feature description
+--------------------------------
+
+Previously, temporary tables are defined once and automatically
+created (starting with empty contents) in every session before using them.

I don’t think this statement is correct, I mean if we define a temp
table in one session then it doesn’t automatically create in all the
sessions.

+
+Like local temporary table, Global Temporary Table supports ON COMMIT
PRESERVE ROWS
+or ON COMMIT DELETE ROWS clause, so that data in the temporary table can be
+cleaned up or reserved automatically when a session exits or a
transaction COMMITs.

/reserved/preserved

I was trying to look into the “Main design idea” section.

+1) CATALOG
+GTTs store session-specific data. The storage information of GTTs'data, their
+transaction information, and their statistics are not stored in the catalog.

I did not understand what do you mean by “transaction information” is
not stored in the catalog? Mean what transaction information are
stored in catalog in the normal table which is not stored for GTT?

+Changes to the GTT's metadata affect all sessions.
+The operations making those changes include truncate GTT, Vacuum/Cluster GTT,
+and Lock GTT.

How does Truncate or Vacuum affect all the sessions, I mean truncate
should only truncate the data of the current session and the same is
true for the vacuum no?

I will try to do a more detailed review.

--
Regards,
Dilip Kumar
EnterpriseDB: http://www.enterprisedb.com

#313wenjing
wjzeng2012@gmail.com
In reply to: Dilip Kumar (#312)
2 attachment(s)
Re: [Proposal] Global temporary tables

Dilip Kumar <dilipbalaut@gmail.com> 于2021年5月10日周一 下午6:44写道:

On Thu, Apr 22, 2021 at 1:11 PM wenjing <wjzeng2012@gmail.com> wrote:

I have briefly looked into the design comments added by the patch. I
have a few questions.

+Feature description
+--------------------------------
+
+Previously, temporary tables are defined once and automatically
+created (starting with empty contents) in every session before using them.

I don’t think this statement is correct, I mean if we define a temp
table in one session then it doesn’t automatically create in all the
sessions.

The point is the schema definition of GTT which is shared between sessions.
When a session creates a GTT, once the transaction for the Create Table is
committed, other sessions can see the GTT and can use it.
so I modified the description as follows:
automatically exist in every session that needs them.

What do you think?

+
+Like local temporary table, Global Temporary Table supports ON COMMIT
PRESERVE ROWS
+or ON COMMIT DELETE ROWS clause, so that data in the temporary table can
be
+cleaned up or reserved automatically when a session exits or a
transaction COMMITs.

/reserved/preserved

OK, I fixed it.

I was trying to look into the “Main design idea” section.

+1) CATALOG
+GTTs store session-specific data. The storage information of GTTs'data,
their
+transaction information, and their statistics are not stored in the
catalog.

I did not understand what do you mean by “transaction information” is
not stored in the catalog? Mean what transaction information are
stored in catalog in the normal table which is not stored for GTT?

"Transaction Information" refers to the GTT's relfrozenXID,
The relfrozenxid of a normal table is stored in pg_class, but GTT is not.

Each row of the data (the tuple header) contains transaction information
(such as xmin xmax).
At the same time, for regular table we record the oldest XID (as
relfrozenXID) in each piece of data into the pg_class, which is used to
clean up the data and clog and reuse transactional resources.
My design is:
Each session in GTT has a local copy of data (session level relfrozenXID),
which is stored in memory (local hashtable). and vacuum will refer to this
information.

+Changes to the GTT's metadata affect all sessions.
+The operations making those changes include truncate GTT, Vacuum/Cluster
GTT,
+and Lock GTT.

How does Truncate or Vacuum affect all the sessions, I mean truncate
should only truncate the data of the current session and the same is
true for the vacuum no?

Your understanding is correct.
Truncate GTT, VACUUM/CLUuster GTT, and Lock GTT affect current session and
without causing exclusive locking.
"Changes to the GTT's metadata affect All Sessions. "is not used to
describe the lock behavior. I deleted it.

I will try to do a more detailed review.

Thank you very much for your careful review. We are closer to success.

--
Regards,
Dilip Kumar
EnterpriseDB: http://www.enterprisedb.com

I updated the code and passed the regression tests.

Regards,
wjzeng

Attachments:

0002-gtt-v49-doc.patchapplication/octet-stream; name=0002-gtt-v49-doc.patchDownload
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index a8c5e4028af..40261a44bcd 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -169,32 +169,67 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     <listitem>
      <para>
       If specified, the table is created as a temporary table.
-      Temporary tables are automatically dropped at the end of a
-      session, or optionally at the end of the current transaction
-      (see <literal>ON COMMIT</literal> below).  The default
-      search_path includes the temporary schema first and so identically
-      named existing permanent tables are not chosen for new plans
+      Optionally, <literal>GLOBAL</literal> or <literal>LOCAL</literal>
+      can be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
+      They represent two types of temporary tables supported by <productname>PostgreSQL</productname>:
+      global temporary table and local temporary table. Without specified
+      GLOBAL or LOCAL, a local temporary table is created by default.
+     </para>
+
+    <para>
+     Both types of temporary tables’ data are truncated at the
+     end of a session or optionally at the end of the current transaction.
+     (see <literal>ON COMMIT</literal> below). For global temporary table,
+     its schema is reserved and reused by future sessions or transactions.
+     For local temporary table, both its data and its schema are dropped.
+    </para>
+
+    <variablelist>
+     <varlistentry>
+      <term><literal>Global Temporary Table</literal></term>
+      <listitem>
+       <para>
+        Global temporary table are defined just once and automatically exist
+        (starting with empty contents) in every session that needs them.
+        The schema definition of temporary tables is persistent and shared among sessions.
+        However, the data in temporary tables are kept private to sessions themselves,
+        even though they use same name and same schema.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>Local Temporary Table</literal></term>
+     <listitem>
+     <para>
+      Local temporary table are automatically dropped at the end of a
+      session (include schema and data). Future sessions need to create
+      their own temporary tables when they are used.
+     </para>
+     <para>
+      The default search_path includes the temporary schema first and so
+      identically named existing permanent tables are not chosen for new plans
       while the temporary table exists, unless they are referenced
       with schema-qualified names. Any indexes created on a temporary
       table are automatically temporary as well.
      </para>
+     </listitem>
+     </varlistentry>
+    </variablelist>
 
-     <para>
-      The <link linkend="autovacuum">autovacuum daemon</link> cannot
-      access and therefore cannot vacuum or analyze temporary tables.
-      For this reason, appropriate vacuum and analyze operations should be
-      performed via session SQL commands.  For example, if a temporary
-      table is going to be used in complex queries, it is wise to run
-      <command>ANALYZE</command> on the temporary table after it is populated.
-     </para>
+    <para>
+     The <link linkend="autovacuum">autovacuum daemon</link> cannot
+     access and therefore cannot vacuum or analyze temporary tables.
+     For this reason, appropriate vacuum and analyze operations should be
+     performed via session SQL commands.  For example, if a temporary
+     table is going to be used in complex queries, it is wise to run
+     <command>ANALYZE</command> on the temporary table after it is populated.
+    </para>
+    <para>
+     The Temporary table resembles the SQL standard, but has some differences.
+     see <xref linkend="sql-createtable-compatibility"/> below.
+    </para>
 
-     <para>
-      Optionally, <literal>GLOBAL</literal> or <literal>LOCAL</literal>
-      can be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
-      This presently makes no difference in <productname>PostgreSQL</productname>
-      and is deprecated; see
-      <xref linkend="sql-createtable-compatibility"/> below.
-     </para>
     </listitem>
    </varlistentry>
 
@@ -2125,13 +2160,17 @@ CREATE TABLE cities_partdef
    <title>Temporary Tables</title>
 
    <para>
-    Although the syntax of <literal>CREATE TEMPORARY TABLE</literal>
-    resembles that of the SQL standard, the effect is not the same.  In the
-    standard,
-    temporary tables are defined just once and automatically exist (starting
-    with empty contents) in every session that needs them.
-    <productname>PostgreSQL</productname> instead
-    requires each session to issue its own <literal>CREATE TEMPORARY
+    Although the syntax of <literal>CREATE GLOBAL/LOCAL TEMPORARY TABLE</literal>
+    resembles that of the SQL standard, the effect is not the same.
+    The global temporary table follows the SQL standards while local temporary
+    table does not.
+   </para>
+
+   <para>
+    First, in the standard, both global and local temporary tables are defined just
+    once and automatically exist (starting with empty contents) in every session
+    that needs them. For local temporary tables, <productname>PostgreSQL</productname>
+    instead requires each session to issue its own <literal>CREATE LOCAL TEMPORARY
     TABLE</literal> command for each temporary table to be used.  This allows
     different sessions to use the same temporary table name for different
     purposes, whereas the standard's approach constrains all instances of a
@@ -2139,29 +2178,14 @@ CREATE TABLE cities_partdef
    </para>
 
    <para>
-    The standard's definition of the behavior of temporary tables is
-    widely ignored.  <productname>PostgreSQL</productname>'s behavior
-    on this point is similar to that of several other SQL databases.
-   </para>
-
-   <para>
-    The SQL standard also distinguishes between global and local temporary
+    Second, the SQL standard distinguishes between global and local temporary
     tables, where a local temporary table has a separate set of contents for
     each SQL module within each session, though its definition is still shared
-    across sessions.  Since <productname>PostgreSQL</productname> does not
+    across sessions. Since <productname>PostgreSQL</productname> does not
     support SQL modules, this distinction is not relevant in
     <productname>PostgreSQL</productname>.
    </para>
 
-   <para>
-    For compatibility's sake, <productname>PostgreSQL</productname> will
-    accept the <literal>GLOBAL</literal> and <literal>LOCAL</literal> keywords
-    in a temporary table declaration, but they currently have no effect.
-    Use of these keywords is discouraged, since future versions of
-    <productname>PostgreSQL</productname> might adopt a more
-    standard-compliant interpretation of their meaning.
-   </para>
-
    <para>
     The <literal>ON COMMIT</literal> clause for temporary tables
     also resembles the SQL standard, but has some differences.
@@ -2169,7 +2193,8 @@ CREATE TABLE cities_partdef
     default behavior is <literal>ON COMMIT DELETE ROWS</literal>.  However, the
     default behavior in <productname>PostgreSQL</productname> is
     <literal>ON COMMIT PRESERVE ROWS</literal>.  The <literal>ON COMMIT
-    DROP</literal> option does not exist in SQL.
+    DROP</literal> option does not exist in SQL and is not supported by
+    global temporary table.
    </para>
   </refsect2>
 
-- 
2.24.3 (Apple Git-128)

0001-gtt-v49.patchapplication/octet-stream; name=0001-gtt-v49.patchDownload
From a792a78aeea89d1e15141b48bc4387abdc8c8e2a Mon Sep 17 00:00:00 2001
From: wjzeng <wjzeng2012@gmail.com>
Date: Wed, 12 May 2021 11:09:18 +0800
Subject: [PATCH] gtt v49 0512

---
 src/backend/access/common/reloptions.c       |   20 +-
 src/backend/access/gist/gistutil.c           |    2 +-
 src/backend/access/hash/hash.c               |    2 +-
 src/backend/access/heap/heapam_handler.c     |    4 +-
 src/backend/access/heap/vacuumlazy.c         |    9 +
 src/backend/access/nbtree/nbtpage.c          |    9 +
 src/backend/bootstrap/bootparse.y            |    3 +-
 src/backend/catalog/Makefile                 |    1 +
 src/backend/catalog/README.gtt               |  165 ++
 src/backend/catalog/catalog.c                |    1 +
 src/backend/catalog/heap.c                   |   91 +-
 src/backend/catalog/index.c                  |  117 +-
 src/backend/catalog/namespace.c              |    7 +
 src/backend/catalog/storage.c                |   52 +-
 src/backend/catalog/storage_gtt.c            | 1651 ++++++++++++++++++
 src/backend/catalog/system_views.sql         |   85 +
 src/backend/commands/analyze.c               |   78 +-
 src/backend/commands/cluster.c               |  235 ++-
 src/backend/commands/copy.c                  |    2 +-
 src/backend/commands/copyfrom.c              |    4 +
 src/backend/commands/indexcmds.c             |    8 +-
 src/backend/commands/lockcmds.c              |    5 +-
 src/backend/commands/sequence.c              |   81 +-
 src/backend/commands/tablecmds.c             |  176 +-
 src/backend/commands/vacuum.c                |   92 +-
 src/backend/commands/view.c                  |    6 +
 src/backend/executor/execMain.c              |    4 +
 src/backend/executor/execPartition.c         |    4 +
 src/backend/executor/nodeModifyTable.c       |    5 +
 src/backend/optimizer/path/allpaths.c        |    4 +-
 src/backend/optimizer/plan/planner.c         |    2 +-
 src/backend/optimizer/util/plancat.c         |    9 +
 src/backend/parser/analyze.c                 |    5 +
 src/backend/parser/gram.y                    |   20 +-
 src/backend/parser/parse_relation.c          |   51 +
 src/backend/parser/parse_utilcmd.c           |    9 +
 src/backend/postmaster/autovacuum.c          |   10 +-
 src/backend/storage/buffer/bufmgr.c          |   11 +
 src/backend/storage/ipc/ipci.c               |    4 +
 src/backend/storage/ipc/procarray.c          |   76 +
 src/backend/storage/lmgr/lwlock.c            |    4 +-
 src/backend/storage/lmgr/proc.c              |    2 +
 src/backend/utils/adt/dbsize.c               |    7 +
 src/backend/utils/adt/selfuncs.c             |   99 +-
 src/backend/utils/cache/lsyscache.c          |   14 +
 src/backend/utils/cache/relcache.c           |  106 +-
 src/backend/utils/misc/guc.c                 |   32 +
 src/bin/pg_dump/pg_dump.c                    |   49 +-
 src/bin/pg_upgrade/check.c                   |    4 +-
 src/bin/pg_upgrade/info.c                    |   21 +-
 src/bin/pg_upgrade/pg_upgrade.c              |   12 +-
 src/bin/pg_upgrade/pg_upgrade.h              |    2 +-
 src/bin/psql/describe.c                      |    3 +-
 src/bin/psql/tab-complete.c                  |    7 +
 src/include/catalog/heap.h                   |    3 +-
 src/include/catalog/pg_class.h               |    1 +
 src/include/catalog/pg_proc.dat              |   34 +
 src/include/catalog/storage.h                |    2 +-
 src/include/catalog/storage_gtt.h            |   46 +
 src/include/commands/sequence.h              |    1 +
 src/include/parser/parse_relation.h          |    3 +
 src/include/storage/bufpage.h                |    2 +
 src/include/storage/lwlock.h                 |    1 +
 src/include/storage/proc.h                   |    2 +
 src/include/storage/procarray.h              |    2 +
 src/include/utils/guc.h                      |    4 +
 src/include/utils/rel.h                      |   47 +-
 src/test/regress/expected/gtt_clean.out      |   14 +
 src/test/regress/expected/gtt_function.out   |  381 ++++
 src/test/regress/expected/gtt_parallel_1.out |   90 +
 src/test/regress/expected/gtt_parallel_2.out |  343 ++++
 src/test/regress/expected/gtt_prepare.out    |   10 +
 src/test/regress/expected/gtt_stats.out      |   80 +
 src/test/regress/expected/rules.out          |   88 +
 src/test/regress/parallel_schedule           |    7 +
 src/test/regress/sql/gtt_clean.sql           |    8 +
 src/test/regress/sql/gtt_function.sql        |  253 +++
 src/test/regress/sql/gtt_parallel_1.sql      |   44 +
 src/test/regress/sql/gtt_parallel_2.sql      |  154 ++
 src/test/regress/sql/gtt_prepare.sql         |   19 +
 src/test/regress/sql/gtt_stats.sql           |   46 +
 81 files changed, 4986 insertions(+), 181 deletions(-)
 create mode 100644 src/backend/catalog/README.gtt
 create mode 100644 src/backend/catalog/storage_gtt.c
 create mode 100644 src/include/catalog/storage_gtt.h
 create mode 100644 src/test/regress/expected/gtt_clean.out
 create mode 100644 src/test/regress/expected/gtt_function.out
 create mode 100644 src/test/regress/expected/gtt_parallel_1.out
 create mode 100644 src/test/regress/expected/gtt_parallel_2.out
 create mode 100644 src/test/regress/expected/gtt_prepare.out
 create mode 100644 src/test/regress/expected/gtt_stats.out
 create mode 100644 src/test/regress/sql/gtt_clean.sql
 create mode 100644 src/test/regress/sql/gtt_function.sql
 create mode 100644 src/test/regress/sql/gtt_parallel_1.sql
 create mode 100644 src/test/regress/sql/gtt_parallel_2.sql
 create mode 100644 src/test/regress/sql/gtt_prepare.sql
 create mode 100644 src/test/regress/sql/gtt_stats.sql

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 5554275e64..1d3e992cb8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -168,6 +168,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * In order to avoid consistency problems, the global temporary table
+	 * uses ShareUpdateExclusiveLock.
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temporary table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},
 	/* list terminator */
 	{{NULL}}
 };
@@ -1817,6 +1830,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1961,11 +1976,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
-	/*
-	 * autovacuum_enabled, autovacuum_analyze_threshold and
-	 * autovacuum_analyze_scale_factor are supported for partitioned tables.
-	 */
-
 	return default_reloptions(reloptions, validate, RELOPT_KIND_PARTITIONED);
 }
 
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index 8dcd53c457..3bef4a163c 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1024,7 +1024,7 @@ gistproperty(Oid index_oid, int attno,
 XLogRecPtr
 gistGetFakeLSN(Relation rel)
 {
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	if (RELATION_IS_TEMP(rel))
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 0752fb38a9..5c85d777f4 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -151,7 +151,7 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	if (!RELATION_IS_TEMP(index))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 7a9a640989..67fcc75438 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -591,7 +591,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
@@ -644,7 +644,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 4b4db4c81b..adbf75002f 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -63,6 +63,7 @@
 #include "access/xlog.h"
 #include "catalog/index.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -511,6 +512,14 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
 	Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
+	/*
+	 * not every AM requires these to be valid, but regular heap does.
+	 * Transaction information for the global temp table will be stored
+	 * in the local hash table, not the catalog.
+	 */
+	Assert(RELATION_IS_GLOBAL_TEMP(rel) ^ TransactionIdIsNormal(rel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(rel) ^ MultiXactIdIsValid(rel->rd_rel->relminmxid));
+
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
 	{
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 706e16ae94..1cbd83d802 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -677,6 +678,14 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		/*
+		 * If a global temporary table storage file is not initialized in the
+		 * current backend, its index does not have a root page, just returns 0.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 5fcd004e1b..58b994cef5 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -212,7 +212,8 @@ Boot_CreateStmt:
 												   mapped_relation,
 												   true,
 												   &relfrozenxid,
-												   &relminmxid);
+												   &relminmxid,
+												   false);
 						elog(DEBUG4, "bootstrap relation created");
 					}
 					else
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 69f9dd51a7..9ed7dd3b3d 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -43,6 +43,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/README.gtt b/src/backend/catalog/README.gtt
new file mode 100644
index 0000000000..bedc85c8df
--- /dev/null
+++ b/src/backend/catalog/README.gtt
@@ -0,0 +1,165 @@
+Global Temporary Table(GTT)
+==============
+
+Feature description
+--------------------------------
+
+Previously, temporary tables are defined once and automatically
+exist (starting with empty contents) in every session before using them.
+
+The temporary table implementation in PostgreSQL, known as Local temp tables(LTT),
+did not fully comply with the SQL standard. This version added the support of
+Global Temporary Table .
+
+The metadata of Global Temporary Table is persistent and shared among sessions.
+The data stored in the Global temporary table is independent of sessions. This
+means, when a session creates a Global Temporary Table and writes some data.
+Other sessions cannot see those data, but they have an empty Global Temporary
+Table with same schema.
+
+Like local temporary table, Global Temporary Table supports ON COMMIT PRESERVE ROWS
+or ON COMMIT DELETE ROWS clause, so that data in the temporary table can be
+cleaned up or preserved automatically when a session exits or a transaction COMMITs.
+
+Unlike Local Temporary Table, Global Temporary Table does not support
+ON COMMIT DROP clauses.
+
+In following paragraphs, we use GTT for Global Temporary Table and LTT for
+local temporary table.
+
+Main design ideas
+-----------------------------------------
+
+STORAGE & BUFFER
+
+In general, GTT and LTT use the same storage and buffer design and
+implementation. The storage files for both types of temporary tables are named
+as t_backendid_relfilenode, and the local buffer is used to cache the data.
+
+The schema of GTTs is shared among sessions while their data are not. We build
+a new mechanisms to manage those non-shared data and their statistics.
+Here is the summary of changes:
+
+1) CATALOG
+GTTs store session-specific data. The storage information of GTTs'data, their
+transaction information, and their statistics are not stored in the catalog.
+
+2) STORAGE INFO & STATISTICS & TRANSACTION
+In order to maintain durability and availability of GTTs'session-specific data,
+their storage information, statistics, and transaction information is managed
+in a local hash table tt_storage_local_hash.
+
+3) DDL
+A shared hash table active_gtt_shared_hash is added to track the state of the
+GTT in a different session. This information is recorded in the hash table
+during the DDL execution of the GTT.
+
+4) LOCK
+The data stored in a GTT can only be modified or accessed by owning session.
+The statements that only modify data in a GTT do not need a high level of table
+locking.
+The operations making those changes include truncate GTT, Vacuum/Cluster GTT,
+and Lock GTT.
+
+Detailed design
+-----------------------------------------
+
+1. CATALOG
+1.1 relpersistence
+define RELPERSISTENCEGLOBALTEMP 'g'
+Mark Global Temporary Table in pg_class relpersistence to 'g'. The relpersistence
+of indexes created on the GTT, sequences on GTT and toast tables on GTT are
+also set to 'g'
+
+1.2 on commit clause
+LTT's status associated with on commit DELETE ROWS and on commit PRESERVE ROWS
+is not stored in catalog. Instead, GTTs need a bool value on_commit_delete_rows
+in reloptions which is shared among sessions.
+
+1.3 gram.y
+GTT is already supported in syntax tree. We remove the warning message
+"GLOBAL is deprecated in temporary table creation" and mark
+relpersistence = RELPERSISTENCEGLOBALTEMP.
+
+2. STORAGE INFO & STATISTICS DATA & TRANSACTION INFO
+2.1. gtt_storage_local_hash
+Each backend creates a local hashtable gtt_storage_local_hash to track a GTT's
+storage file information, statistics, and transaction information.
+
+2.2 GTT storage file info track
+1) When one session inserts data into a GTT for the first time, record the
+storage info to gtt_storage_local_hash.
+2) Use beforeshmemexit to ensure that all files of session GTT are deleted when
+the session exits.
+
+2.3 statistics info
+1) relpages reltuples relallvisible
+2) The statistics of each column from pg_statistic
+All the above information is stored in gtt_storage_local_hash.
+When doing vacuum or analyze, GTT's statistic is updated, which is used by
+the SQL planner.
+The statistics summarizes only data in the current session.
+
+2.3 transaction info track
+frozenxid minmulti from pg_class is stored to gtt_storage_local_hash.
+
+4 DDL
+4.1. active_gtt_shared_hash
+This is the hash table created in shared memory to trace the GTT files initialized
+in each session. Each hash entry contains a bitmap that records the backendid of
+the initialized GTT file. With this hash table, we know which backend/session
+is using this GTT. Such information is used during GTT's DDL operations.
+
+4.1 DROP GTT
+One GTT is allowed to be deleted when there is only one session using the table
+and the session is the current session.
+After holding the AccessExclusiveLock lock on GTT, active_gtt_shared_hash
+is checked to ensure that.
+
+4.2 ALTER GTT/DROP INDEX ON GTT
+Same as drop GTT.
+
+4.3 CREATE INDEX ON GTT
+1) create index on GTT statements build index based on local data in a session.
+2) After the index is created, record the index metadata to the catalog.
+3) Other sessions can enable or disable the local GTT index.
+
+5 LOCK
+
+5.1 TRUNCATE GTT
+The truncate GTT command uses RowExclusiveLock, not AccessExclusiveLock, because
+this command only cleans up local data and local buffers in current session.
+
+5.2 CLUSTER GTT/VACUUM FULL GTT
+Same as truncate GTT.
+
+5.3 Lock GTT
+A lock GTT statement does not hold any table locks.
+
+6 MVCC commit log(clog) cleanup
+
+The GTT storage file contains transaction information. Queries for GTT data rely
+on transaction information such as clog. The transaction information required by
+each session may be completely different. We need to ensure that the transaction
+information of the GTT data is not cleaned up during its lifetime and that
+transaction resources are recycled at the instance level.
+
+6.1 The session level GTT oldest frozenxid
+1) To manage all GTT transaction information, add session level oldest frozenxid
+in each session. When one GTT is created or removed, record the session level
+oldest frozenxid and store it in MyProc.
+2) When vacuum advances the database's frozenxid, session level oldest frozenxid
+should be considered. This is acquired by searching all of MyProc. This way,
+we can avoid the clog required by GTTs to be cleaned.
+
+6.2 vacuum GTT
+Users can perform vacuum over a GTT to clean up local data in the GTT.
+
+6.3 autovacuum GTT
+Autovacuum skips all GTTs, because the data in GTTs is only visible in current
+session.
+
+7 OTHERS
+Parallel query
+Planner does not produce parallel query plans for SQL related to GTT. Because
+GTT private data cannot be accessed across processes.
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 245d536372..a38b594898 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -441,6 +441,7 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 431e62e389..ccdd61f245 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -62,6 +62,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,
@@ -305,7 +307,8 @@ heap_create(const char *relname,
 			bool mapped_relation,
 			bool allow_system_table_mods,
 			TransactionId *relfrozenxid,
-			MultiXactId *relminmxid)
+			MultiXactId *relminmxid,
+			bool skip_create_storage)
 {
 	bool		create_storage;
 	Relation	rel;
@@ -370,7 +373,9 @@ heap_create(const char *relname,
 	 * storage is already created, so don't do it here.  Also don't create it
 	 * for relkinds without physical storage.
 	 */
-	if (!RELKIND_HAS_STORAGE(relkind) || OidIsValid(relfilenode))
+	if (!RELKIND_HAS_STORAGE(relkind) ||
+		OidIsValid(relfilenode) ||
+		skip_create_storage)
 		create_storage = false;
 	else
 	{
@@ -428,7 +433,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -999,6 +1004,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -1037,8 +1043,21 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/*
+	 * The transaction information of the global temporary table is stored
+	 * in the local hash table, not in catalog.
+	 */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1304,7 +1323,8 @@ heap_create_with_catalog(const char *relname,
 							   mapped_relation,
 							   allow_system_table_mods,
 							   &relfrozenxid,
-							   &relminmxid);
+							   &relminmxid,
+							   false);
 
 	Assert(relid == RelationGetRelid(new_rel_desc));
 
@@ -1411,6 +1431,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1996,6 +2017,19 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/*
+	 * Only when other sessions are not using this Global temporary table,
+	 * is it allowed to DROP it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot drop global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3261,7 +3295,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3273,7 +3307,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3319,8 +3353,16 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		/*
+		 * Truncate global temporary table only clears local data,
+		 * so only low-level locks need to be held.
+		 */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3353,6 +3395,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3361,23 +3404,47 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	/* For global temporary table only */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		/*
+		 * If this GTT is not initialized in current backend, there is
+		 * no needs to anything.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(rel)))
+			return;
+
+		/*
+		 * Truncate GTT only clears local data, so only low-level locks
+		 * need to be held.
+		 */
+		lockmode = RowExclusiveLock;
+	}
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	/*
+	 * After the data is cleaned up on the GTT, the transaction information
+	 * for the data(stored in local hash table) is also need reset.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(RelationGetRelid(rel), 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 8ded2b53d4..7369143810 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -54,6 +54,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"
@@ -730,6 +731,29 @@ index_create(Relation heapRelation,
 	char		relkind;
 	TransactionId relfrozenxid;
 	MultiXactId relminmxid;
+	bool		skip_create_storage = false;
+
+	/* For global temporary table only */
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temporary table with concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot reindex global temporary tables concurrently")));
+
+		/*
+		 * For the case that some backend is applied relcache message to create
+		 * an index on a global temporary table, if this table in the current
+		 * backend are not initialized, the creation of index storage on the
+		 * table are also skipped.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+		{
+			skip_create_storage = true;
+			flags |= INDEX_CREATE_SKIP_BUILD;
+		}
+	}
 
 	/* constraint flags can only be set when a constraint is requested */
 	Assert((constr_flags == 0) ||
@@ -937,7 +961,8 @@ index_create(Relation heapRelation,
 								mapped_relation,
 								allow_system_table_mods,
 								&relfrozenxid,
-								&relminmxid);
+								&relminmxid,
+								skip_create_storage);
 
 	Assert(relfrozenxid == InvalidTransactionId);
 	Assert(relminmxid == InvalidMultiXactId);
@@ -2105,7 +2130,7 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 * lock (see comments in RemoveRelations), and a non-concurrent DROP is
 	 * more efficient.
 	 */
-	Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+	Assert(!RelpersistenceTsTemp(get_rel_persistence(indexId)) ||
 		   (!concurrent && !concurrent_lock_mode));
 
 	/*
@@ -2137,6 +2162,20 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/*
+	 * Allow to drop index on global temporary table when only current
+	 * backend use it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation) &&
+		is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+	{
+		ereport(ERROR,
+			(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+			 errmsg("cannot drop index %s or global temporary table %s",
+					RelationGetRelationName(userIndexRelation), RelationGetRelationName(userHeapRelation)),
+			 errhint("Because the index is created on the global temporary table and other backend attached it.")));
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2745,6 +2784,7 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = RELATION_IS_GLOBAL_TEMP(rel);
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2839,20 +2879,37 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
-		{
-			rd_rel->relpages = (int32) relpages;
-			dirty = true;
-		}
-		if (rd_rel->reltuples != (float4) reltuples)
+		/* For global temporary table */
+		if (is_gtt)
 		{
-			rd_rel->reltuples = (float4) reltuples;
-			dirty = true;
+			/* Update GTT'statistics into local relcache */
+			rel->rd_rel->relpages = (int32) relpages;
+			rel->rd_rel->reltuples = (float4) reltuples;
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+
+			/* Update GTT'statistics into local hashtable */
+			up_gtt_relstats(RelationGetRelid(rel), relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+		else
 		{
-			rd_rel->relallvisible = (int32) relallvisible;
-			dirty = true;
+			if (rd_rel->relpages != (int32) relpages)
+			{
+				rd_rel->relpages = (int32) relpages;
+				dirty = true;
+			}
+
+			if (rd_rel->reltuples != (float4) reltuples)
+			{
+				rd_rel->reltuples = (float4) reltuples;
+				dirty = true;
+			}
+
+			if (rd_rel->relallvisible != (int32) relallvisible)
+			{
+				rd_rel->relallvisible = (int32) relallvisible;
+				dirty = true;
+			}
 		}
 	}
 
@@ -2965,6 +3022,26 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, progress_index, progress_vals);
 	}
 
+	/* For build index on global temporary table */
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		/*
+		 * If the storage for the index in this session is not initialized,
+		 * it needs to be created.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			/* Before create init storage, fix the local Relcache first */
+			force_enable_gtt_index(indexRelation);
+
+			Assert(gtt_storage_attached(RelationGetRelid(heapRelation)));
+
+			/* Init storage for index */
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3520,6 +3597,20 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	if (!OidIsValid(heapId))
 		return;
 
+	/*
+	 * For reindex on global temporary table, If the storage for the index
+	 * in current backend is not initialized, nothing is done.
+	 */
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		!gtt_storage_attached(indexId))
+	{
+		/* Suppress use of the target index while rebuilding it */
+		SetReindexProcessing(heapId, indexId);
+		/* Re-allow use of target index */
+		ResetReindexProcessing();
+		return;
+	}
+
 	if ((params->options & REINDEXOPT_MISSING_OK) != 0)
 		heapRelation = try_table_open(heapId, ShareLock);
 	else
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 005e029c38..5e59b47969 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -655,6 +655,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/* Do not allow create global temporary table in temporary schemas */
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp table in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index cba7a9ada0..9cf6802f9b 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,6 +27,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -61,6 +62,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			temprelOid;			/* InvalidOid if not a global temporary rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -115,7 +117,7 @@ AddPendingSync(const RelFileNode *rnode)
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -126,7 +128,12 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/*
+		 * Global temporary table and local temporary table use same
+		 * design on storage module.
+		 */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -154,6 +161,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
@@ -165,6 +173,21 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		AddPendingSync(&rnode);
 	}
 
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		Assert(rel && RELATION_IS_GLOBAL_TEMP(rel));
+
+		/*
+		 * Remember the reloid of global temporary table, which is used for
+		 * transaction commit or rollback.
+		 * see smgrDoPendingDeletes.
+		 */
+		pending->temprelOid = RelationGetRelid(rel);
+
+		/* Remember global temporary table storage info to localhash */
+		remember_gtt_storage_info(rnode, rel);
+	}
+
 	return srel;
 }
 
@@ -201,11 +224,20 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/*
+	 * Remember the reloid of global temporary table, which is used for
+	 * transaction commit or rollback.
+	 * see smgrDoPendingDeletes.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->temprelOid = RelationGetRelid(rel);
+
 	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
@@ -602,6 +634,7 @@ smgrDoPendingDeletes(bool isCommit)
 	int			nrels = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -631,14 +664,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->temprelOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -648,12 +685,21 @@ smgrDoPendingDeletes(bool isCommit)
 
 	if (nrels > 0)
 	{
+		int	i;
+
 		smgrdounlinkall(srels, nrels, false);
 
-		for (int i = 0; i < nrels; i++)
+		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* Delete global temporary table info in localhash */
+			if (gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000000..6aec275c73
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1651 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  The body implementation of Global Temparary table.
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *	  See src/backend/catalog/GTT_README for Global temparary table's
+ *	  requirements and design.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_info_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+int		vacuum_gtt_defer_check_age = 0;
+
+/*
+ * The Global temporary table's shared hash table data structure
+ */
+typedef struct gtt_ctl_data
+{
+	LWLock		lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+/* record this global temporary table in which backends are being used */
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+/*
+ * The Global temporary table's local hash table data structure
+ */
+/* Record the storage information and statistical information of the global temporary table */
+typedef struct
+{
+	Oid			relfilenode;
+	Oid			spcnode;
+
+	/* pg_class relstat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+
+	/* pg_statistic column stat */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+
+	Oid			oldrelid;			/* remember the source of relid, before the switch relfilenode. */
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_free_statistics(gtt_relfilenode *rnode);
+static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok);
+static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok);
+static Bitmapset *copy_active_gtt_bitmap(Oid relid);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+/*
+ * Calculate shared hash table entry size for GTT.
+ */
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	/* hash entry header size */
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	/*
+	 * hash entry data size
+	 * this is a bitmap in shared memory, each backend have a bit.
+	 */
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+/*
+ * Calculate shared hash table max size for GTT.
+ */
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	/* shared hash header size */
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	/* hash entry size */
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	/* max size */
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+/*
+ * Initialization shared hash table for GTT.
+ */
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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();
+	}
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(gtt_fnode);
+	info.entrysize = action_gtt_shared_hash_entry_size();
+	active_gtt_shared_hash =
+		ShmemInitHash("active gtt shared hash",
+						gtt_shared_ctl->max_entry,
+						gtt_shared_ctl->max_entry,
+						&info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+/*
+ * Record GTT relid to shared hash table, which means that current backend is using this GTT.
+ */
+static void
+gtt_storage_checkin(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			found;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *)&(fnode), 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_global_temporary_table.")));
+	}
+
+	if (!found)
+	{
+		int			wordnum;
+
+		/* init bitmap */
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	/* record itself in bitmap */
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+/*
+ * Remove the GTT relid record from the shared hash table which means that current backend is
+ * not use this GTT.
+ */
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode				fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	if (!skiplock)
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		if (!skiplock)
+			LWLockRelease(&gtt_shared_ctl->lock);
+
+		if (isCommit)
+			elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+	/* remove itself from bitmap */
+	bms_del_member(entry->map, MyBackendId);
+
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+
+	if (!skiplock)
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+	return;
+}
+
+/*
+ * Gets usage information for a GTT from shared hash table.
+ * The information is in the form of bitmap.
+ * Quickly copy the entire bitmap from shared memory and return it.
+ * that to avoid holding locks for a long time.
+ */
+static Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	Bitmapset	*map_copy = NULL;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		return NULL;
+	}
+
+	Assert(entry->map);
+
+	/* copy the entire bitmap */
+	if (!bms_is_empty(entry->map))
+		map_copy = bms_copy(entry->map);
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return map_copy;
+}
+
+/*
+ * Check if there are other backends using this GTT besides the current backend.
+ */
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool		in_use = false;
+	int			num_use = 0;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		return false;
+	}
+
+	Assert(entry->map);
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+	/* how many backend are using this GTT */
+	num_use = bms_num_members(entry->map);
+	if (num_use == 0)
+		in_use = false;
+	else if (num_use == 1)
+	{
+		/* check if this is itself */
+		if(bms_is_member(MyBackendId, entry->map))
+			in_use = false;
+		else
+			in_use = true;
+	}
+	else
+		in_use = true;
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return in_use;
+}
+
+/*
+ * Record GTT information to local hash.
+ * They include GTT storage info, transaction info and statistical info.
+ */
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+	gtt_local_hash_entry	*entry;
+	MemoryContext			oldcontext;
+	gtt_relfilenode			*new_node = NULL;
+	Oid						relid = RelationGetRelid(rel);
+	int 					natts = 0;
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	/* First time through: initialize the hash table */
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		HASHCTL		ctl;
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_info_context =
+			AllocSetContextCreate(CacheMemoryContext,
+								"gtt info context",
+								ALLOCSET_DEFAULT_SIZES);
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		ctl.hcxt = gtt_info_context;
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+	}
+
+	Assert(CacheMemoryContext);
+	Assert(gtt_info_context);
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	entry = gtt_search_by_relid(relid, true);
+	if (!entry)
+	{
+		bool		found = false;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash,
+						(void *) &relid, HASH_ENTER, &found);
+
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+		entry->oldrelid = InvalidOid;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			/* record the on commit clause */
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			gtt_storage_checkin(relid);
+		}
+	}
+
+	/* record storage info relstat columnstats and transaction info to relfilenode list */
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	new_node->natts = 0;
+	new_node->attnum = NULL;
+	new_node->att_stat_tups = NULL;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* init column stats structure */
+	natts = RelationGetNumberOfAttributes(rel);
+	new_node->attnum = palloc0(sizeof(int) * natts);
+	new_node->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+	new_node->natts = natts;
+
+	/* only heap have transaction info */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+
+		/**/
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+
+	/* Registration callbacks are used to trigger cleanup during process exit */
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+/*
+ * Remove GTT information from local hash when transaction commit/rollback.
+ */
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	gtt_relfilenode *d_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+		return;
+	}
+
+	d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+		else if (entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+			gtt_relfilenode *gttnode2 = NULL;
+
+			/*
+			 * For cluster GTT rollback.
+			 * We need to roll back the exchange relfilenode operation.
+			 */
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+			Assert(gttnode2->relfilenode == rnode.relNode);
+			Assert(list_length(entry->relfilenode_list) == 1);
+			/* rollback switch relfilenode */
+			gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+									   entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+									   false);
+			/* clean up footprint */
+			entry2->oldrelid = InvalidOid;
+
+			/* temp relfilenode need free */
+			d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+			Assert(d_rnode);
+		}
+		else
+		{
+			/* rollback transaction */
+			if (entry->relfilenode_list == NIL)
+			{
+				if (entry->relkind == RELKIND_RELATION ||
+					entry->relkind == RELKIND_SEQUENCE)
+					gtt_storage_checkout(relid, false, isCommit);
+
+				hash_search(gtt_storage_local_hash,
+						(void *) &(relid), HASH_REMOVE, NULL);
+			}
+
+			return;
+		}
+	}
+
+	/* Clean up transaction info from Local order list and MyProc */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+
+		/* this is valid relfrozenxid */
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	/* delete relfilenode from rel entry */
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	gtt_free_statistics(d_rnode);
+
+	if (entry->relfilenode_list == NIL)
+	{
+		/* this means we truncate this GTT at current backend */
+
+		/* tell shared hash that current backend will no longer use this GTT */
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(relid, false, isCommit);
+
+		if (isCommit && entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+
+			/* commit transaction at cluster GTT, need clean up footprint */
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			entry2->oldrelid = InvalidOid;
+		}
+
+		hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_REMOVE, NULL);
+	}
+
+	return;
+}
+
+/*
+ * Check if current backend is using this GTT.
+ */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool found = false;
+	gtt_local_hash_entry *entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (!OidIsValid(relid))
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry)
+		found = true;
+
+	return found;
+}
+
+/*
+ * When backend exit, bulk cleaning all GTT storage and local buffer of this backend.
+ */
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS				status;
+	gtt_local_hash_entry		*entry;
+	SMgrRelation	*srels = NULL;
+	Oid				*relids = NULL;
+	char			*relkinds = NULL;
+	int			nrels = 0,
+				nfiles = 0,
+				maxrels = 0,
+				maxfiles = 0,
+				i = 0;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	/* Search all relfilenode for GTT in current backend */
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel;
+			RelFileNode		rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel = smgropen(rnode, MyBackendId);
+
+			if (maxfiles == 0)
+			{
+				maxfiles = 32;
+				srels = palloc(sizeof(SMgrRelation) * maxfiles);
+			}
+			else if (maxfiles <= nfiles)
+			{
+				maxfiles *= 2;
+				srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+			}
+
+			srels[nfiles++] = srel;
+		}
+
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			relids  = palloc(sizeof(Oid) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			relids  = repalloc(relids , sizeof(Oid) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		relkinds[nrels] = entry->relkind;
+		relids[nrels] = entry->relid;
+		nrels++;
+	}
+
+	/* drop local buffer and storage */
+	if (nfiles > 0)
+	{
+		/* Need to ensure we have a usable transaction. */
+                AbortOutOfAnyTransaction();
+
+		smgrdounlinkall(srels, nfiles, false);
+		for (i = 0; i < nfiles; i++)
+			smgrclose(srels[i]);
+
+		pfree(srels);
+	}
+
+	if (nrels)
+	{
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+		for (i = 0; i < nrels; i++)
+		{
+			/* tell shared hash */
+			if (relkinds[i] == RELKIND_RELATION ||
+				relkinds[i] == RELKIND_SEQUENCE)
+				gtt_storage_checkout(relids[i], true, false);
+		}
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+		pfree(relids);
+		pfree(relkinds);
+	}
+
+	/* set to global area */
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;
+
+	return;
+}
+
+/*
+ * Update GTT relstats(relpage/reltuple/relallvisible)
+ * to local hash.
+ */
+void
+up_gtt_relstats(Oid relid,
+					BlockNumber num_pages,
+					double num_tuples,
+					BlockNumber num_all_visible_pages,
+					TransactionId relfrozenxid,
+					TransactionId relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (!OidIsValid(relid))
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (num_pages > 0 &&
+		gtt_rnode->relpages != (int32)num_pages)
+		gtt_rnode->relpages = (int32)num_pages;
+
+	if (num_tuples > 0 &&
+		gtt_rnode->reltuples != (float4)num_tuples)
+		gtt_rnode->reltuples = (float4)num_tuples;
+
+	/* only heap contain transaction information and relallvisible */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (num_all_visible_pages > 0 &&
+			gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+		{
+			gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+		}
+
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNextTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			/* set to local order list */
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			/* set to global area */
+			set_gtt_session_relfrozenxid();
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search GTT relstats(relpage/reltuple/relallvisible)
+ * from local has.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update GTT info(definition is same as pg_statistic)
+ * to local hash.
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	MemoryContext			oldcontext;
+	int						i = 0;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return;
+
+	Assert(entry->relid == reloid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (gtt_rnode->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	/* switch context to gtt_info_context for store tuple at heap_form_tuple */
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == 0)
+		{
+			gtt_rnode->attnum[i] = attnum;
+			break;
+		}
+		else if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			heap_freetuple(gtt_rnode->att_stat_tups[i]);
+			gtt_rnode->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < gtt_rnode->natts);
+	Assert(gtt_rnode->att_stat_tups[i] == NULL);
+	gtt_rnode->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search GTT statistic info(definition is same as pg_statistic)
+ * from local hash.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	int						i = 0;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return NULL;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return NULL;
+
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			return gtt_rnode->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Insert a RelfrozenXID into the list and keep the list in order.
+ */
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell		*cell;
+	int				i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Remove a RelfrozenXID from order list gtt_session_relfrozenxid_list.
+ */
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+/*
+ * Update of backend Level oldest relfrozenxid to MyProc.
+ * This makes each backend's oldest RelFrozenxID globally visible.
+ */
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->backend_gtt_frozenxid != gtt_frozenxid)
+		MyProc->backend_gtt_frozenxid = gtt_frozenxid;
+}
+
+/*
+ * Get GTT column level data statistics.
+ */
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo 	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate *tupstore;
+	HeapTuple		tuple;
+	Relation		rel = NULL;
+	Oid				reloid = PG_GETARG_OID(0);
+	int				attnum = PG_GETARG_INT32(1);
+	char			rel_persistence;
+	TupleDesc	  	tupdesc;
+	MemoryContext 	oldcontext;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	/* get data from local hash */
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, NoLock);
+	relation_close(pg_tatistic, AccessShareLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get GTT table level data statistics.
+ */
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate	*tupstore;
+	TupleDesc		tupdesc;
+	MemoryContext	oldcontext;
+	HeapTuple		tuple;
+	Oid				reloid = PG_GETARG_OID(0);
+	Oid				relnode = 0;
+	char			rel_persistence;
+	BlockNumber		relpages = 0;
+	BlockNumber		relallvisible = 0;
+	uint32			relfrozenxid = 0;
+	uint32			relminmxid = 0;
+	double			reltuples = 0;
+	Relation		rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[6];
+		bool	isnull[6];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get a list of backend pids that are currently using this GTT.
+ */
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	PGPROC			*proc = NULL;
+	Bitmapset		*map = NULL;
+	Tuplestorestate *tupstore;
+	TupleDesc		tupdesc;
+	MemoryContext	oldcontext;
+	HeapTuple		tuple;
+	Oid				reloid = PG_GETARG_OID(0);
+	char			rel_persistence;
+	Relation		rel = NULL;
+	pid_t			pid = 0;
+	int				backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	/* get data from share hash */
+	map = copy_active_gtt_bitmap(reloid);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			/* backendid map to process pid */
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get backend level oldest relfrozenxid of each backend using GTT in current database.
+ */
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate *tupstore;
+	int				*pids = NULL;
+	uint32			*xids = NULL;
+	TupleDesc		tupdesc;
+	MemoryContext	oldcontext;
+	HeapTuple		tuple;
+	int				num_xid = MaxBackends + 1;
+	int				i = 0;
+	int				j = 0;
+	uint32			oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+
+	/* Get backend level oldest relfrozenxid in all backend that in MyDatabaseId use GTT */
+	oldest = list_all_backend_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+/*
+ * In order to build the GTT index, force enable GTT'index.
+ */
+void
+force_enable_gtt_index(Relation index)
+{
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(RelationGetRelid(index)));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+/*
+ * Fix the local state of the GTT's index.
+ */
+void
+gtt_fix_index_backend_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid heapOid = index->rd_index->indrelid;
+
+	/* Must be GTT */
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	/*
+	 * If this GTT is not initialized in the current backend,
+	 * its index status is temporarily set to invalid(local relcache).
+	 */
+	if (gtt_storage_attached(heapOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+/*
+ * During the SQL initialization of the executor (InitPlan)
+ * Initialize storage of GTT GTT'indexes and build empty index.
+ */
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int			i;
+	Oid			toastrelid;
+
+	if (!(operation == CMD_UTILITY || operation == CMD_INSERT))
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	/* Each GTT is initialized once in each backend */
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	/* init heap storage */
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+
+		index_build(relation, index, info, true, false);
+	}
+
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+
+		/* init index storage */
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid			indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+			/* build empty index */
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+/*
+ * Release the data structure memory used to store GTT storage info.
+ */
+static void
+gtt_free_statistics(gtt_relfilenode *rnode)
+{
+	int i;
+
+	Assert(rnode);
+
+	for (i = 0; i < rnode->natts; i++)
+	{
+		if (rnode->att_stat_tups[i])
+		{
+			heap_freetuple(rnode->att_stat_tups[i]);
+			rnode->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (rnode->attnum)
+		pfree(rnode->attnum);
+
+	if (rnode->att_stat_tups)
+		pfree(rnode->att_stat_tups);
+
+	pfree(rnode);
+
+	return;
+}
+
+/*
+ * Get the current relfilenode of this GTT.
+ */
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
+/*
+ * For cluster GTT.
+ * Exchange new and old relfilenode, leave footprints ensure rollback capability.
+ */
+void
+gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint)
+{
+	gtt_local_hash_entry	*entry1;
+	gtt_local_hash_entry	*entry2;
+	gtt_relfilenode			*gtt_rnode1 = NULL;
+	gtt_relfilenode			*gtt_rnode2 = NULL;
+	MemoryContext			oldcontext;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry1 = gtt_search_by_relid(rel1, false);
+	gtt_rnode1 = gtt_search_relfilenode(entry1, relfilenode1, false);
+
+	entry2 = gtt_search_by_relid(rel2, false);
+	gtt_rnode2 = gtt_search_relfilenode(entry2, relfilenode2, false);
+
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+	entry1->relfilenode_list = list_delete_ptr(entry1->relfilenode_list, gtt_rnode1);
+	entry2->relfilenode_list = lappend(entry2->relfilenode_list, gtt_rnode1);
+
+	entry2->relfilenode_list = list_delete_ptr(entry2->relfilenode_list, gtt_rnode2);
+	entry1->relfilenode_list = lappend(entry1->relfilenode_list, gtt_rnode2);
+	MemoryContextSwitchTo(oldcontext);
+
+	if (footprint)
+	{
+		entry1->oldrelid = rel2;
+		entry2->oldrelid = rel1;
+	}
+
+	return;
+}
+
+/*
+ * Get a relfilenode used by this GTT during the transaction life cycle.
+ */
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok)
+{
+	gtt_relfilenode		*rnode = NULL;
+	ListCell			*lc;
+
+	Assert(entry);
+
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == relfilenode)
+		{
+			rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (!missing_ok && rnode == NULL)
+		elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+	return rnode;
+}
+
+/*
+ * Get one GTT info from local hash.
+ */
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry == NULL && !missing_ok)
+		elog(ERROR, "relid %u not found in local hash", relid);
+
+	return entry;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 5c84d758bb..a1e226b301 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 8aa329a2a0..9c7b156fdd 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -104,7 +105,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -185,6 +186,17 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -587,14 +599,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]));
 		}
 
 		/*
@@ -1638,7 +1651,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;
@@ -1740,31 +1753,48 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/*
+		 * For global temporary table,
+		 * Update column statistic to localhash, not catalog.
+		 */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 6487a9e3fc..9fa0d136ed 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/toasting.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
@@ -73,6 +74,12 @@ static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
 							bool verbose, bool *pSwapToastByContent,
 							TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
 static List *get_tables_to_cluster(MemoryContext cluster_context);
+static void gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables);
 
 
 /*---------------------------------------------------------------------------
@@ -390,6 +397,18 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap) &&
+		!gtt_storage_attached(RelationGetRelid(OldHeap)))
+	{
+		relation_close(OldHeap, AccessExclusiveLock);
+		pgstat_progress_end_command();
+		return;
+	}
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
@@ -774,6 +793,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	BlockNumber num_pages;
 	int			elevel = verbose ? INFO : DEBUG2;
 	PGRUsage	ru0;
+	bool		is_gtt = false;
+	uint32		gtt_relfrozenxid = 0;
+	uint32		gtt_relminmxid = 0;
 
 	pg_rusage_init(&ru0);
 
@@ -787,6 +809,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	else
 		OldIndex = NULL;
 
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		is_gtt = true;
+
 	/*
 	 * Their tuple descriptors should be exactly alike, but here we only need
 	 * assume that they have the same number of columns.
@@ -854,20 +879,38 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 						  &OldestXmin, &FreezeXid, NULL, &MultiXactCutoff,
 						  NULL);
 
-	/*
-	 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
-	 * backwards, so take the max.
-	 */
-	if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
-		TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
-		FreezeXid = OldHeap->rd_rel->relfrozenxid;
+	if (is_gtt)
+	{
+		/* Gets transaction information for global temporary table from localhash. */
+		get_gtt_relstats(OIDOldHeap,
+					NULL, NULL, NULL,
+					&gtt_relfrozenxid, &gtt_relminmxid);
+
+		if (TransactionIdIsValid(gtt_relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, gtt_relfrozenxid))
+			FreezeXid = gtt_relfrozenxid;
+
+		if (MultiXactIdIsValid(gtt_relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, gtt_relminmxid))
+			MultiXactCutoff = gtt_relminmxid;
+	}
+	else
+	{
+		/*
+		 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
+		 * backwards, so take the max.
+		 */
+		if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
+			FreezeXid = OldHeap->rd_rel->relfrozenxid;
 
-	/*
-	 * MultiXactCutoff, similarly, shouldn't go backwards either.
-	 */
-	if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
-		MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
-		MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+		/*
+		 * MultiXactCutoff, similarly, shouldn't go backwards either.
+		 */
+		if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
+			MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+	}
 
 	/*
 	 * Decide whether to use an indexscan or seqscan-and-optional-sort to scan
@@ -935,6 +978,15 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	table_close(OldHeap, NoLock);
 	table_close(NewHeap, NoLock);
 
+	/* Update relstats of global temporary table to localhash. */
+	if (is_gtt)
+	{
+		up_gtt_relstats(RelationGetRelid(NewHeap), num_pages, num_tuples, 0,
+						InvalidTransactionId, InvalidMultiXactId);
+		CommandCounterIncrement();
+		return;
+	}
+
 	/* Update pg_class to reflect the correct values of pages and tuples. */
 	relRelation = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1371,10 +1423,22 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 	 * Swap the contents of the heap relations (including any toast tables).
 	 * Also set old heap's relfrozenxid to frozenXid.
 	 */
-	swap_relation_files(OIDOldHeap, OIDNewHeap,
+	if (newrelpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		Assert(!is_system_catalog);
+		/* For global temporary table modify data in localhash, not pg_class */
+		gtt_swap_relation_files(OIDOldHeap, OIDNewHeap,
+								(OIDOldHeap == RelationRelationId),
+								swap_toast_by_content, is_internal,
+								frozenXid, cutoffMulti, mapped_tables);
+	}
+	else
+	{
+		swap_relation_files(OIDOldHeap, OIDNewHeap,
 						(OIDOldHeap == RelationRelationId),
 						swap_toast_by_content, is_internal,
 						frozenXid, cutoffMulti, mapped_tables);
+	}
 
 	/*
 	 * If it's a system catalog, queue a sinval message to flush all catcaches
@@ -1582,3 +1646,146 @@ get_tables_to_cluster(MemoryContext cluster_context)
 
 	return rvs;
 }
+
+/*
+ * For global temporary table, storage information is stored in localhash,
+ * This function like swap_relation_files except that update storage information,
+ * in the localhash, not pg_class.
+ */
+static void
+gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables)
+{
+	Relation	relRelation;
+	Oid			relfilenode1,
+				relfilenode2;
+	Relation	rel1;
+	Relation	rel2;
+
+	relRelation = table_open(RelationRelationId, RowExclusiveLock);
+
+	rel1 = relation_open(r1, AccessExclusiveLock);
+	rel2 = relation_open(r2, AccessExclusiveLock);
+
+	relfilenode1 = gtt_fetch_current_relfilenode(r1);
+	relfilenode2 = gtt_fetch_current_relfilenode(r2);
+
+	Assert(OidIsValid(relfilenode1) && OidIsValid(relfilenode2));
+	gtt_switch_rel_relfilenode(r1, relfilenode1, r2, relfilenode2, true);
+
+	CacheInvalidateRelcache(rel1);
+	CacheInvalidateRelcache(rel2);
+
+	InvokeObjectPostAlterHookArg(RelationRelationId, r1, 0,
+								 InvalidOid, is_internal);
+	InvokeObjectPostAlterHookArg(RelationRelationId, r2, 0,
+								 InvalidOid, true);
+
+	if (rel1->rd_rel->reltoastrelid || rel2->rd_rel->reltoastrelid)
+	{
+		if (swap_toast_by_content)
+		{
+			if (rel1->rd_rel->reltoastrelid && rel2->rd_rel->reltoastrelid)
+			{
+				gtt_swap_relation_files(rel1->rd_rel->reltoastrelid,
+									rel2->rd_rel->reltoastrelid,
+									target_is_pg_class,
+									swap_toast_by_content,
+									is_internal,
+									frozenXid,
+									cutoffMulti,
+									mapped_tables);
+			}
+			else
+				elog(ERROR, "cannot swap toast files by content when there's only one");
+		}
+		else
+		{
+			ObjectAddress baseobject,
+						toastobject;
+			long		count;
+
+			if (IsSystemRelation(rel1))
+				elog(ERROR, "cannot swap toast files by links for system catalogs");
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel1->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel2->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+
+			/* Register new dependencies */
+			baseobject.classId = RelationRelationId;
+			baseobject.objectSubId = 0;
+			toastobject.classId = RelationRelationId;
+			toastobject.objectSubId = 0;
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r1;
+				toastobject.objectId = rel1->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r2;
+				toastobject.objectId = rel2->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+		}
+	}
+
+	if (swap_toast_by_content &&
+		rel1->rd_rel->relkind == RELKIND_TOASTVALUE &&
+		rel2->rd_rel->relkind == RELKIND_TOASTVALUE)
+	{
+		Oid			toastIndex1,
+					toastIndex2;
+
+		/* Get valid index for each relation */
+		toastIndex1 = toast_get_valid_index(r1,
+											AccessExclusiveLock);
+		toastIndex2 = toast_get_valid_index(r2,
+											AccessExclusiveLock);
+
+		gtt_swap_relation_files(toastIndex1,
+							toastIndex2,
+							target_is_pg_class,
+							swap_toast_by_content,
+							is_internal,
+							InvalidTransactionId,
+							InvalidMultiXactId,
+							mapped_tables);
+	}
+
+	relation_close(rel1, NoLock);
+	relation_close(rel2, NoLock);
+
+	table_close(relRelation, RowExclusiveLock);
+
+	RelationCloseSmgrByOid(r1);
+	RelationCloseSmgrByOid(r2);
+
+	CommandCounterIncrement();
+}
+
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 8265b981eb..ef9d710bfe 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -290,7 +290,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP_ON_CURRENT_SESSION(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, whereClause,
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 40a54ad0bd..7895e7d99b 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -30,6 +30,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/copyfrom_internal.h"
 #include "commands/progress.h"
@@ -659,6 +660,9 @@ CopyFrom(CopyFromState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	/* Check and init global temporary table storage in current backend */
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	/*
 	 * Set up a ModifyTableState so we can let FDW(s) init themselves for
 	 * foreign-table result relation(s).
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 3edf61993a..cc72c4c5b9 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -570,7 +570,7 @@ DefineIndex(Oid relationId,
 	 * is more efficient.  Do this before any use of the concurrent option is
 	 * done.
 	 */
-	if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+	if (stmt->concurrent && !RelpersistenceTsTemp(get_rel_persistence(relationId)))
 		concurrent = true;
 	else
 		concurrent = false;
@@ -2608,7 +2608,7 @@ ReindexIndex(RangeVar *indexRelation, ReindexParams *params, bool isTopLevel)
 	if (relkind == RELKIND_PARTITIONED_INDEX)
 		ReindexPartitions(indOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 persistence != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(persistence))
 		ReindexRelationConcurrently(indOid, params);
 	else
 	{
@@ -2717,7 +2717,7 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 	if (get_rel_relkind(heapOid) == RELKIND_PARTITIONED_TABLE)
 		ReindexPartitions(heapOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(get_rel_persistence(heapOid)))
 	{
 		result = ReindexRelationConcurrently(heapOid, params);
 
@@ -3132,7 +3132,7 @@ ReindexMultipleInternal(List *relids, ReindexParams *params)
 			   relkind != RELKIND_PARTITIONED_TABLE);
 
 		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			relpersistence != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(relpersistence))
 		{
 			ReindexParams newparams = *params;
 
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 34f2270ced..b8ea2efb5f 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -57,7 +57,10 @@ LockTableCommand(LockStmt *lockstmt)
 										  RangeVarCallbackForLockTable,
 										  (void *) &lockstmt->mode);
 
-		if (get_rel_relkind(reloid) == RELKIND_VIEW)
+		/* Lock table command ignores global temporary table. */
+		if (get_rel_persistence(reloid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+		else if (get_rel_relkind(reloid) == RELKIND_VIEW)
 			LockViewRecurse(reloid, lockstmt->mode, lockstmt->nowait, NIL);
 		else if (recurse)
 			LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait);
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 0415df9ccb..39f82bc278 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -275,8 +278,6 @@ ResetSequence(Oid seq_relid)
 	Buffer		buf;
 	HeapTupleData seqdatatuple;
 	HeapTuple	tuple;
-	HeapTuple	pgstuple;
-	Form_pg_sequence pgsform;
 	int64		startv;
 
 	/*
@@ -287,12 +288,7 @@ ResetSequence(Oid seq_relid)
 	init_sequence(seq_relid, &elm, &seq_rel);
 	(void) read_seq_tuple(seq_rel, &buf, &seqdatatuple);
 
-	pgstuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seq_relid));
-	if (!HeapTupleIsValid(pgstuple))
-		elog(ERROR, "cache lookup failed for sequence %u", seq_relid);
-	pgsform = (Form_pg_sequence) GETSTRUCT(pgstuple);
-	startv = pgsform->seqstart;
-	ReleaseSysCache(pgstuple);
+	startv = get_seqence_start_value(seq_relid);
 
 	/*
 	 * Copy the existing sequence tuple.
@@ -451,6 +447,15 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 
 	init_sequence(relid, &elm, &seqrel);
 
+	if (RELATION_IS_GLOBAL_TEMP(seqrel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(seqrel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary sequence %s when other backend attached it.",
+						RelationGetRelationName(seqrel))));
+	}
+
 	rel = table_open(SequenceRelationId, RowExclusiveLock);
 	seqtuple = SearchSysCacheCopy1(SEQRELID,
 								   ObjectIdGetDatum(relid));
@@ -611,7 +616,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +941,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1153,6 +1158,14 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	/* Initializes the storage for sequence which the global temporary table belongs. */
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1953,3 +1966,51 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+/*
+ * Get the startValue of the sequence from syscache.
+ */
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+/*
+ * Initialize sequence which global temporary table belongs.
+ */
+void
+gtt_init_seq(Relation rel)
+{
+	Datum		value[SEQ_COL_LASTCOL] = {0};
+	bool		null[SEQ_COL_LASTCOL] = {false};
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 591bf01189..f47a7890bb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -602,6 +603,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
 static char GetAttributeCompression(Form_pg_attribute att, char *compression);
+static OnCommitAction gtt_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -647,6 +649,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -658,7 +661,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * Check consistency of arguments
 	 */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !RelpersistenceTsTemp(stmt->relation->relpersistence))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -688,7 +691,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	if (RelpersistenceTsTemp(stmt->relation->relpersistence)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -789,6 +792,56 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* For global temporary table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* Check parent table */
+		if (inheritOids)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			table_close(relation, NoLock);
+		}
+
+		/* Check oncommit clause and save to reloptions */
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temporary table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1423,7 +1476,7 @@ RemoveRelations(DropStmt *drop)
 		 * relation persistence cannot be known without its OID.
 		 */
 		if (drop->concurrent &&
-			get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(get_rel_persistence(relOid)))
 		{
 			Assert(list_length(drop->objects) == 1 &&
 				   drop->removeType == OBJECT_INDEX);
@@ -1632,7 +1685,16 @@ ExecuteTruncate(TruncateStmt *stmt)
 		Relation	rel;
 		bool		recurse = rv->inh;
 		Oid			myrelid;
-		LOCKMODE	lockmode = AccessExclusiveLock;
+		LOCKMODE	lockmode;
+
+		/*
+		 * Truncate global temp table only cleans up the data in current backend,
+		 * only low-level locks are required.
+		 */
+		if (rv->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+		else
+			lockmode = AccessExclusiveLock;
 
 		myrelid = RangeVarGetRelidExtended(rv, lockmode,
 										   0, RangeVarCallbackForTruncate,
@@ -1951,6 +2013,14 @@ ExecuteTruncateGuts(List *explicit_rels,
 			continue;
 		}
 
+		/*
+		 * Skip the global temporary table that is not initialized for storage
+		 * in current backend.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			continue;
+
 		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
@@ -4018,6 +4088,16 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temporary table only current backend use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -5378,6 +5458,42 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot rewrite temporary tables of other sessions")));
 
+			if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+			{
+				if (tab->chgPersistence)
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot change global temporary table persistence setting")));
+
+				/*
+				 * The storage for the global temporary table needs to be initialized
+				 * before rewrite table.
+				 */
+				if(!gtt_storage_attached(tab->relid))
+				{
+					ResultRelInfo *resultRelInfo;
+					MemoryContext oldcontext;
+					MemoryContext ctx_alter_gtt;
+
+					ctx_alter_gtt = AllocSetContextCreate(CurrentMemoryContext,
+											"gtt alter table", ALLOCSET_DEFAULT_SIZES);
+					oldcontext = MemoryContextSwitchTo(ctx_alter_gtt);
+
+					resultRelInfo = makeNode(ResultRelInfo);
+					InitResultRelInfo(resultRelInfo, OldHeap,
+									1, NULL, 0);
+					if (resultRelInfo->ri_RelationDesc->rd_rel->relhasindex &&
+						resultRelInfo->ri_IndexRelationDescs == NULL)
+						ExecOpenIndices(resultRelInfo, false);
+
+					init_gtt_storage(CMD_UTILITY, resultRelInfo);
+					ExecCloseIndices(resultRelInfo);
+
+					MemoryContextSwitchTo(oldcontext);
+					MemoryContextDelete(ctx_alter_gtt);
+				}
+			}
+
 			/*
 			 * Select destination tablespace (same as original unless user
 			 * requested a change)
@@ -8905,6 +9021,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -13600,6 +13722,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -13798,6 +13923,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temporary table");
+
 	/* Check first if relation can be moved to new tablespace */
 	if (!CheckRelationTableSpaceMove(rel, newTableSpace))
 	{
@@ -14102,7 +14230,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,
@@ -15704,6 +15832,7 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
 	switch (rel->rd_rel->relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -18645,3 +18774,40 @@ GetAttributeCompression(Form_pg_attribute att, char *compression)
 
 	return cmethod;
 }
+
+/*
+ * Parse the on commit clause for the temporary table
+ */
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			bool	res = false;
+			char	*sval = defGetString(def);
+
+			/* It has to be a Boolean value */
+			if (!parse_bool(sval, &res))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
+
+			if (res)
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 39df05c735..0a53f57c12 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"
@@ -1301,6 +1302,22 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = RELATION_IS_GLOBAL_TEMP(relation);
+
+	 /* For global temporary table */
+	if (is_gtt)
+	{
+		/* Store relation statistics and transaction information to the localhash */
+		up_gtt_relstats(RelationGetRelid(relation),
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+
+		/* Update relation statistics to local relcache */
+		relation->rd_rel->relpages = (int32) num_pages;
+		relation->rd_rel->reltuples = (float4) num_tuples;
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1314,17 +1331,23 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (!is_gtt &&
+		pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (!is_gtt &&
+		pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (!is_gtt &&
+		pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1369,7 +1392,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNextTransactionId(),
@@ -1380,7 +1404,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1488,6 +1513,13 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/*
+		 * The relfrozenxid for a global temporary talble is stored in localhash,
+		 * not pg_class, See list_all_session_gtt_frozenxids()
+		 */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1545,6 +1577,43 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/* If enable global temporary table */
+	if (max_active_gtt > 0)
+	{
+		TransactionId	safe_age;
+		/*  */
+		TransactionId	oldest_gtt_frozenxid =
+			list_all_backend_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid))
+		{
+			safe_age = oldest_gtt_frozenxid + vacuum_gtt_defer_check_age;
+			if (safe_age < FirstNormalTransactionId)
+				safe_age += FirstNormalTransactionId;
+
+			/*
+			 * We tolerate that the minimum age of gtt is less than
+			 * the minimum age of conventional tables, otherwise it will
+			 * throw warning message.
+			 */
+			if (TransactionIdIsNormal(safe_age) &&
+				TransactionIdPrecedes(safe_age, newFrozenXid))
+			{
+				ereport(WARNING,
+					(errmsg("global temp table oldest relfrozenxid %u is the oldest in the entire db",
+							oldest_gtt_frozenxid),
+					 errdetail("The oldest relfrozenxid in pg_class is %u", newFrozenXid),
+					 errhint("If they differ greatly, please consider cleaning up the data in global temp table.")));
+			}
+
+			/*
+			 * We need to ensure that the clog required by gtt is not cleand.
+			 */
+			if (TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+				newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1896,6 +1965,19 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	/*
+	 * Skip those global temporary table that are not initialized in
+	 * current backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel) &&
+		!gtt_storage_attached(RelationGetRelid(rel)))
+	{
+		relation_close(rel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index f2642dba6c..e088101ccd 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -530,6 +530,12 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	/* Global temporary table are not sensible. */
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index df3d7f9a8b..5c66b35714 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -784,6 +784,10 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		/* This is one kind of temp table */
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 8e2feafd28..5bf39b9a34 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
@@ -533,6 +534,9 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	/* Init storage for partitioned global temporary table in current backend */
+	init_gtt_storage(mtstate->operation, leaf_part_rri);
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index a62928ae7c..d43a1432e8 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -38,6 +38,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -633,6 +634,9 @@ ExecInsert(ModifyTableState *mtstate,
 		resultRelInfo->ri_IndexRelationDescs == NULL)
 		ExecOpenIndices(resultRelInfo, onconflict != ONCONFLICT_NONE);
 
+	/* Init storage for global temporary table in current backend */
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	/*
 	 * BEFORE ROW INSERT Triggers.
 	 *
@@ -2787,6 +2791,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		i++;
 	}
 
+
 	/*
 	 * Now we may initialize the subplan.
 	 */
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 353454b183..b6c30e88e0 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -48,7 +48,7 @@
 #include "partitioning/partprune.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/lsyscache.h"
-
+#include "utils/rel.h"
 
 /* results of subquery_is_pushdown_safe */
 typedef struct pushdown_safety_info
@@ -619,7 +619,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			if (RelpersistenceTsTemp(get_rel_persistence(rte->relid)))
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 1868c4eff4..d4a958f8e3 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -5917,7 +5917,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
-	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+	if (RELATION_IS_TEMP(heap) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index c5194fdbbf..38d7c65854 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -30,6 +30,7 @@
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temporary table in current backend */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index e415bc3df0..2b0cbbf952 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2892,6 +2892,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 aaf1a51f68..2faa6c24ac 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3403,17 +3403,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; }
@@ -11683,19 +11677,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 d451f055f7..63e67f494a 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -82,6 +82,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);
 
 
 /*
@@ -3665,3 +3666,53 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * Like function isQueryUsingTempRelation_walker
+ * return true if any relation underlying
+ * the query is a global temporary table.
+ */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* Check if the query uses global temporary table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 48cce4567b..d46790d6e8 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -459,6 +459,13 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	/*
+	 * If a sequence is bound to a global temporary table, then the sequence
+	 * must been "global temporary"
+	 */
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
@@ -3359,6 +3366,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		cxt.isforeign = false;
 	}
 	cxt.relation = stmt->relation;
+	/* Sets the table persistence to the context */
+	cxt.relation->relpersistence = RelationGetRelPersistence(rel);
 	cxt.rel = rel;
 	cxt.inhRelations = NIL;
 	cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index d516df0ac5..5a2dc7c3c4 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2158,6 +2158,14 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/*
+			 * Aotuvacuum cannot vacuum the private data stored in each backend
+			 * that belongs to global temporary table, so skip them.
+			 */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2224,7 +2232,7 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		if (RelpersistenceTsTemp(classForm->relpersistence))
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 0c5b87864b..51bee4eea0 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -2944,6 +2945,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * Returns 0 if this global temporary table is not initialized in current
+	 * backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 3e4ec53a97..09f676d6a6 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -23,6 +23,7 @@
 #include "access/syncscan.h"
 #include "access/twophase.h"
 #include "commands/async.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
@@ -150,6 +151,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
@@ -222,6 +224,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* For global temporary table shared hashtable */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 5ff8cab394..07385c09da 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -65,6 +65,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -5078,3 +5079,78 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temporary table.
+ */
+int
+list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct *arrayP = procArray;
+	TransactionId result = InvalidTransactionId;
+	int			index;
+	uint8			flags = 0;
+	int			i = 0;
+
+	/* return 0 if feature is disabled */
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+		*n = 0;
+	}
+
+	/* Disable in standby node */
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		uint8           statusFlags = ProcGlobal->statusFlags[index];
+
+		if (statusFlags & flags)
+			continue;
+
+		/* Fetch all backend that is belonging to MyDatabaseId */
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->backend_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->backend_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->backend_gtt_frozenxid, result))
+				result = proc->backend_gtt_frozenxid;
+
+			/* save backend pid and backend level oldest relfrozenxid */
+			if (max_size > 0)
+			{
+				pids[i] = proc->pid;
+				xids[i] = proc->backend_gtt_frozenxid;
+				i++;
+			}
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (max_size > 0)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 55b9d7970e..7549fe0c57 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -175,7 +175,9 @@ static const char *const BuiltinTrancheNames[] = {
 	/* LWTRANCHE_PARALLEL_APPEND: */
 	"ParallelAppend",
 	/* LWTRANCHE_PER_XACT_PREDICATE_LIST: */
-	"PerXactPredicateList"
+	"PerXactPredicateList",
+	/* LWTRANCHE_GTT_CTL */
+	"GlobalTempTableControl"
 };
 
 StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 692f21ef6a..b4f572c590 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -393,6 +393,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;	/* init backend level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
@@ -578,6 +579,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;	/* init backend level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index da1a879f1f..d4d40a0302 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -982,6 +982,13 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/*
+			 * For global temporary table ,each backend has its own storage,
+			 * also only sees its own storage. Use Backendid to identify them.
+			 */
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 3d4304cce7..19a3daf73b 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -108,6 +108,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -5115,12 +5116,26 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								/* For global temporary table, get statistic data from localhash */
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -5368,15 +5383,28 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6799,6 +6827,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;
@@ -6816,6 +6845,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6827,6 +6864,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;
@@ -6842,6 +6881,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7760,6 +7807,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))
@@ -7772,6 +7821,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7784,6 +7842,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.
@@ -7803,6 +7863,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 6bba5f8ec4..fa81808df6 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -3113,6 +3114,19 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		/* For global temporary table, get statistic data from localhash */
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index bd88f6105b..c0cff393ec 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -65,6 +65,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1116,6 +1117,28 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+
+				/* For global temporary table, get relstat data from localhash */
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+
+				/* And put them to local relcache */
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1173,6 +1196,8 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			/* The state of the global temporary table's index may need to be set */
+			gtt_fix_index_backend_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -1300,7 +1325,22 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+			/*
+			 * For global temporary table, get the latest relfilenode
+			 * from localhash and put it in relcache.
+			 */
+			if (OidIsValid(newrelnode) &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2248,6 +2288,9 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		/* The state of the global temporary table's index may need to be set */
+		gtt_fix_index_backend_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3486,6 +3529,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3595,28 +3642,38 @@ void
 RelationSetNewRelfilenode(Relation relation, char persistence)
 {
 	Oid			newrelfilenode;
-	Relation	pg_class;
-	HeapTuple	tuple;
+	Relation	pg_class = NULL;
+	HeapTuple	tuple = NULL;
 	Form_pg_class classform;
 	MultiXactId minmulti = InvalidMultiXactId;
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
+	/*
+	 * For global temporary table, storage information for the table is
+	 * maintained locally, not in catalog.
+	 */
+	bool		update_catalog = !RELATION_IS_GLOBAL_TEMP(relation);
 
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
 
-	/*
-	 * Get a writable copy of the pg_class tuple for the given relation.
-	 */
-	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+	memset(&classform, 0, sizeof(classform));
 
-	tuple = SearchSysCacheCopy1(RELOID,
-								ObjectIdGetDatum(RelationGetRelid(relation)));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "could not find tuple for relation %u",
-			 RelationGetRelid(relation));
-	classform = (Form_pg_class) GETSTRUCT(tuple);
+	if (update_catalog)
+	{
+		/*
+		 * Get a writable copy of the pg_class tuple for the given relation.
+		 */
+		pg_class = table_open(RelationRelationId, RowExclusiveLock);
+
+		tuple = SearchSysCacheCopy1(RELOID,
+									ObjectIdGetDatum(RelationGetRelid(relation)));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u",
+				 RelationGetRelid(relation));
+		classform = (Form_pg_class) GETSTRUCT(tuple);
+	}
 
 	/*
 	 * Schedule unlinking of the old storage at transaction commit.
@@ -3642,7 +3699,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;
@@ -3662,6 +3719,18 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 			break;
 	}
 
+	/* For global temporary table */
+	if (!update_catalog)
+	{
+		Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+		Assert(RELATION_IS_GLOBAL_TEMP(relation));
+		Assert(!RelationIsMapped(relation));
+
+		/* Make cache invalid and set new relnode to local cache. */
+		CacheInvalidateRelcache(relation);
+		relation->rd_node.relNode = relnode;
+	}
 	/*
 	 * If we're dealing with a mapped index, pg_class.relfilenode doesn't
 	 * change; instead we have to send the update to the relation mapper.
@@ -3671,7 +3740,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	 * possibly-inaccurate values of relpages etc, but those will be fixed up
 	 * later.
 	 */
-	if (RelationIsMapped(relation))
+	else if (RelationIsMapped(relation))
 	{
 		/* This case is only supported for indexes */
 		Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3717,9 +3786,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 		CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 	}
 
-	heap_freetuple(tuple);
+	if (update_catalog)
+	{
+		heap_freetuple(tuple);
 
-	table_close(pg_class, RowExclusiveLock);
+		table_close(pg_class, RowExclusiveLock);
+	}
 
 	/*
 	 * Make the pg_class row change or relation map change visible.  This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 0a180341c2..6d82b8ff07 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -44,6 +44,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "commands/prepare.h"
 #include "commands/trigger.h"
@@ -151,6 +152,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temporary table feature.
+ * table schema are still saved in catalog.
+ *
+ * num > 0 means allows the database to manage multiple active tables at the same time.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2110,6 +2123,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 "
@@ -2658,6 +2680,16 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"vacuum_gtt_defer_check_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("The defer check age of GTT, used to check expired data after vacuum."),
+			NULL
+		},
+		&vacuum_gtt_defer_check_age,
+		10000, 0, 1000000,
+		NULL, NULL, NULL
+	},
+
 	/*
 	 * See also CheckRequiredParameterValues() if this parameter changes
 	 */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index e384690d94..6b7c677717 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2513,6 +2513,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
 		dopt->no_unlogged_table_data)
 		return;
 
+	/* Don't dump data in global temporary table/sequence */
+	if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		return;
+
 	/* Check that the data is not explicitly excluded */
 	if (simple_oid_list_member(&tabledata_exclude_oids,
 							   tbinfo->dobj.catId.oid))
@@ -15860,6 +15864,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
 		char	   *foreign = "";
+		char	   *table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15913,9 +15918,15 @@ dumpTableSchema(Archive *fout, const 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);
 
@@ -16279,6 +16290,15 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 			}
 		}
 
+		/*
+		 * Transaction information for the global temporary table is not stored
+		 * in the pg_class.
+		 */
+		if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			Assert(tbinfo->frozenxid == 0);
+			Assert(tbinfo->minmxid == 0);
+		}
 		/*
 		 * In binary_upgrade mode, arrange to restore the old relfrozenxid and
 		 * relminmxid of all vacuumable relations.  (While vacuum.c processes
@@ -16286,7 +16306,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		 * of other relations; so this "if" lacks RELKIND_TOASTVALUE, and the
 		 * child toast table is handled below.)
 		 */
-		if (dopt->binary_upgrade &&
+		else if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
 			 tbinfo->relkind == RELKIND_MATVIEW))
 		{
@@ -17291,6 +17311,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	PQExpBuffer query = createPQExpBuffer();
 	PQExpBuffer delqry = createPQExpBuffer();
 	char	   *qseqname;
+	bool	   global_temp_seq = false;
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
@@ -17300,9 +17321,12 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
-						  "FROM pg_catalog.pg_sequence "
-						  "WHERE seqrelid = '%u'::oid",
+						  "seqcache, seqcycle, "
+						  "c.relpersistence "
+						  "FROM pg_catalog.pg_sequence s, "
+						  "pg_catalog.pg_class c "
+						  "WHERE seqrelid = '%u'::oid "
+						  "and s.seqrelid = c.oid",
 						  tbinfo->dobj.catId.oid);
 	}
 	else if (fout->remoteVersion >= 80400)
@@ -17347,6 +17371,9 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
 
+	if (fout->remoteVersion >= 140000)
+		global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
 	if (strcmp(seqtype, "smallint") == 0)
@@ -17424,9 +17451,13 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	}
 	else
 	{
-		appendPQExpBuffer(query,
-						  "CREATE SEQUENCE %s\n",
-						  fmtQualifiedDumpable(tbinfo));
+		appendPQExpBuffer(query, "CREATE ");
+
+		if (global_temp_seq)
+			appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+		appendPQExpBuffer(query, "SEQUENCE %s\n",
+								  fmtQualifiedDumpable(tbinfo));
 
 		if (strcmp(seqtype, "bigint") != 0)
 			appendPQExpBuffer(query, "    AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 955a1533d0..09b538837d 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -88,7 +88,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -178,7 +178,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 5d9a26cf82..2de11d5d70 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -447,8 +449,17 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
 			 "         ON c.relnamespace = n.oid "
 			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
+			 CppAsString2(RELKIND_MATVIEW) ") AND ");
+
+	if (skip_gtt)
+	{
+		/* exclude global temp tables */
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+			"    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND ");
+	}
+
 	/* exclude possible orphaned temp tables */
+	snprintf(query + strlen(query), sizeof(query) - strlen(query),
 			 "    ((n.nspname !~ '^pg_temp_' AND "
 			 "      n.nspname !~ '^pg_toast_temp_' AND "
 			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index e23b8ca88d..729a9c61e8 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -407,7 +407,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -638,7 +638,10 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+			/* exclude global temp tables */
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -649,7 +652,10 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+		/* exclude global temp tables */
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index a5f71c5294..b6f8f8920e 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -387,7 +387,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 3e39fdb545..f9c17d0d12 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -4067,7 +4067,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index d917987fd5..afab53e7b0 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1055,6 +1055,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2484,6 +2486,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2692,6 +2697,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 6ce480b49c..a0ccfb3d77 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -59,7 +59,8 @@ extern Relation heap_create(const char *relname,
 							bool mapped_relation,
 							bool allow_system_table_mods,
 							TransactionId *relfrozenxid,
-							MultiXactId *relminmxid);
+							MultiXactId *relminmxid,
+							bool skip_create_storage);
 
 extern Oid	heap_create_with_catalog(const char *relname,
 									 Oid relnamespace,
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 3e37729436..67652c21b5 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -175,6 +175,7 @@ DECLARE_INDEX(pg_class_tblspc_relfilenode_index, 3455, on pg_class using btree(r
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 26c3fc0f6b..542b086c80 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5666,6 +5666,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '9874',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '9875',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '9876',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '9877',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 0ab32b44e9..92e9f8ba48 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,7 +22,7 @@
 /* GUC variables */
 extern int	wal_skip_threshold;
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationPreTruncate(Relation rel);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000000..d48162c6b8
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,46 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern int		vacuum_gtt_defer_check_age;
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Oid relid,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void force_enable_gtt_index(Relation index);
+extern void gtt_fix_index_backend_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+extern void gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index 40544dd4c7..7b66d808fc 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 8336c2c5a2..bddcfe7256 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index c86ccdaf60..6b395551c1 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -399,6 +399,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index a8f052e484..4b4ed1a13a 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -189,6 +189,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_SHARED_TIDBITMAP,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_PER_XACT_PREDICATE_LIST,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 2fd1ff09a7..7ccb4dd46a 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -157,6 +157,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId backend_gtt_frozenxid;	/* backend level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index b01fa52139..8efffa55ac 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -94,4 +94,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 24a5d9d3fb..825133f157 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -283,6 +283,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 774ac5b2b1..b380c02f69 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	struct SMgrRelationData *rd_smgr;	/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -318,6 +318,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
@@ -589,11 +590,13 @@ typedef struct ViewOptions
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -601,6 +604,7 @@ typedef struct ViewOptions
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -613,6 +617,30 @@ typedef struct ViewOptions
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP_ON_CURRENT_SESSION
+ *		Test a rel is either local temp relation of this session
+ *		or global temp relation.
+ */
+#define RELATION_IS_TEMP_ON_CURRENT_SESSION(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is local temp relation or global temporary relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RelpersistenceTsTemp
+ *		Test a relpersistence is local temp relation or global temporary relation.
+ */
+#define RelpersistenceTsTemp(relpersistence) \
+	(relpersistence == RELPERSISTENCE_TEMP || \
+	 relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -658,6 +686,19 @@ typedef struct ViewOptions
 	 (relation)->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&	\
 	 !IsCatalogRelation(relation))
 
+/* For global temporary table */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/* Get on commit clause value only for global temporary table */
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+/* Get relpersistence for relation */
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000000..ca2d135056
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,14 @@
+reset search_path;
+select pg_sleep(5);
+ pg_sleep 
+----------
+ 
+(1 row)
+
+drop schema gtt cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
+drop cascades to table gtt.gtt_with_seq
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000000..0ac9e228d2
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,381 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+ n 
+---
+ 9
+(1 row)
+
+commit;
+-- 0 row
+select * from gtt6;
+ n 
+---
+(0 rows)
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temporary table
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temporary table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  could not create global temporary table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR:  insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count 
+-------
+     1
+(1 row)
+
+select count(*) from orders;
+ count 
+-------
+     0
+(1 row)
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  2 |  3
+(2 rows)
+
+commit;
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+select * from gtt_s_2 order by c1;
+ c1 | c2 
+----+----
+  1 |  2
+  2 |  4
+(2 rows)
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 1)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 300)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 33 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test_new
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.gtt6
+drop cascades to table gtt_function.foo
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to sequence gtt_function.seq_1
+drop cascades to table gtt_function.gtt_s_1
+drop cascades to table gtt_function.gtt_s_2
+drop cascades to table gtt_function.gt1
+drop cascades to table gtt_function.gtt_test1
+drop cascades to table gtt_function.gtt_test2
+drop cascades to table gtt_function.gtt_test3
+drop cascades to table gtt_function.gtt_test4
+drop cascades to table gtt_function.gtt_test5
+drop cascades to table gtt_function.gtt_test6
+drop cascades to table gtt_function.gtt_test7
+drop cascades to table gtt_function.gtt_test8
+drop cascades to table gtt_function.gtt_test9
+drop cascades to table gtt_function.gtt_test10
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000000..0646aaed73
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,90 @@
+set search_path=gtt,sys;
+select nextval('gtt_with_seq_c2_seq');
+ nextval 
+---------
+       1
+(1 row)
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000000..0fccf6b81c
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,343 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+rollback;
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+commit;
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          114688 |                 614400
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |            49152 |         49152 |                  49152
+(2 rows)
+
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2000
+(1 row)
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+ c1 | c2 
+----+----
+  1 |  1
+(1 row)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000000..8c0c376429
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,10 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000000..4420fdbd8f
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,80 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+ gtt_stats  | gtt_pkey  |        1 |         0 |             0
+(2 rows)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+reindex table gtt;
+reindex index gtt_pkey;
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+ gtt_stats  | gtt_pkey  |       30 |     10000 |             0
+(2 rows)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index e5ab11275d..23b2863fec 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1359,6 +1359,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 22b0d3584d..3a6fc341eb 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -130,3 +130,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000000..2c8e5868d1
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,8 @@
+
+
+reset search_path;
+
+select pg_sleep(5);
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000000..fd8b4d3e3b
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,253 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+commit;
+-- 0 row
+select * from gtt6;
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+commit;
+select * from gtt_s_1 order by c1;
+select * from gtt_s_2 order by c1;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000000..d05745e313
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,44 @@
+
+
+set search_path=gtt,sys;
+
+select nextval('gtt_with_seq_c2_seq');
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000000..81a60392c2
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,154 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+rollback;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+commit;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+select count(*) from gtt_t_kenyon;
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000000..dbe84d1b80
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,19 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000000..d61b0ff828
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,46 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+reindex table gtt;
+
+reindex index gtt_pkey;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
-- 
2.27.0

#314wenjing
wjzeng2012@gmail.com
In reply to: wenjing (#313)
3 attachment(s)
Re: [Proposal] Global temporary tables

Rebase code based on the latest version.

Regards,
wenjing

Attachments:

0002-gtt-v50-regress.patchapplication/octet-stream; name=0002-gtt-v50-regress.patchDownload
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000000..ca2d135056
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,14 @@
+reset search_path;
+select pg_sleep(5);
+ pg_sleep 
+----------
+ 
+(1 row)
+
+drop schema gtt cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
+drop cascades to table gtt.gtt_with_seq
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000000..0ac9e228d2
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,381 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+ n 
+---
+ 9
+(1 row)
+
+commit;
+-- 0 row
+select * from gtt6;
+ n 
+---
+(0 rows)
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temporary table
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temporary table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  could not create global temporary table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR:  insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count 
+-------
+     1
+(1 row)
+
+select count(*) from orders;
+ count 
+-------
+     0
+(1 row)
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  2 |  3
+(2 rows)
+
+commit;
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+select * from gtt_s_2 order by c1;
+ c1 | c2 
+----+----
+  1 |  2
+  2 |  4
+(2 rows)
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 1)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 300)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 33 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test_new
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.gtt6
+drop cascades to table gtt_function.foo
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to sequence gtt_function.seq_1
+drop cascades to table gtt_function.gtt_s_1
+drop cascades to table gtt_function.gtt_s_2
+drop cascades to table gtt_function.gt1
+drop cascades to table gtt_function.gtt_test1
+drop cascades to table gtt_function.gtt_test2
+drop cascades to table gtt_function.gtt_test3
+drop cascades to table gtt_function.gtt_test4
+drop cascades to table gtt_function.gtt_test5
+drop cascades to table gtt_function.gtt_test6
+drop cascades to table gtt_function.gtt_test7
+drop cascades to table gtt_function.gtt_test8
+drop cascades to table gtt_function.gtt_test9
+drop cascades to table gtt_function.gtt_test10
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000000..0646aaed73
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,90 @@
+set search_path=gtt,sys;
+select nextval('gtt_with_seq_c2_seq');
+ nextval 
+---------
+       1
+(1 row)
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000000..0fccf6b81c
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,343 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+rollback;
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+commit;
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          114688 |                 614400
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |            49152 |         49152 |                  49152
+(2 rows)
+
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2000
+(1 row)
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+ c1 | c2 
+----+----
+  1 |  1
+(1 row)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000000..8c0c376429
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,10 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000000..4420fdbd8f
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,80 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+ gtt_stats  | gtt_pkey  |        1 |         0 |             0
+(2 rows)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+reindex table gtt;
+reindex index gtt_pkey;
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+ gtt_stats  | gtt_pkey  |       30 |     10000 |             0
+(2 rows)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index e5ab11275d..23b2863fec 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1359,6 +1359,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 22b0d3584d..3a6fc341eb 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -130,3 +130,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000000..2c8e5868d1
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,8 @@
+
+
+reset search_path;
+
+select pg_sleep(5);
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000000..fd8b4d3e3b
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,253 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+commit;
+-- 0 row
+select * from gtt6;
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+commit;
+select * from gtt_s_1 order by c1;
+select * from gtt_s_2 order by c1;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000000..d05745e313
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,44 @@
+
+
+set search_path=gtt,sys;
+
+select nextval('gtt_with_seq_c2_seq');
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000000..81a60392c2
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,154 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+rollback;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+commit;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+select count(*) from gtt_t_kenyon;
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000000..dbe84d1b80
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,19 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000000..d61b0ff828
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,46 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+reindex table gtt;
+
+reindex index gtt_pkey;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
-- 
2.27.0

0003-gtt-v50-doc.patchapplication/octet-stream; name=0003-gtt-v50-doc.patchDownload
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index a8c5e4028af..40261a44bcd 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -169,32 +169,67 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     <listitem>
      <para>
       If specified, the table is created as a temporary table.
-      Temporary tables are automatically dropped at the end of a
-      session, or optionally at the end of the current transaction
-      (see <literal>ON COMMIT</literal> below).  The default
-      search_path includes the temporary schema first and so identically
-      named existing permanent tables are not chosen for new plans
+      Optionally, <literal>GLOBAL</literal> or <literal>LOCAL</literal>
+      can be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
+      They represent two types of temporary tables supported by <productname>PostgreSQL</productname>:
+      global temporary table and local temporary table. Without specified
+      GLOBAL or LOCAL, a local temporary table is created by default.
+     </para>
+
+    <para>
+     Both types of temporary tables’ data are truncated at the
+     end of a session or optionally at the end of the current transaction.
+     (see <literal>ON COMMIT</literal> below). For global temporary table,
+     its schema is reserved and reused by future sessions or transactions.
+     For local temporary table, both its data and its schema are dropped.
+    </para>
+
+    <variablelist>
+     <varlistentry>
+      <term><literal>Global Temporary Table</literal></term>
+      <listitem>
+       <para>
+        Global temporary table are defined just once and automatically exist
+        (starting with empty contents) in every session that needs them.
+        The schema definition of temporary tables is persistent and shared among sessions.
+        However, the data in temporary tables are kept private to sessions themselves,
+        even though they use same name and same schema.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>Local Temporary Table</literal></term>
+     <listitem>
+     <para>
+      Local temporary table are automatically dropped at the end of a
+      session (include schema and data). Future sessions need to create
+      their own temporary tables when they are used.
+     </para>
+     <para>
+      The default search_path includes the temporary schema first and so
+      identically named existing permanent tables are not chosen for new plans
       while the temporary table exists, unless they are referenced
       with schema-qualified names. Any indexes created on a temporary
       table are automatically temporary as well.
      </para>
+     </listitem>
+     </varlistentry>
+    </variablelist>
 
-     <para>
-      The <link linkend="autovacuum">autovacuum daemon</link> cannot
-      access and therefore cannot vacuum or analyze temporary tables.
-      For this reason, appropriate vacuum and analyze operations should be
-      performed via session SQL commands.  For example, if a temporary
-      table is going to be used in complex queries, it is wise to run
-      <command>ANALYZE</command> on the temporary table after it is populated.
-     </para>
+    <para>
+     The <link linkend="autovacuum">autovacuum daemon</link> cannot
+     access and therefore cannot vacuum or analyze temporary tables.
+     For this reason, appropriate vacuum and analyze operations should be
+     performed via session SQL commands.  For example, if a temporary
+     table is going to be used in complex queries, it is wise to run
+     <command>ANALYZE</command> on the temporary table after it is populated.
+    </para>
+    <para>
+     The Temporary table resembles the SQL standard, but has some differences.
+     see <xref linkend="sql-createtable-compatibility"/> below.
+    </para>
 
-     <para>
-      Optionally, <literal>GLOBAL</literal> or <literal>LOCAL</literal>
-      can be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
-      This presently makes no difference in <productname>PostgreSQL</productname>
-      and is deprecated; see
-      <xref linkend="sql-createtable-compatibility"/> below.
-     </para>
     </listitem>
    </varlistentry>
 
@@ -2125,13 +2160,17 @@ CREATE TABLE cities_partdef
    <title>Temporary Tables</title>
 
    <para>
-    Although the syntax of <literal>CREATE TEMPORARY TABLE</literal>
-    resembles that of the SQL standard, the effect is not the same.  In the
-    standard,
-    temporary tables are defined just once and automatically exist (starting
-    with empty contents) in every session that needs them.
-    <productname>PostgreSQL</productname> instead
-    requires each session to issue its own <literal>CREATE TEMPORARY
+    Although the syntax of <literal>CREATE GLOBAL/LOCAL TEMPORARY TABLE</literal>
+    resembles that of the SQL standard, the effect is not the same.
+    The global temporary table follows the SQL standards while local temporary
+    table does not.
+   </para>
+
+   <para>
+    First, in the standard, both global and local temporary tables are defined just
+    once and automatically exist (starting with empty contents) in every session
+    that needs them. For local temporary tables, <productname>PostgreSQL</productname>
+    instead requires each session to issue its own <literal>CREATE LOCAL TEMPORARY
     TABLE</literal> command for each temporary table to be used.  This allows
     different sessions to use the same temporary table name for different
     purposes, whereas the standard's approach constrains all instances of a
@@ -2139,29 +2178,14 @@ CREATE TABLE cities_partdef
    </para>
 
    <para>
-    The standard's definition of the behavior of temporary tables is
-    widely ignored.  <productname>PostgreSQL</productname>'s behavior
-    on this point is similar to that of several other SQL databases.
-   </para>
-
-   <para>
-    The SQL standard also distinguishes between global and local temporary
+    Second, the SQL standard distinguishes between global and local temporary
     tables, where a local temporary table has a separate set of contents for
     each SQL module within each session, though its definition is still shared
-    across sessions.  Since <productname>PostgreSQL</productname> does not
+    across sessions. Since <productname>PostgreSQL</productname> does not
     support SQL modules, this distinction is not relevant in
     <productname>PostgreSQL</productname>.
    </para>
 
-   <para>
-    For compatibility's sake, <productname>PostgreSQL</productname> will
-    accept the <literal>GLOBAL</literal> and <literal>LOCAL</literal> keywords
-    in a temporary table declaration, but they currently have no effect.
-    Use of these keywords is discouraged, since future versions of
-    <productname>PostgreSQL</productname> might adopt a more
-    standard-compliant interpretation of their meaning.
-   </para>
-
    <para>
     The <literal>ON COMMIT</literal> clause for temporary tables
     also resembles the SQL standard, but has some differences.
@@ -2169,7 +2193,8 @@ CREATE TABLE cities_partdef
     default behavior is <literal>ON COMMIT DELETE ROWS</literal>.  However, the
     default behavior in <productname>PostgreSQL</productname> is
     <literal>ON COMMIT PRESERVE ROWS</literal>.  The <literal>ON COMMIT
-    DROP</literal> option does not exist in SQL.
+    DROP</literal> option does not exist in SQL and is not supported by
+    global temporary table.
    </para>
   </refsect2>
 
-- 
2.24.3 (Apple Git-128)

0001-gtt-v50.patchapplication/octet-stream; name=0001-gtt-v50.patchDownload
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 5554275e64..1d3e992cb8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -168,6 +168,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * In order to avoid consistency problems, the global temporary table
+	 * uses ShareUpdateExclusiveLock.
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temporary table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},
 	/* list terminator */
 	{{NULL}}
 };
@@ -1817,6 +1830,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1961,11 +1976,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
-	/*
-	 * autovacuum_enabled, autovacuum_analyze_threshold and
-	 * autovacuum_analyze_scale_factor are supported for partitioned tables.
-	 */
-
 	return default_reloptions(reloptions, validate, RELOPT_KIND_PARTITIONED);
 }
 
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index 8dcd53c457..3bef4a163c 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1024,7 +1024,7 @@ gistproperty(Oid index_oid, int attno,
 XLogRecPtr
 gistGetFakeLSN(Relation rel)
 {
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	if (RELATION_IS_TEMP(rel))
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 0752fb38a9..5c85d777f4 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -151,7 +151,7 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	if (!RELATION_IS_TEMP(index))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index e2cd79ec54..8e34e14c43 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -591,7 +591,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
@@ -644,7 +644,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 4b600e951a..12554f0b57 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -63,6 +63,7 @@
 #include "access/xlog.h"
 #include "catalog/index.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -510,6 +511,14 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 	Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
 	Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
+	/*
+	 * not every AM requires these to be valid, but regular heap does.
+	 * Transaction information for the global temp table will be stored
+	 * in the local hash table, not the catalog.
+	 */
+	Assert(RELATION_IS_GLOBAL_TEMP(rel) ^ TransactionIdIsNormal(rel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(rel) ^ MultiXactIdIsValid(rel->rd_rel->relminmxid));
+
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
 	{
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index ebec8fa5b8..84766b3a33 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -677,6 +678,14 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		/*
+		 * If a global temporary table storage file is not initialized in the
+		 * current backend, its index does not have a root page, just returns 0.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 5fcd004e1b..58b994cef5 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -212,7 +212,8 @@ Boot_CreateStmt:
 												   mapped_relation,
 												   true,
 												   &relfrozenxid,
-												   &relminmxid);
+												   &relminmxid,
+												   false);
 						elog(DEBUG4, "bootstrap relation created");
 					}
 					else
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 69f9dd51a7..9ed7dd3b3d 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -43,6 +43,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/README.gtt b/src/backend/catalog/README.gtt
new file mode 100644
index 0000000000..bedc85c8df
--- /dev/null
+++ b/src/backend/catalog/README.gtt
@@ -0,0 +1,165 @@
+Global Temporary Table(GTT)
+==============
+
+Feature description
+--------------------------------
+
+Previously, temporary tables are defined once and automatically
+exist (starting with empty contents) in every session before using them.
+
+The temporary table implementation in PostgreSQL, known as Local temp tables(LTT),
+did not fully comply with the SQL standard. This version added the support of
+Global Temporary Table .
+
+The metadata of Global Temporary Table is persistent and shared among sessions.
+The data stored in the Global temporary table is independent of sessions. This
+means, when a session creates a Global Temporary Table and writes some data.
+Other sessions cannot see those data, but they have an empty Global Temporary
+Table with same schema.
+
+Like local temporary table, Global Temporary Table supports ON COMMIT PRESERVE ROWS
+or ON COMMIT DELETE ROWS clause, so that data in the temporary table can be
+cleaned up or preserved automatically when a session exits or a transaction COMMITs.
+
+Unlike Local Temporary Table, Global Temporary Table does not support
+ON COMMIT DROP clauses.
+
+In following paragraphs, we use GTT for Global Temporary Table and LTT for
+local temporary table.
+
+Main design ideas
+-----------------------------------------
+
+STORAGE & BUFFER
+
+In general, GTT and LTT use the same storage and buffer design and
+implementation. The storage files for both types of temporary tables are named
+as t_backendid_relfilenode, and the local buffer is used to cache the data.
+
+The schema of GTTs is shared among sessions while their data are not. We build
+a new mechanisms to manage those non-shared data and their statistics.
+Here is the summary of changes:
+
+1) CATALOG
+GTTs store session-specific data. The storage information of GTTs'data, their
+transaction information, and their statistics are not stored in the catalog.
+
+2) STORAGE INFO & STATISTICS & TRANSACTION
+In order to maintain durability and availability of GTTs'session-specific data,
+their storage information, statistics, and transaction information is managed
+in a local hash table tt_storage_local_hash.
+
+3) DDL
+A shared hash table active_gtt_shared_hash is added to track the state of the
+GTT in a different session. This information is recorded in the hash table
+during the DDL execution of the GTT.
+
+4) LOCK
+The data stored in a GTT can only be modified or accessed by owning session.
+The statements that only modify data in a GTT do not need a high level of table
+locking.
+The operations making those changes include truncate GTT, Vacuum/Cluster GTT,
+and Lock GTT.
+
+Detailed design
+-----------------------------------------
+
+1. CATALOG
+1.1 relpersistence
+define RELPERSISTENCEGLOBALTEMP 'g'
+Mark Global Temporary Table in pg_class relpersistence to 'g'. The relpersistence
+of indexes created on the GTT, sequences on GTT and toast tables on GTT are
+also set to 'g'
+
+1.2 on commit clause
+LTT's status associated with on commit DELETE ROWS and on commit PRESERVE ROWS
+is not stored in catalog. Instead, GTTs need a bool value on_commit_delete_rows
+in reloptions which is shared among sessions.
+
+1.3 gram.y
+GTT is already supported in syntax tree. We remove the warning message
+"GLOBAL is deprecated in temporary table creation" and mark
+relpersistence = RELPERSISTENCEGLOBALTEMP.
+
+2. STORAGE INFO & STATISTICS DATA & TRANSACTION INFO
+2.1. gtt_storage_local_hash
+Each backend creates a local hashtable gtt_storage_local_hash to track a GTT's
+storage file information, statistics, and transaction information.
+
+2.2 GTT storage file info track
+1) When one session inserts data into a GTT for the first time, record the
+storage info to gtt_storage_local_hash.
+2) Use beforeshmemexit to ensure that all files of session GTT are deleted when
+the session exits.
+
+2.3 statistics info
+1) relpages reltuples relallvisible
+2) The statistics of each column from pg_statistic
+All the above information is stored in gtt_storage_local_hash.
+When doing vacuum or analyze, GTT's statistic is updated, which is used by
+the SQL planner.
+The statistics summarizes only data in the current session.
+
+2.3 transaction info track
+frozenxid minmulti from pg_class is stored to gtt_storage_local_hash.
+
+4 DDL
+4.1. active_gtt_shared_hash
+This is the hash table created in shared memory to trace the GTT files initialized
+in each session. Each hash entry contains a bitmap that records the backendid of
+the initialized GTT file. With this hash table, we know which backend/session
+is using this GTT. Such information is used during GTT's DDL operations.
+
+4.1 DROP GTT
+One GTT is allowed to be deleted when there is only one session using the table
+and the session is the current session.
+After holding the AccessExclusiveLock lock on GTT, active_gtt_shared_hash
+is checked to ensure that.
+
+4.2 ALTER GTT/DROP INDEX ON GTT
+Same as drop GTT.
+
+4.3 CREATE INDEX ON GTT
+1) create index on GTT statements build index based on local data in a session.
+2) After the index is created, record the index metadata to the catalog.
+3) Other sessions can enable or disable the local GTT index.
+
+5 LOCK
+
+5.1 TRUNCATE GTT
+The truncate GTT command uses RowExclusiveLock, not AccessExclusiveLock, because
+this command only cleans up local data and local buffers in current session.
+
+5.2 CLUSTER GTT/VACUUM FULL GTT
+Same as truncate GTT.
+
+5.3 Lock GTT
+A lock GTT statement does not hold any table locks.
+
+6 MVCC commit log(clog) cleanup
+
+The GTT storage file contains transaction information. Queries for GTT data rely
+on transaction information such as clog. The transaction information required by
+each session may be completely different. We need to ensure that the transaction
+information of the GTT data is not cleaned up during its lifetime and that
+transaction resources are recycled at the instance level.
+
+6.1 The session level GTT oldest frozenxid
+1) To manage all GTT transaction information, add session level oldest frozenxid
+in each session. When one GTT is created or removed, record the session level
+oldest frozenxid and store it in MyProc.
+2) When vacuum advances the database's frozenxid, session level oldest frozenxid
+should be considered. This is acquired by searching all of MyProc. This way,
+we can avoid the clog required by GTTs to be cleaned.
+
+6.2 vacuum GTT
+Users can perform vacuum over a GTT to clean up local data in the GTT.
+
+6.3 autovacuum GTT
+Autovacuum skips all GTTs, because the data in GTTs is only visible in current
+session.
+
+7 OTHERS
+Parallel query
+Planner does not produce parallel query plans for SQL related to GTT. Because
+GTT private data cannot be accessed across processes.
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 245d536372..a38b594898 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -441,6 +441,7 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index afa830d924..fc56199eae 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -62,6 +62,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,
@@ -306,7 +308,8 @@ heap_create(const char *relname,
 			bool mapped_relation,
 			bool allow_system_table_mods,
 			TransactionId *relfrozenxid,
-			MultiXactId *relminmxid)
+			MultiXactId *relminmxid,
+			bool skip_create_storage)
 {
 	bool		create_storage;
 	Relation	rel;
@@ -371,7 +374,9 @@ heap_create(const char *relname,
 	 * storage is already created, so don't do it here.  Also don't create it
 	 * for relkinds without physical storage.
 	 */
-	if (!RELKIND_HAS_STORAGE(relkind) || OidIsValid(relfilenode))
+	if (!RELKIND_HAS_STORAGE(relkind) ||
+		OidIsValid(relfilenode) ||
+		skip_create_storage)
 		create_storage = false;
 	else
 	{
@@ -429,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:
@@ -1000,6 +1005,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -1038,8 +1044,21 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/*
+	 * The transaction information of the global temporary table is stored
+	 * in the local hash table, not in catalog.
+	 */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1305,7 +1324,8 @@ heap_create_with_catalog(const char *relname,
 							   mapped_relation,
 							   allow_system_table_mods,
 							   &relfrozenxid,
-							   &relminmxid);
+							   &relminmxid,
+							   false);
 
 	Assert(relid == RelationGetRelid(new_rel_desc));
 
@@ -1412,6 +1432,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1995,6 +2016,19 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/*
+	 * Only when other sessions are not using this Global temporary table,
+	 * is it allowed to DROP it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot drop global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3271,7 +3305,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3283,7 +3317,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3329,8 +3363,16 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		/*
+		 * Truncate global temporary table only clears local data,
+		 * so only low-level locks need to be held.
+		 */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3363,6 +3405,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3371,23 +3414,47 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	/* For global temporary table only */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		/*
+		 * If this GTT is not initialized in current backend, there is
+		 * no needs to anything.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(rel)))
+			return;
+
+		/*
+		 * Truncate GTT only clears local data, so only low-level locks
+		 * need to be held.
+		 */
+		lockmode = RowExclusiveLock;
+	}
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	/*
+	 * After the data is cleaned up on the GTT, the transaction information
+	 * for the data(stored in local hash table) is also need reset.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(RelationGetRelid(rel), 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 50b7a16bce..fd182b612e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -54,6 +54,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"
@@ -732,6 +733,29 @@ index_create(Relation heapRelation,
 	char		relkind;
 	TransactionId relfrozenxid;
 	MultiXactId relminmxid;
+	bool		skip_create_storage = false;
+
+	/* For global temporary table only */
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temporary table with concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot reindex global temporary tables concurrently")));
+
+		/*
+		 * For the case that some backend is applied relcache message to create
+		 * an index on a global temporary table, if this table in the current
+		 * backend are not initialized, the creation of index storage on the
+		 * table are also skipped.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+		{
+			skip_create_storage = true;
+			flags |= INDEX_CREATE_SKIP_BUILD;
+		}
+	}
 
 	/* constraint flags can only be set when a constraint is requested */
 	Assert((constr_flags == 0) ||
@@ -939,7 +963,8 @@ index_create(Relation heapRelation,
 								mapped_relation,
 								allow_system_table_mods,
 								&relfrozenxid,
-								&relminmxid);
+								&relminmxid,
+								skip_create_storage);
 
 	Assert(relfrozenxid == InvalidTransactionId);
 	Assert(relminmxid == InvalidMultiXactId);
@@ -2107,7 +2132,7 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 * lock (see comments in RemoveRelations), and a non-concurrent DROP is
 	 * more efficient.
 	 */
-	Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+	Assert(!RelpersistenceTsTemp(get_rel_persistence(indexId)) ||
 		   (!concurrent && !concurrent_lock_mode));
 
 	/*
@@ -2139,6 +2164,20 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/*
+	 * Allow to drop index on global temporary table when only current
+	 * backend use it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation) &&
+		is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+	{
+		ereport(ERROR,
+			(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+			 errmsg("cannot drop index %s or global temporary table %s",
+					RelationGetRelationName(userIndexRelation), RelationGetRelationName(userHeapRelation)),
+			 errhint("Because the index is created on the global temporary table and other backend attached it.")));
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2747,6 +2786,7 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = RELATION_IS_GLOBAL_TEMP(rel);
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2841,20 +2881,37 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
-		{
-			rd_rel->relpages = (int32) relpages;
-			dirty = true;
-		}
-		if (rd_rel->reltuples != (float4) reltuples)
+		/* For global temporary table */
+		if (is_gtt)
 		{
-			rd_rel->reltuples = (float4) reltuples;
-			dirty = true;
+			/* Update GTT'statistics into local relcache */
+			rel->rd_rel->relpages = (int32) relpages;
+			rel->rd_rel->reltuples = (float4) reltuples;
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+
+			/* Update GTT'statistics into local hashtable */
+			up_gtt_relstats(RelationGetRelid(rel), relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+		else
 		{
-			rd_rel->relallvisible = (int32) relallvisible;
-			dirty = true;
+			if (rd_rel->relpages != (int32) relpages)
+			{
+				rd_rel->relpages = (int32) relpages;
+				dirty = true;
+			}
+
+			if (rd_rel->reltuples != (float4) reltuples)
+			{
+				rd_rel->reltuples = (float4) reltuples;
+				dirty = true;
+			}
+
+			if (rd_rel->relallvisible != (int32) relallvisible)
+			{
+				rd_rel->relallvisible = (int32) relallvisible;
+				dirty = true;
+			}
 		}
 	}
 
@@ -2967,6 +3024,26 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, progress_index, progress_vals);
 	}
 
+	/* For build index on global temporary table */
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		/*
+		 * If the storage for the index in this session is not initialized,
+		 * it needs to be created.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			/* Before create init storage, fix the local Relcache first */
+			force_enable_gtt_index(indexRelation);
+
+			Assert(gtt_storage_attached(RelationGetRelid(heapRelation)));
+
+			/* Init storage for index */
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3522,6 +3599,20 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	if (!OidIsValid(heapId))
 		return;
 
+	/*
+	 * For reindex on global temporary table, If the storage for the index
+	 * in current backend is not initialized, nothing is done.
+	 */
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		!gtt_storage_attached(indexId))
+	{
+		/* Suppress use of the target index while rebuilding it */
+		SetReindexProcessing(heapId, indexId);
+		/* Re-allow use of target index */
+		ResetReindexProcessing();
+		return;
+	}
+
 	if ((params->options & REINDEXOPT_MISSING_OK) != 0)
 		heapRelation = try_table_open(heapId, ShareLock);
 	else
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 005e029c38..5e59b47969 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -655,6 +655,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/* Do not allow create global temporary table in temporary schemas */
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp table in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index cba7a9ada0..9cf6802f9b 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,6 +27,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -61,6 +62,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			temprelOid;			/* InvalidOid if not a global temporary rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -115,7 +117,7 @@ AddPendingSync(const RelFileNode *rnode)
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -126,7 +128,12 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/*
+		 * Global temporary table and local temporary table use same
+		 * design on storage module.
+		 */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -154,6 +161,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
@@ -165,6 +173,21 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		AddPendingSync(&rnode);
 	}
 
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		Assert(rel && RELATION_IS_GLOBAL_TEMP(rel));
+
+		/*
+		 * Remember the reloid of global temporary table, which is used for
+		 * transaction commit or rollback.
+		 * see smgrDoPendingDeletes.
+		 */
+		pending->temprelOid = RelationGetRelid(rel);
+
+		/* Remember global temporary table storage info to localhash */
+		remember_gtt_storage_info(rnode, rel);
+	}
+
 	return srel;
 }
 
@@ -201,11 +224,20 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/*
+	 * Remember the reloid of global temporary table, which is used for
+	 * transaction commit or rollback.
+	 * see smgrDoPendingDeletes.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->temprelOid = RelationGetRelid(rel);
+
 	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
@@ -602,6 +634,7 @@ smgrDoPendingDeletes(bool isCommit)
 	int			nrels = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -631,14 +664,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->temprelOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -648,12 +685,21 @@ smgrDoPendingDeletes(bool isCommit)
 
 	if (nrels > 0)
 	{
+		int	i;
+
 		smgrdounlinkall(srels, nrels, false);
 
-		for (int i = 0; i < nrels; i++)
+		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* Delete global temporary table info in localhash */
+			if (gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000000..6aec275c73
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1651 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  The body implementation of Global Temparary table.
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *	  See src/backend/catalog/GTT_README for Global temparary table's
+ *	  requirements and design.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_info_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+int		vacuum_gtt_defer_check_age = 0;
+
+/*
+ * The Global temporary table's shared hash table data structure
+ */
+typedef struct gtt_ctl_data
+{
+	LWLock		lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+/* record this global temporary table in which backends are being used */
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+/*
+ * The Global temporary table's local hash table data structure
+ */
+/* Record the storage information and statistical information of the global temporary table */
+typedef struct
+{
+	Oid			relfilenode;
+	Oid			spcnode;
+
+	/* pg_class relstat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+
+	/* pg_statistic column stat */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+
+	Oid			oldrelid;			/* remember the source of relid, before the switch relfilenode. */
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_free_statistics(gtt_relfilenode *rnode);
+static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok);
+static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok);
+static Bitmapset *copy_active_gtt_bitmap(Oid relid);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+/*
+ * Calculate shared hash table entry size for GTT.
+ */
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	/* hash entry header size */
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	/*
+	 * hash entry data size
+	 * this is a bitmap in shared memory, each backend have a bit.
+	 */
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+/*
+ * Calculate shared hash table max size for GTT.
+ */
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	/* shared hash header size */
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	/* hash entry size */
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	/* max size */
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+/*
+ * Initialization shared hash table for GTT.
+ */
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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();
+	}
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(gtt_fnode);
+	info.entrysize = action_gtt_shared_hash_entry_size();
+	active_gtt_shared_hash =
+		ShmemInitHash("active gtt shared hash",
+						gtt_shared_ctl->max_entry,
+						gtt_shared_ctl->max_entry,
+						&info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+/*
+ * Record GTT relid to shared hash table, which means that current backend is using this GTT.
+ */
+static void
+gtt_storage_checkin(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			found;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *)&(fnode), 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_global_temporary_table.")));
+	}
+
+	if (!found)
+	{
+		int			wordnum;
+
+		/* init bitmap */
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	/* record itself in bitmap */
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+/*
+ * Remove the GTT relid record from the shared hash table which means that current backend is
+ * not use this GTT.
+ */
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode				fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	if (!skiplock)
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		if (!skiplock)
+			LWLockRelease(&gtt_shared_ctl->lock);
+
+		if (isCommit)
+			elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+	/* remove itself from bitmap */
+	bms_del_member(entry->map, MyBackendId);
+
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+
+	if (!skiplock)
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+	return;
+}
+
+/*
+ * Gets usage information for a GTT from shared hash table.
+ * The information is in the form of bitmap.
+ * Quickly copy the entire bitmap from shared memory and return it.
+ * that to avoid holding locks for a long time.
+ */
+static Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	Bitmapset	*map_copy = NULL;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		return NULL;
+	}
+
+	Assert(entry->map);
+
+	/* copy the entire bitmap */
+	if (!bms_is_empty(entry->map))
+		map_copy = bms_copy(entry->map);
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return map_copy;
+}
+
+/*
+ * Check if there are other backends using this GTT besides the current backend.
+ */
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool		in_use = false;
+	int			num_use = 0;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		return false;
+	}
+
+	Assert(entry->map);
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+	/* how many backend are using this GTT */
+	num_use = bms_num_members(entry->map);
+	if (num_use == 0)
+		in_use = false;
+	else if (num_use == 1)
+	{
+		/* check if this is itself */
+		if(bms_is_member(MyBackendId, entry->map))
+			in_use = false;
+		else
+			in_use = true;
+	}
+	else
+		in_use = true;
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return in_use;
+}
+
+/*
+ * Record GTT information to local hash.
+ * They include GTT storage info, transaction info and statistical info.
+ */
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+	gtt_local_hash_entry	*entry;
+	MemoryContext			oldcontext;
+	gtt_relfilenode			*new_node = NULL;
+	Oid						relid = RelationGetRelid(rel);
+	int 					natts = 0;
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	/* First time through: initialize the hash table */
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		HASHCTL		ctl;
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_info_context =
+			AllocSetContextCreate(CacheMemoryContext,
+								"gtt info context",
+								ALLOCSET_DEFAULT_SIZES);
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		ctl.hcxt = gtt_info_context;
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+	}
+
+	Assert(CacheMemoryContext);
+	Assert(gtt_info_context);
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	entry = gtt_search_by_relid(relid, true);
+	if (!entry)
+	{
+		bool		found = false;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash,
+						(void *) &relid, HASH_ENTER, &found);
+
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+		entry->oldrelid = InvalidOid;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			/* record the on commit clause */
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			gtt_storage_checkin(relid);
+		}
+	}
+
+	/* record storage info relstat columnstats and transaction info to relfilenode list */
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	new_node->natts = 0;
+	new_node->attnum = NULL;
+	new_node->att_stat_tups = NULL;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* init column stats structure */
+	natts = RelationGetNumberOfAttributes(rel);
+	new_node->attnum = palloc0(sizeof(int) * natts);
+	new_node->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+	new_node->natts = natts;
+
+	/* only heap have transaction info */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+
+		/**/
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+
+	/* Registration callbacks are used to trigger cleanup during process exit */
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+/*
+ * Remove GTT information from local hash when transaction commit/rollback.
+ */
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	gtt_relfilenode *d_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+		return;
+	}
+
+	d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+		else if (entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+			gtt_relfilenode *gttnode2 = NULL;
+
+			/*
+			 * For cluster GTT rollback.
+			 * We need to roll back the exchange relfilenode operation.
+			 */
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+			Assert(gttnode2->relfilenode == rnode.relNode);
+			Assert(list_length(entry->relfilenode_list) == 1);
+			/* rollback switch relfilenode */
+			gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+									   entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+									   false);
+			/* clean up footprint */
+			entry2->oldrelid = InvalidOid;
+
+			/* temp relfilenode need free */
+			d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+			Assert(d_rnode);
+		}
+		else
+		{
+			/* rollback transaction */
+			if (entry->relfilenode_list == NIL)
+			{
+				if (entry->relkind == RELKIND_RELATION ||
+					entry->relkind == RELKIND_SEQUENCE)
+					gtt_storage_checkout(relid, false, isCommit);
+
+				hash_search(gtt_storage_local_hash,
+						(void *) &(relid), HASH_REMOVE, NULL);
+			}
+
+			return;
+		}
+	}
+
+	/* Clean up transaction info from Local order list and MyProc */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+
+		/* this is valid relfrozenxid */
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	/* delete relfilenode from rel entry */
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	gtt_free_statistics(d_rnode);
+
+	if (entry->relfilenode_list == NIL)
+	{
+		/* this means we truncate this GTT at current backend */
+
+		/* tell shared hash that current backend will no longer use this GTT */
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(relid, false, isCommit);
+
+		if (isCommit && entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+
+			/* commit transaction at cluster GTT, need clean up footprint */
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			entry2->oldrelid = InvalidOid;
+		}
+
+		hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_REMOVE, NULL);
+	}
+
+	return;
+}
+
+/*
+ * Check if current backend is using this GTT.
+ */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool found = false;
+	gtt_local_hash_entry *entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (!OidIsValid(relid))
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry)
+		found = true;
+
+	return found;
+}
+
+/*
+ * When backend exit, bulk cleaning all GTT storage and local buffer of this backend.
+ */
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS				status;
+	gtt_local_hash_entry		*entry;
+	SMgrRelation	*srels = NULL;
+	Oid				*relids = NULL;
+	char			*relkinds = NULL;
+	int			nrels = 0,
+				nfiles = 0,
+				maxrels = 0,
+				maxfiles = 0,
+				i = 0;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	/* Search all relfilenode for GTT in current backend */
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel;
+			RelFileNode		rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel = smgropen(rnode, MyBackendId);
+
+			if (maxfiles == 0)
+			{
+				maxfiles = 32;
+				srels = palloc(sizeof(SMgrRelation) * maxfiles);
+			}
+			else if (maxfiles <= nfiles)
+			{
+				maxfiles *= 2;
+				srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+			}
+
+			srels[nfiles++] = srel;
+		}
+
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			relids  = palloc(sizeof(Oid) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			relids  = repalloc(relids , sizeof(Oid) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		relkinds[nrels] = entry->relkind;
+		relids[nrels] = entry->relid;
+		nrels++;
+	}
+
+	/* drop local buffer and storage */
+	if (nfiles > 0)
+	{
+		/* Need to ensure we have a usable transaction. */
+                AbortOutOfAnyTransaction();
+
+		smgrdounlinkall(srels, nfiles, false);
+		for (i = 0; i < nfiles; i++)
+			smgrclose(srels[i]);
+
+		pfree(srels);
+	}
+
+	if (nrels)
+	{
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+		for (i = 0; i < nrels; i++)
+		{
+			/* tell shared hash */
+			if (relkinds[i] == RELKIND_RELATION ||
+				relkinds[i] == RELKIND_SEQUENCE)
+				gtt_storage_checkout(relids[i], true, false);
+		}
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+		pfree(relids);
+		pfree(relkinds);
+	}
+
+	/* set to global area */
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;
+
+	return;
+}
+
+/*
+ * Update GTT relstats(relpage/reltuple/relallvisible)
+ * to local hash.
+ */
+void
+up_gtt_relstats(Oid relid,
+					BlockNumber num_pages,
+					double num_tuples,
+					BlockNumber num_all_visible_pages,
+					TransactionId relfrozenxid,
+					TransactionId relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (!OidIsValid(relid))
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (num_pages > 0 &&
+		gtt_rnode->relpages != (int32)num_pages)
+		gtt_rnode->relpages = (int32)num_pages;
+
+	if (num_tuples > 0 &&
+		gtt_rnode->reltuples != (float4)num_tuples)
+		gtt_rnode->reltuples = (float4)num_tuples;
+
+	/* only heap contain transaction information and relallvisible */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (num_all_visible_pages > 0 &&
+			gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+		{
+			gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+		}
+
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNextTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			/* set to local order list */
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			/* set to global area */
+			set_gtt_session_relfrozenxid();
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search GTT relstats(relpage/reltuple/relallvisible)
+ * from local has.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update GTT info(definition is same as pg_statistic)
+ * to local hash.
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	MemoryContext			oldcontext;
+	int						i = 0;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return;
+
+	Assert(entry->relid == reloid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (gtt_rnode->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	/* switch context to gtt_info_context for store tuple at heap_form_tuple */
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == 0)
+		{
+			gtt_rnode->attnum[i] = attnum;
+			break;
+		}
+		else if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			heap_freetuple(gtt_rnode->att_stat_tups[i]);
+			gtt_rnode->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < gtt_rnode->natts);
+	Assert(gtt_rnode->att_stat_tups[i] == NULL);
+	gtt_rnode->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search GTT statistic info(definition is same as pg_statistic)
+ * from local hash.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	int						i = 0;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return NULL;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return NULL;
+
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			return gtt_rnode->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Insert a RelfrozenXID into the list and keep the list in order.
+ */
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell		*cell;
+	int				i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Remove a RelfrozenXID from order list gtt_session_relfrozenxid_list.
+ */
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+/*
+ * Update of backend Level oldest relfrozenxid to MyProc.
+ * This makes each backend's oldest RelFrozenxID globally visible.
+ */
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->backend_gtt_frozenxid != gtt_frozenxid)
+		MyProc->backend_gtt_frozenxid = gtt_frozenxid;
+}
+
+/*
+ * Get GTT column level data statistics.
+ */
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo 	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate *tupstore;
+	HeapTuple		tuple;
+	Relation		rel = NULL;
+	Oid				reloid = PG_GETARG_OID(0);
+	int				attnum = PG_GETARG_INT32(1);
+	char			rel_persistence;
+	TupleDesc	  	tupdesc;
+	MemoryContext 	oldcontext;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	/* get data from local hash */
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, NoLock);
+	relation_close(pg_tatistic, AccessShareLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get GTT table level data statistics.
+ */
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate	*tupstore;
+	TupleDesc		tupdesc;
+	MemoryContext	oldcontext;
+	HeapTuple		tuple;
+	Oid				reloid = PG_GETARG_OID(0);
+	Oid				relnode = 0;
+	char			rel_persistence;
+	BlockNumber		relpages = 0;
+	BlockNumber		relallvisible = 0;
+	uint32			relfrozenxid = 0;
+	uint32			relminmxid = 0;
+	double			reltuples = 0;
+	Relation		rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[6];
+		bool	isnull[6];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get a list of backend pids that are currently using this GTT.
+ */
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	PGPROC			*proc = NULL;
+	Bitmapset		*map = NULL;
+	Tuplestorestate *tupstore;
+	TupleDesc		tupdesc;
+	MemoryContext	oldcontext;
+	HeapTuple		tuple;
+	Oid				reloid = PG_GETARG_OID(0);
+	char			rel_persistence;
+	Relation		rel = NULL;
+	pid_t			pid = 0;
+	int				backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	/* get data from share hash */
+	map = copy_active_gtt_bitmap(reloid);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			/* backendid map to process pid */
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get backend level oldest relfrozenxid of each backend using GTT in current database.
+ */
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate *tupstore;
+	int				*pids = NULL;
+	uint32			*xids = NULL;
+	TupleDesc		tupdesc;
+	MemoryContext	oldcontext;
+	HeapTuple		tuple;
+	int				num_xid = MaxBackends + 1;
+	int				i = 0;
+	int				j = 0;
+	uint32			oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+
+	/* Get backend level oldest relfrozenxid in all backend that in MyDatabaseId use GTT */
+	oldest = list_all_backend_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+/*
+ * In order to build the GTT index, force enable GTT'index.
+ */
+void
+force_enable_gtt_index(Relation index)
+{
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(RelationGetRelid(index)));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+/*
+ * Fix the local state of the GTT's index.
+ */
+void
+gtt_fix_index_backend_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid heapOid = index->rd_index->indrelid;
+
+	/* Must be GTT */
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	/*
+	 * If this GTT is not initialized in the current backend,
+	 * its index status is temporarily set to invalid(local relcache).
+	 */
+	if (gtt_storage_attached(heapOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+/*
+ * During the SQL initialization of the executor (InitPlan)
+ * Initialize storage of GTT GTT'indexes and build empty index.
+ */
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int			i;
+	Oid			toastrelid;
+
+	if (!(operation == CMD_UTILITY || operation == CMD_INSERT))
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	/* Each GTT is initialized once in each backend */
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	/* init heap storage */
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+
+		index_build(relation, index, info, true, false);
+	}
+
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+
+		/* init index storage */
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid			indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+			/* build empty index */
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+/*
+ * Release the data structure memory used to store GTT storage info.
+ */
+static void
+gtt_free_statistics(gtt_relfilenode *rnode)
+{
+	int i;
+
+	Assert(rnode);
+
+	for (i = 0; i < rnode->natts; i++)
+	{
+		if (rnode->att_stat_tups[i])
+		{
+			heap_freetuple(rnode->att_stat_tups[i]);
+			rnode->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (rnode->attnum)
+		pfree(rnode->attnum);
+
+	if (rnode->att_stat_tups)
+		pfree(rnode->att_stat_tups);
+
+	pfree(rnode);
+
+	return;
+}
+
+/*
+ * Get the current relfilenode of this GTT.
+ */
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
+/*
+ * For cluster GTT.
+ * Exchange new and old relfilenode, leave footprints ensure rollback capability.
+ */
+void
+gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint)
+{
+	gtt_local_hash_entry	*entry1;
+	gtt_local_hash_entry	*entry2;
+	gtt_relfilenode			*gtt_rnode1 = NULL;
+	gtt_relfilenode			*gtt_rnode2 = NULL;
+	MemoryContext			oldcontext;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry1 = gtt_search_by_relid(rel1, false);
+	gtt_rnode1 = gtt_search_relfilenode(entry1, relfilenode1, false);
+
+	entry2 = gtt_search_by_relid(rel2, false);
+	gtt_rnode2 = gtt_search_relfilenode(entry2, relfilenode2, false);
+
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+	entry1->relfilenode_list = list_delete_ptr(entry1->relfilenode_list, gtt_rnode1);
+	entry2->relfilenode_list = lappend(entry2->relfilenode_list, gtt_rnode1);
+
+	entry2->relfilenode_list = list_delete_ptr(entry2->relfilenode_list, gtt_rnode2);
+	entry1->relfilenode_list = lappend(entry1->relfilenode_list, gtt_rnode2);
+	MemoryContextSwitchTo(oldcontext);
+
+	if (footprint)
+	{
+		entry1->oldrelid = rel2;
+		entry2->oldrelid = rel1;
+	}
+
+	return;
+}
+
+/*
+ * Get a relfilenode used by this GTT during the transaction life cycle.
+ */
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok)
+{
+	gtt_relfilenode		*rnode = NULL;
+	ListCell			*lc;
+
+	Assert(entry);
+
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == relfilenode)
+		{
+			rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (!missing_ok && rnode == NULL)
+		elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+	return rnode;
+}
+
+/*
+ * Get one GTT info from local hash.
+ */
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry == NULL && !missing_ok)
+		elog(ERROR, "relid %u not found in local hash", relid);
+
+	return entry;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 999d984068..e25b78d055 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 426c1e6710..2909f39b9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -104,7 +105,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -185,6 +186,17 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -587,14 +599,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]));
 		}
 
 		/*
@@ -1637,7 +1650,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;
@@ -1739,31 +1752,48 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/*
+		 * For global temporary table,
+		 * Update column statistic to localhash, not catalog.
+		 */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 6487a9e3fc..9fa0d136ed 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/toasting.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
@@ -73,6 +74,12 @@ static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
 							bool verbose, bool *pSwapToastByContent,
 							TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
 static List *get_tables_to_cluster(MemoryContext cluster_context);
+static void gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables);
 
 
 /*---------------------------------------------------------------------------
@@ -390,6 +397,18 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap) &&
+		!gtt_storage_attached(RelationGetRelid(OldHeap)))
+	{
+		relation_close(OldHeap, AccessExclusiveLock);
+		pgstat_progress_end_command();
+		return;
+	}
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
@@ -774,6 +793,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	BlockNumber num_pages;
 	int			elevel = verbose ? INFO : DEBUG2;
 	PGRUsage	ru0;
+	bool		is_gtt = false;
+	uint32		gtt_relfrozenxid = 0;
+	uint32		gtt_relminmxid = 0;
 
 	pg_rusage_init(&ru0);
 
@@ -787,6 +809,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	else
 		OldIndex = NULL;
 
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		is_gtt = true;
+
 	/*
 	 * Their tuple descriptors should be exactly alike, but here we only need
 	 * assume that they have the same number of columns.
@@ -854,20 +879,38 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 						  &OldestXmin, &FreezeXid, NULL, &MultiXactCutoff,
 						  NULL);
 
-	/*
-	 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
-	 * backwards, so take the max.
-	 */
-	if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
-		TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
-		FreezeXid = OldHeap->rd_rel->relfrozenxid;
+	if (is_gtt)
+	{
+		/* Gets transaction information for global temporary table from localhash. */
+		get_gtt_relstats(OIDOldHeap,
+					NULL, NULL, NULL,
+					&gtt_relfrozenxid, &gtt_relminmxid);
+
+		if (TransactionIdIsValid(gtt_relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, gtt_relfrozenxid))
+			FreezeXid = gtt_relfrozenxid;
+
+		if (MultiXactIdIsValid(gtt_relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, gtt_relminmxid))
+			MultiXactCutoff = gtt_relminmxid;
+	}
+	else
+	{
+		/*
+		 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
+		 * backwards, so take the max.
+		 */
+		if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
+			FreezeXid = OldHeap->rd_rel->relfrozenxid;
 
-	/*
-	 * MultiXactCutoff, similarly, shouldn't go backwards either.
-	 */
-	if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
-		MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
-		MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+		/*
+		 * MultiXactCutoff, similarly, shouldn't go backwards either.
+		 */
+		if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
+			MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+	}
 
 	/*
 	 * Decide whether to use an indexscan or seqscan-and-optional-sort to scan
@@ -935,6 +978,15 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	table_close(OldHeap, NoLock);
 	table_close(NewHeap, NoLock);
 
+	/* Update relstats of global temporary table to localhash. */
+	if (is_gtt)
+	{
+		up_gtt_relstats(RelationGetRelid(NewHeap), num_pages, num_tuples, 0,
+						InvalidTransactionId, InvalidMultiXactId);
+		CommandCounterIncrement();
+		return;
+	}
+
 	/* Update pg_class to reflect the correct values of pages and tuples. */
 	relRelation = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1371,10 +1423,22 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 	 * Swap the contents of the heap relations (including any toast tables).
 	 * Also set old heap's relfrozenxid to frozenXid.
 	 */
-	swap_relation_files(OIDOldHeap, OIDNewHeap,
+	if (newrelpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		Assert(!is_system_catalog);
+		/* For global temporary table modify data in localhash, not pg_class */
+		gtt_swap_relation_files(OIDOldHeap, OIDNewHeap,
+								(OIDOldHeap == RelationRelationId),
+								swap_toast_by_content, is_internal,
+								frozenXid, cutoffMulti, mapped_tables);
+	}
+	else
+	{
+		swap_relation_files(OIDOldHeap, OIDNewHeap,
 						(OIDOldHeap == RelationRelationId),
 						swap_toast_by_content, is_internal,
 						frozenXid, cutoffMulti, mapped_tables);
+	}
 
 	/*
 	 * If it's a system catalog, queue a sinval message to flush all catcaches
@@ -1582,3 +1646,146 @@ get_tables_to_cluster(MemoryContext cluster_context)
 
 	return rvs;
 }
+
+/*
+ * For global temporary table, storage information is stored in localhash,
+ * This function like swap_relation_files except that update storage information,
+ * in the localhash, not pg_class.
+ */
+static void
+gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables)
+{
+	Relation	relRelation;
+	Oid			relfilenode1,
+				relfilenode2;
+	Relation	rel1;
+	Relation	rel2;
+
+	relRelation = table_open(RelationRelationId, RowExclusiveLock);
+
+	rel1 = relation_open(r1, AccessExclusiveLock);
+	rel2 = relation_open(r2, AccessExclusiveLock);
+
+	relfilenode1 = gtt_fetch_current_relfilenode(r1);
+	relfilenode2 = gtt_fetch_current_relfilenode(r2);
+
+	Assert(OidIsValid(relfilenode1) && OidIsValid(relfilenode2));
+	gtt_switch_rel_relfilenode(r1, relfilenode1, r2, relfilenode2, true);
+
+	CacheInvalidateRelcache(rel1);
+	CacheInvalidateRelcache(rel2);
+
+	InvokeObjectPostAlterHookArg(RelationRelationId, r1, 0,
+								 InvalidOid, is_internal);
+	InvokeObjectPostAlterHookArg(RelationRelationId, r2, 0,
+								 InvalidOid, true);
+
+	if (rel1->rd_rel->reltoastrelid || rel2->rd_rel->reltoastrelid)
+	{
+		if (swap_toast_by_content)
+		{
+			if (rel1->rd_rel->reltoastrelid && rel2->rd_rel->reltoastrelid)
+			{
+				gtt_swap_relation_files(rel1->rd_rel->reltoastrelid,
+									rel2->rd_rel->reltoastrelid,
+									target_is_pg_class,
+									swap_toast_by_content,
+									is_internal,
+									frozenXid,
+									cutoffMulti,
+									mapped_tables);
+			}
+			else
+				elog(ERROR, "cannot swap toast files by content when there's only one");
+		}
+		else
+		{
+			ObjectAddress baseobject,
+						toastobject;
+			long		count;
+
+			if (IsSystemRelation(rel1))
+				elog(ERROR, "cannot swap toast files by links for system catalogs");
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel1->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel2->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+
+			/* Register new dependencies */
+			baseobject.classId = RelationRelationId;
+			baseobject.objectSubId = 0;
+			toastobject.classId = RelationRelationId;
+			toastobject.objectSubId = 0;
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r1;
+				toastobject.objectId = rel1->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r2;
+				toastobject.objectId = rel2->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+		}
+	}
+
+	if (swap_toast_by_content &&
+		rel1->rd_rel->relkind == RELKIND_TOASTVALUE &&
+		rel2->rd_rel->relkind == RELKIND_TOASTVALUE)
+	{
+		Oid			toastIndex1,
+					toastIndex2;
+
+		/* Get valid index for each relation */
+		toastIndex1 = toast_get_valid_index(r1,
+											AccessExclusiveLock);
+		toastIndex2 = toast_get_valid_index(r2,
+											AccessExclusiveLock);
+
+		gtt_swap_relation_files(toastIndex1,
+							toastIndex2,
+							target_is_pg_class,
+							swap_toast_by_content,
+							is_internal,
+							InvalidTransactionId,
+							InvalidMultiXactId,
+							mapped_tables);
+	}
+
+	relation_close(rel1, NoLock);
+	relation_close(rel2, NoLock);
+
+	table_close(relRelation, RowExclusiveLock);
+
+	RelationCloseSmgrByOid(r1);
+	RelationCloseSmgrByOid(r2);
+
+	CommandCounterIncrement();
+}
+
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 8265b981eb..ef9d710bfe 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -290,7 +290,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP_ON_CURRENT_SESSION(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, whereClause,
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 40a54ad0bd..7895e7d99b 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -30,6 +30,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/copyfrom_internal.h"
 #include "commands/progress.h"
@@ -659,6 +660,9 @@ CopyFrom(CopyFromState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	/* Check and init global temporary table storage in current backend */
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	/*
 	 * Set up a ModifyTableState so we can let FDW(s) init themselves for
 	 * foreign-table result relation(s).
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 76774dce06..b16bb684ac 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -570,7 +570,7 @@ DefineIndex(Oid relationId,
 	 * is more efficient.  Do this before any use of the concurrent option is
 	 * done.
 	 */
-	if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+	if (stmt->concurrent && !RelpersistenceTsTemp(get_rel_persistence(relationId)))
 		concurrent = true;
 	else
 		concurrent = false;
@@ -2608,7 +2608,7 @@ ReindexIndex(RangeVar *indexRelation, ReindexParams *params, bool isTopLevel)
 	if (relkind == RELKIND_PARTITIONED_INDEX)
 		ReindexPartitions(indOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 persistence != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(persistence))
 		ReindexRelationConcurrently(indOid, params);
 	else
 	{
@@ -2717,7 +2717,7 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 	if (get_rel_relkind(heapOid) == RELKIND_PARTITIONED_TABLE)
 		ReindexPartitions(heapOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(get_rel_persistence(heapOid)))
 	{
 		result = ReindexRelationConcurrently(heapOid, params);
 
@@ -3132,7 +3132,7 @@ ReindexMultipleInternal(List *relids, ReindexParams *params)
 			   relkind != RELKIND_PARTITIONED_TABLE);
 
 		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			relpersistence != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(relpersistence))
 		{
 			ReindexParams newparams = *params;
 
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 34f2270ced..b8ea2efb5f 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -57,7 +57,10 @@ LockTableCommand(LockStmt *lockstmt)
 										  RangeVarCallbackForLockTable,
 										  (void *) &lockstmt->mode);
 
-		if (get_rel_relkind(reloid) == RELKIND_VIEW)
+		/* Lock table command ignores global temporary table. */
+		if (get_rel_persistence(reloid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+		else if (get_rel_relkind(reloid) == RELKIND_VIEW)
 			LockViewRecurse(reloid, lockstmt->mode, lockstmt->nowait, NIL);
 		else if (recurse)
 			LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait);
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 0415df9ccb..39f82bc278 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -275,8 +278,6 @@ ResetSequence(Oid seq_relid)
 	Buffer		buf;
 	HeapTupleData seqdatatuple;
 	HeapTuple	tuple;
-	HeapTuple	pgstuple;
-	Form_pg_sequence pgsform;
 	int64		startv;
 
 	/*
@@ -287,12 +288,7 @@ ResetSequence(Oid seq_relid)
 	init_sequence(seq_relid, &elm, &seq_rel);
 	(void) read_seq_tuple(seq_rel, &buf, &seqdatatuple);
 
-	pgstuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seq_relid));
-	if (!HeapTupleIsValid(pgstuple))
-		elog(ERROR, "cache lookup failed for sequence %u", seq_relid);
-	pgsform = (Form_pg_sequence) GETSTRUCT(pgstuple);
-	startv = pgsform->seqstart;
-	ReleaseSysCache(pgstuple);
+	startv = get_seqence_start_value(seq_relid);
 
 	/*
 	 * Copy the existing sequence tuple.
@@ -451,6 +447,15 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 
 	init_sequence(relid, &elm, &seqrel);
 
+	if (RELATION_IS_GLOBAL_TEMP(seqrel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(seqrel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary sequence %s when other backend attached it.",
+						RelationGetRelationName(seqrel))));
+	}
+
 	rel = table_open(SequenceRelationId, RowExclusiveLock);
 	seqtuple = SearchSysCacheCopy1(SEQRELID,
 								   ObjectIdGetDatum(relid));
@@ -611,7 +616,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +941,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1153,6 +1158,14 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	/* Initializes the storage for sequence which the global temporary table belongs. */
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1953,3 +1966,51 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+/*
+ * Get the startValue of the sequence from syscache.
+ */
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+/*
+ * Initialize sequence which global temporary table belongs.
+ */
+void
+gtt_init_seq(Relation rel)
+{
+	Datum		value[SEQ_COL_LASTCOL] = {0};
+	bool		null[SEQ_COL_LASTCOL] = {false};
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 028e8ac46b..1acbbf4221 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -602,7 +603,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
 static char GetAttributeCompression(Oid atttypid, char *compression);
-
+static OnCommitAction gtt_oncommit_option(List *options);
 
 /* ----------------------------------------------------------------
  *		DefineRelation
@@ -647,6 +648,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -658,7 +660,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * Check consistency of arguments
 	 */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !RelpersistenceTsTemp(stmt->relation->relpersistence))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -688,7 +690,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	if (RelpersistenceTsTemp(stmt->relation->relpersistence)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -789,6 +791,56 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* For global temporary table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* Check parent table */
+		if (inheritOids)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			table_close(relation, NoLock);
+		}
+
+		/* Check oncommit clause and save to reloptions */
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temporary table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1415,7 +1467,7 @@ RemoveRelations(DropStmt *drop)
 		 * relation persistence cannot be known without its OID.
 		 */
 		if (drop->concurrent &&
-			get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(get_rel_persistence(relOid)))
 		{
 			Assert(list_length(drop->objects) == 1 &&
 				   drop->removeType == OBJECT_INDEX);
@@ -1624,7 +1676,16 @@ ExecuteTruncate(TruncateStmt *stmt)
 		Relation	rel;
 		bool		recurse = rv->inh;
 		Oid			myrelid;
-		LOCKMODE	lockmode = AccessExclusiveLock;
+		LOCKMODE	lockmode;
+
+		/*
+		 * Truncate global temp table only cleans up the data in current backend,
+		 * only low-level locks are required.
+		 */
+		if (rv->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+		else
+			lockmode = AccessExclusiveLock;
 
 		myrelid = RangeVarGetRelidExtended(rv, lockmode,
 										   0, RangeVarCallbackForTruncate,
@@ -1943,6 +2004,14 @@ ExecuteTruncateGuts(List *explicit_rels,
 			continue;
 		}
 
+		/*
+		 * Skip the global temporary table that is not initialized for storage
+		 * in current backend.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			continue;
+
 		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
@@ -4010,6 +4079,16 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temporary table only current backend use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -5370,6 +5449,42 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot rewrite temporary tables of other sessions")));
 
+			if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+			{
+				if (tab->chgPersistence)
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot change global temporary table persistence setting")));
+
+				/*
+				 * The storage for the global temporary table needs to be initialized
+				 * before rewrite table.
+				 */
+				if(!gtt_storage_attached(tab->relid))
+				{
+					ResultRelInfo *resultRelInfo;
+					MemoryContext oldcontext;
+					MemoryContext ctx_alter_gtt;
+
+					ctx_alter_gtt = AllocSetContextCreate(CurrentMemoryContext,
+											"gtt alter table", ALLOCSET_DEFAULT_SIZES);
+					oldcontext = MemoryContextSwitchTo(ctx_alter_gtt);
+
+					resultRelInfo = makeNode(ResultRelInfo);
+					InitResultRelInfo(resultRelInfo, OldHeap,
+									1, NULL, 0);
+					if (resultRelInfo->ri_RelationDesc->rd_rel->relhasindex &&
+						resultRelInfo->ri_IndexRelationDescs == NULL)
+						ExecOpenIndices(resultRelInfo, false);
+
+					init_gtt_storage(CMD_UTILITY, resultRelInfo);
+					ExecCloseIndices(resultRelInfo);
+
+					MemoryContextSwitchTo(oldcontext);
+					MemoryContextDelete(ctx_alter_gtt);
+				}
+			}
+
 			/*
 			 * Select destination tablespace (same as original unless user
 			 * requested a change)
@@ -8893,6 +9008,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -13572,6 +13693,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -13770,6 +13894,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temporary table");
+
 	/* Check first if relation can be moved to new tablespace */
 	if (!CheckRelationTableSpaceMove(rel, newTableSpace))
 	{
@@ -14074,7 +14201,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,
@@ -15672,6 +15799,7 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
 	switch (rel->rd_rel->relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -18614,3 +18742,40 @@ GetAttributeCompression(Oid atttypid, char *compression)
 
 	return cmethod;
 }
+
+/*
+ * Parse the on commit clause for the temporary table
+ */
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			bool	res = false;
+			char	*sval = defGetString(def);
+
+			/* It has to be a Boolean value */
+			if (!parse_bool(sval, &res))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
+
+			if (res)
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7421d7cfbd..f361671b9f 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"
@@ -1301,6 +1302,22 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = RELATION_IS_GLOBAL_TEMP(relation);
+
+	 /* For global temporary table */
+	if (is_gtt)
+	{
+		/* Store relation statistics and transaction information to the localhash */
+		up_gtt_relstats(RelationGetRelid(relation),
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+
+		/* Update relation statistics to local relcache */
+		relation->rd_rel->relpages = (int32) num_pages;
+		relation->rd_rel->reltuples = (float4) num_tuples;
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1314,17 +1331,23 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (!is_gtt &&
+		pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (!is_gtt &&
+		pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (!is_gtt &&
+		pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1369,7 +1392,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNextTransactionId(),
@@ -1380,7 +1404,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1488,6 +1513,13 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/*
+		 * The relfrozenxid for a global temporary talble is stored in localhash,
+		 * not pg_class, See list_all_session_gtt_frozenxids()
+		 */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1545,6 +1577,43 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/* If enable global temporary table */
+	if (max_active_gtt > 0)
+	{
+		TransactionId	safe_age;
+		/*  */
+		TransactionId	oldest_gtt_frozenxid =
+			list_all_backend_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid))
+		{
+			safe_age = oldest_gtt_frozenxid + vacuum_gtt_defer_check_age;
+			if (safe_age < FirstNormalTransactionId)
+				safe_age += FirstNormalTransactionId;
+
+			/*
+			 * We tolerate that the minimum age of gtt is less than
+			 * the minimum age of conventional tables, otherwise it will
+			 * throw warning message.
+			 */
+			if (TransactionIdIsNormal(safe_age) &&
+				TransactionIdPrecedes(safe_age, newFrozenXid))
+			{
+				ereport(WARNING,
+					(errmsg("global temp table oldest relfrozenxid %u is the oldest in the entire db",
+							oldest_gtt_frozenxid),
+					 errdetail("The oldest relfrozenxid in pg_class is %u", newFrozenXid),
+					 errhint("If they differ greatly, please consider cleaning up the data in global temp table.")));
+			}
+
+			/*
+			 * We need to ensure that the clog required by gtt is not cleand.
+			 */
+			if (TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+				newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1896,6 +1965,19 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	/*
+	 * Skip those global temporary table that are not initialized in
+	 * current backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel) &&
+		!gtt_storage_attached(RelationGetRelid(rel)))
+	{
+		relation_close(rel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index f2642dba6c..e088101ccd 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -530,6 +530,12 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	/* Global temporary table are not sensible. */
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b3ce4bae53..611e3f18a7 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -784,6 +784,10 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		/* This is one kind of temp table */
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 606c920b06..c4c39c99cf 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
@@ -533,6 +534,9 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	/* Init storage for partitioned global temporary table in current backend */
+	init_gtt_storage(mtstate->operation, leaf_part_rri);
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 379b056310..b2c2f9bd76 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -38,6 +38,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -633,6 +634,9 @@ ExecInsert(ModifyTableState *mtstate,
 		resultRelInfo->ri_IndexRelationDescs == NULL)
 		ExecOpenIndices(resultRelInfo, onconflict != ONCONFLICT_NONE);
 
+	/* Init storage for global temporary table in current backend */
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	/*
 	 * BEFORE ROW INSERT Triggers.
 	 *
@@ -2799,6 +2803,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		i++;
 	}
 
+
 	/*
 	 * Now we may initialize the subplan.
 	 */
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 353454b183..b6c30e88e0 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -48,7 +48,7 @@
 #include "partitioning/partprune.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/lsyscache.h"
-
+#include "utils/rel.h"
 
 /* results of subquery_is_pushdown_safe */
 typedef struct pushdown_safety_info
@@ -619,7 +619,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			if (RelpersistenceTsTemp(get_rel_persistence(rte->relid)))
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 1868c4eff4..d4a958f8e3 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -5917,7 +5917,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
-	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+	if (RELATION_IS_TEMP(heap) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index c5194fdbbf..38d7c65854 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -30,6 +30,7 @@
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temporary table in current backend */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 9cede29d6a..9b9e5c04f9 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2892,6 +2892,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 9ee90e3f13..c0cd0dbd45 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3403,17 +3403,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; }
@@ -11685,19 +11679,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 7465919044..48b8a68984 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -82,6 +82,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);
 
 
 /*
@@ -3665,3 +3666,53 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * Like function isQueryUsingTempRelation_walker
+ * return true if any relation underlying
+ * the query is a global temporary table.
+ */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* Check if the query uses global temporary table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index d5b67d48cf..93a5f43b06 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -459,6 +459,13 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	/*
+	 * If a sequence is bound to a global temporary table, then the sequence
+	 * must been "global temporary"
+	 */
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
@@ -3359,6 +3366,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		cxt.isforeign = false;
 	}
 	cxt.relation = stmt->relation;
+	/* Sets the table persistence to the context */
+	cxt.relation->relpersistence = RelationGetRelPersistence(rel);
 	cxt.rel = rel;
 	cxt.inhRelations = NIL;
 	cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index d516df0ac5..5a2dc7c3c4 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2158,6 +2158,14 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/*
+			 * Aotuvacuum cannot vacuum the private data stored in each backend
+			 * that belongs to global temporary table, so skip them.
+			 */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2224,7 +2232,7 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		if (RelpersistenceTsTemp(classForm->relpersistence))
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 4b296a22c4..a184a4176c 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -2944,6 +2945,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * Returns 0 if this global temporary table is not initialized in current
+	 * backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 3e4ec53a97..09f676d6a6 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -23,6 +23,7 @@
 #include "access/syncscan.h"
 #include "access/twophase.h"
 #include "commands/async.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
@@ -150,6 +151,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
@@ -222,6 +224,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* For global temporary table shared hashtable */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 42a89fc5dc..9f80ad097c 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -65,6 +65,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -5078,3 +5079,78 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temporary table.
+ */
+int
+list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct *arrayP = procArray;
+	TransactionId result = InvalidTransactionId;
+	int			index;
+	uint8			flags = 0;
+	int			i = 0;
+
+	/* return 0 if feature is disabled */
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+		*n = 0;
+	}
+
+	/* Disable in standby node */
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		uint8           statusFlags = ProcGlobal->statusFlags[index];
+
+		if (statusFlags & flags)
+			continue;
+
+		/* Fetch all backend that is belonging to MyDatabaseId */
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->backend_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->backend_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->backend_gtt_frozenxid, result))
+				result = proc->backend_gtt_frozenxid;
+
+			/* save backend pid and backend level oldest relfrozenxid */
+			if (max_size > 0)
+			{
+				pids[i] = proc->pid;
+				xids[i] = proc->backend_gtt_frozenxid;
+				i++;
+			}
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (max_size > 0)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 55b9d7970e..7549fe0c57 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -175,7 +175,9 @@ static const char *const BuiltinTrancheNames[] = {
 	/* LWTRANCHE_PARALLEL_APPEND: */
 	"ParallelAppend",
 	/* LWTRANCHE_PER_XACT_PREDICATE_LIST: */
-	"PerXactPredicateList"
+	"PerXactPredicateList",
+	/* LWTRANCHE_GTT_CTL */
+	"GlobalTempTableControl"
 };
 
 StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 2575ea1ca0..16e7e0825e 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -393,6 +393,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;	/* init backend level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
@@ -578,6 +579,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;	/* init backend level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 3c70bb5943..12b3000e5e 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -982,6 +982,13 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/*
+			 * For global temporary table ,each backend has its own storage,
+			 * also only sees its own storage. Use Backendid to identify them.
+			 */
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 0c8c05f6c2..699507a24c 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -108,6 +108,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -5115,12 +5116,26 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								/* For global temporary table, get statistic data from localhash */
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -5368,15 +5383,28 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6800,6 +6828,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;
@@ -6817,6 +6846,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6828,6 +6865,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;
@@ -6843,6 +6882,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7761,6 +7808,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))
@@ -7773,6 +7822,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7785,6 +7843,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.
@@ -7804,6 +7864,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 6bba5f8ec4..fa81808df6 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -3113,6 +3114,19 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		/* For global temporary table, get statistic data from localhash */
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index fd05615e76..e89e09e682 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -65,6 +65,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1116,6 +1117,28 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+
+				/* For global temporary table, get relstat data from localhash */
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+
+				/* And put them to local relcache */
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1173,6 +1196,8 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			/* The state of the global temporary table's index may need to be set */
+			gtt_fix_index_backend_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -1300,7 +1325,22 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+			/*
+			 * For global temporary table, get the latest relfilenode
+			 * from localhash and put it in relcache.
+			 */
+			if (OidIsValid(newrelnode) &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2248,6 +2288,9 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		/* The state of the global temporary table's index may need to be set */
+		gtt_fix_index_backend_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3486,6 +3529,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3595,28 +3642,38 @@ void
 RelationSetNewRelfilenode(Relation relation, char persistence)
 {
 	Oid			newrelfilenode;
-	Relation	pg_class;
-	HeapTuple	tuple;
+	Relation	pg_class = NULL;
+	HeapTuple	tuple = NULL;
 	Form_pg_class classform;
 	MultiXactId minmulti = InvalidMultiXactId;
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
+	/*
+	 * For global temporary table, storage information for the table is
+	 * maintained locally, not in catalog.
+	 */
+	bool		update_catalog = !RELATION_IS_GLOBAL_TEMP(relation);
 
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
 
-	/*
-	 * Get a writable copy of the pg_class tuple for the given relation.
-	 */
-	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+	memset(&classform, 0, sizeof(classform));
 
-	tuple = SearchSysCacheCopy1(RELOID,
-								ObjectIdGetDatum(RelationGetRelid(relation)));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "could not find tuple for relation %u",
-			 RelationGetRelid(relation));
-	classform = (Form_pg_class) GETSTRUCT(tuple);
+	if (update_catalog)
+	{
+		/*
+		 * Get a writable copy of the pg_class tuple for the given relation.
+		 */
+		pg_class = table_open(RelationRelationId, RowExclusiveLock);
+
+		tuple = SearchSysCacheCopy1(RELOID,
+									ObjectIdGetDatum(RelationGetRelid(relation)));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u",
+				 RelationGetRelid(relation));
+		classform = (Form_pg_class) GETSTRUCT(tuple);
+	}
 
 	/*
 	 * Schedule unlinking of the old storage at transaction commit.
@@ -3642,7 +3699,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;
@@ -3662,6 +3719,18 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 			break;
 	}
 
+	/* For global temporary table */
+	if (!update_catalog)
+	{
+		Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+		Assert(RELATION_IS_GLOBAL_TEMP(relation));
+		Assert(!RelationIsMapped(relation));
+
+		/* Make cache invalid and set new relnode to local cache. */
+		CacheInvalidateRelcache(relation);
+		relation->rd_node.relNode = relnode;
+	}
 	/*
 	 * If we're dealing with a mapped index, pg_class.relfilenode doesn't
 	 * change; instead we have to send the update to the relation mapper.
@@ -3671,7 +3740,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	 * possibly-inaccurate values of relpages etc, but those will be fixed up
 	 * later.
 	 */
-	if (RelationIsMapped(relation))
+	else if (RelationIsMapped(relation))
 	{
 		/* This case is only supported for indexes */
 		Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3717,9 +3786,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 		CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 	}
 
-	heap_freetuple(tuple);
+	if (update_catalog)
+	{
+		heap_freetuple(tuple);
 
-	table_close(pg_class, RowExclusiveLock);
+		table_close(pg_class, RowExclusiveLock);
+	}
 
 	/*
 	 * Make the pg_class row change or relation map change visible.  This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 68b62d523d..53be01d7e0 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -44,6 +44,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "commands/prepare.h"
 #include "commands/trigger.h"
@@ -152,6 +153,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temporary table feature.
+ * table schema are still saved in catalog.
+ *
+ * num > 0 means allows the database to manage multiple active tables at the same time.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2118,6 +2131,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 "
@@ -2666,6 +2688,16 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"vacuum_gtt_defer_check_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("The defer check age of GTT, used to check expired data after vacuum."),
+			NULL
+		},
+		&vacuum_gtt_defer_check_age,
+		10000, 0, 1000000,
+		NULL, NULL, NULL
+	},
+
 	/*
 	 * See also CheckRequiredParameterValues() if this parameter changes
 	 */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 8f53cc7c3b..cdd3055ba6 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2510,6 +2510,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
 		dopt->no_unlogged_table_data)
 		return;
 
+	/* Don't dump data in global temporary table/sequence */
+	if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		return;
+
 	/* Check that the data is not explicitly excluded */
 	if (simple_oid_list_member(&tabledata_exclude_oids,
 							   tbinfo->dobj.catId.oid))
@@ -15805,6 +15809,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
 		char	   *foreign = "";
+		char	   *table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15858,9 +15863,15 @@ dumpTableSchema(Archive *fout, const 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);
 
@@ -16224,6 +16235,15 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 			}
 		}
 
+		/*
+		 * Transaction information for the global temporary table is not stored
+		 * in the pg_class.
+		 */
+		if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			Assert(tbinfo->frozenxid == 0);
+			Assert(tbinfo->minmxid == 0);
+		}
 		/*
 		 * In binary_upgrade mode, arrange to restore the old relfrozenxid and
 		 * relminmxid of all vacuumable relations.  (While vacuum.c processes
@@ -16231,7 +16251,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		 * of other relations; so this "if" lacks RELKIND_TOASTVALUE, and the
 		 * child toast table is handled below.)
 		 */
-		if (dopt->binary_upgrade &&
+		else if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
 			 tbinfo->relkind == RELKIND_MATVIEW))
 		{
@@ -17234,6 +17254,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	PQExpBuffer query = createPQExpBuffer();
 	PQExpBuffer delqry = createPQExpBuffer();
 	char	   *qseqname;
+	bool	   global_temp_seq = false;
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
@@ -17243,9 +17264,12 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
-						  "FROM pg_catalog.pg_sequence "
-						  "WHERE seqrelid = '%u'::oid",
+						  "seqcache, seqcycle, "
+						  "c.relpersistence "
+						  "FROM pg_catalog.pg_sequence s, "
+						  "pg_catalog.pg_class c "
+						  "WHERE seqrelid = '%u'::oid "
+						  "and s.seqrelid = c.oid",
 						  tbinfo->dobj.catId.oid);
 	}
 	else if (fout->remoteVersion >= 80400)
@@ -17290,6 +17314,9 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
 
+	if (fout->remoteVersion >= 140000)
+		global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
 	if (strcmp(seqtype, "smallint") == 0)
@@ -17367,9 +17394,13 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	}
 	else
 	{
-		appendPQExpBuffer(query,
-						  "CREATE SEQUENCE %s\n",
-						  fmtQualifiedDumpable(tbinfo));
+		appendPQExpBuffer(query, "CREATE ");
+
+		if (global_temp_seq)
+			appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+		appendPQExpBuffer(query, "SEQUENCE %s\n",
+								  fmtQualifiedDumpable(tbinfo));
 
 		if (strcmp(seqtype, "bigint") != 0)
 			appendPQExpBuffer(query, "    AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 0c47a6b8cc..1760f372b5 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -88,7 +88,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -178,7 +178,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 5d9a26cf82..2de11d5d70 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -447,8 +449,17 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
 			 "         ON c.relnamespace = n.oid "
 			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
+			 CppAsString2(RELKIND_MATVIEW) ") AND ");
+
+	if (skip_gtt)
+	{
+		/* exclude global temp tables */
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+			"    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND ");
+	}
+
 	/* exclude possible orphaned temp tables */
+	snprintf(query + strlen(query), sizeof(query) - strlen(query),
 			 "    ((n.nspname !~ '^pg_temp_' AND "
 			 "      n.nspname !~ '^pg_toast_temp_' AND "
 			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index e23b8ca88d..729a9c61e8 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -407,7 +407,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -638,7 +638,10 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+			/* exclude global temp tables */
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -649,7 +652,10 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+		/* exclude global temp tables */
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index f7eb2349e6..54fe4e38c4 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -387,7 +387,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 2abf255798..fd3947341f 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -4067,7 +4067,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 109b22acb6..4e3e20c396 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1055,6 +1055,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2484,6 +2486,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2692,6 +2697,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 6ce480b49c..a0ccfb3d77 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -59,7 +59,8 @@ extern Relation heap_create(const char *relname,
 							bool mapped_relation,
 							bool allow_system_table_mods,
 							TransactionId *relfrozenxid,
-							MultiXactId *relminmxid);
+							MultiXactId *relminmxid,
+							bool skip_create_storage);
 
 extern Oid	heap_create_with_catalog(const char *relname,
 									 Oid relnamespace,
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 3e37729436..67652c21b5 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -175,6 +175,7 @@ DECLARE_INDEX(pg_class_tblspc_relfilenode_index, 3455, on pg_class using btree(r
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index acbcae4607..3907b4272b 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5667,6 +5667,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '9874',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '9875',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '9876',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '9877',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 0ab32b44e9..92e9f8ba48 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,7 +22,7 @@
 /* GUC variables */
 extern int	wal_skip_threshold;
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationPreTruncate(Relation rel);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000000..d48162c6b8
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,46 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern int		vacuum_gtt_defer_check_age;
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Oid relid,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void force_enable_gtt_index(Relation index);
+extern void gtt_fix_index_backend_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+extern void gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index 40544dd4c7..7b66d808fc 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 8336c2c5a2..bddcfe7256 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index c86ccdaf60..6b395551c1 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -399,6 +399,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index a8f052e484..4b4ed1a13a 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -189,6 +189,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_SHARED_TIDBITMAP,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_PER_XACT_PREDICATE_LIST,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index be67d8a861..e2f8bb5162 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -157,6 +157,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId backend_gtt_frozenxid;	/* backend level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index b01fa52139..8efffa55ac 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -94,4 +94,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index a7c3a4958e..ce98b47d74 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -282,6 +282,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 774ac5b2b1..b380c02f69 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	struct SMgrRelationData *rd_smgr;	/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -318,6 +318,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
@@ -589,11 +590,13 @@ typedef struct ViewOptions
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -601,6 +604,7 @@ typedef struct ViewOptions
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -613,6 +617,30 @@ typedef struct ViewOptions
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP_ON_CURRENT_SESSION
+ *		Test a rel is either local temp relation of this session
+ *		or global temp relation.
+ */
+#define RELATION_IS_TEMP_ON_CURRENT_SESSION(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is local temp relation or global temporary relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RelpersistenceTsTemp
+ *		Test a relpersistence is local temp relation or global temporary relation.
+ */
+#define RelpersistenceTsTemp(relpersistence) \
+	(relpersistence == RELPERSISTENCE_TEMP || \
+	 relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -658,6 +686,19 @@ typedef struct ViewOptions
 	 (relation)->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&	\
 	 !IsCatalogRelation(relation))
 
+/* For global temporary table */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/* Get on commit clause value only for global temporary table */
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+/* Get relpersistence for relation */
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
-- 
2.27.0

#315Ming Li
mli@apache.org
In reply to: wenjing (#314)
Re: [Proposal] Global temporary tables

Hi Wenjing,

Some suggestions may help:

1) It seems that no test case covers the below scenario: 2 sessions attach
the same gtt, and insert/update/select concurrently. It is better to use
the test framework in src/test/isolation like the code changes in
https://commitfest.postgresql.org/24/2233/.

2) CREATE GLOBAL TEMP SEQUENCE also need to be supported
in src/bin/psql/tab-complete.c

On Wed, Jul 14, 2021 at 10:36 AM wenjing <wjzeng2012@gmail.com> wrote:

Show quoted text

Rebase code based on the latest version.

Regards,
wenjing

#316wenjing
wjzeng2012@gmail.com
In reply to: Ming Li (#315)
3 attachment(s)
Re: [Proposal] Global temporary tables

Ming Li <mli@apache.org> 于2021年7月14日周三 上午10:56写道:

Hi Wenjing,

Some suggestions may help:

1) It seems that no test case covers the below scenario: 2 sessions attach
the same gtt, and insert/update/select concurrently. It is better to use
the test framework in src/test/isolation like the code changes in
https://commitfest.postgresql.org/24/2233/.

Thanks for pointing this out, I am working on this issue.

2) CREATE GLOBAL TEMP SEQUENCE also need to be supported
in src/bin/psql/tab-complete.c

It has been fixed in V51, please check

Regards,
wenjing

Show quoted text

On Wed, Jul 14, 2021 at 10:36 AM wenjing <wjzeng2012@gmail.com> wrote:

Rebase code based on the latest version.

Regards,
wenjing

Attachments:

0002-gtt-v51-regress.patchapplication/octet-stream; name=0002-gtt-v51-regress.patchDownload
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000000..ca2d135056
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,14 @@
+reset search_path;
+select pg_sleep(5);
+ pg_sleep 
+----------
+ 
+(1 row)
+
+drop schema gtt cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
+drop cascades to table gtt.gtt_with_seq
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000000..0ac9e228d2
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,381 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+ n 
+---
+ 9
+(1 row)
+
+commit;
+-- 0 row
+select * from gtt6;
+ n 
+---
+(0 rows)
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temporary table
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count 
+-------
+     2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count 
+-------
+     0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  The parent table must be global temporary table
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+             relname             | relkind | relpersistence |          reloptions           
+---------------------------------+---------+----------------+-------------------------------
+ p_table01                       | p       | g              | {on_commit_delete_rows=true}
+ p_table01_2017                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_2018                  | r       | g              | {on_commit_delete_rows=true}
+ p_table01_id_seq                | S       | g              | 
+ p_table02                       | p       | g              | {on_commit_delete_rows=false}
+ p_table02_2017                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_2018                  | r       | g              | {on_commit_delete_rows=false}
+ p_table02_id_seq                | S       | g              | 
+ tbl_inherits_parent             | r       | p              | 
+ tbl_inherits_parent_global_temp | r       | g              | {on_commit_delete_rows=true}
+ tbl_inherits_partition          | r       | g              | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temporary table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  could not create global temporary table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR:  insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count 
+-------
+     1
+(1 row)
+
+select count(*) from orders;
+ count 
+-------
+     0
+(1 row)
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  2 | 1
+  3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a 
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a 
+----+---
+  4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  2 |  3
+(2 rows)
+
+commit;
+select * from gtt_s_1 order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+select * from gtt_s_2 order by c1;
+ c1 | c2 
+----+----
+  1 |  2
+  2 |  4
+(2 rows)
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 1)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_gt1_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 300)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+              QUERY PLAN              
+--------------------------------------
+ Bitmap Heap Scan on gt1
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_gt1_3
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+               QUERY PLAN               
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+   Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+            QUERY PLAN             
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE:  drop cascades to 33 other objects
+DETAIL:  drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test_new
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.gtt6
+drop cascades to table gtt_function.foo
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to sequence gtt_function.seq_1
+drop cascades to table gtt_function.gtt_s_1
+drop cascades to table gtt_function.gtt_s_2
+drop cascades to table gtt_function.gt1
+drop cascades to table gtt_function.gtt_test1
+drop cascades to table gtt_function.gtt_test2
+drop cascades to table gtt_function.gtt_test3
+drop cascades to table gtt_function.gtt_test4
+drop cascades to table gtt_function.gtt_test5
+drop cascades to table gtt_function.gtt_test6
+drop cascades to table gtt_function.gtt_test7
+drop cascades to table gtt_function.gtt_test8
+drop cascades to table gtt_function.gtt_test9
+drop cascades to table gtt_function.gtt_test10
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000000..0646aaed73
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,90 @@
+set search_path=gtt,sys;
+select nextval('gtt_with_seq_c2_seq');
+ nextval 
+---------
+       1
+(1 row)
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000000..0fccf6b81c
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,343 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+rollback;
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b 
+---+---
+ 1 | 
+ 2 | 
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b 
+---+---
+ 4 | 
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+commit;
+select * from gtt3;
+ a | b 
+---+---
+ 3 | 
+ 5 | 
+(2 rows)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon |           450560 |             8192 |        499712 |          114688 |                 614400
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+      relname       | pg_relation_size | pg_table_size | pg_total_relation_size 
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 |            65536 |         65536 |                  65536
+ idx_gtt_t_kenyon_2 |            49152 |         49152 |                  49152
+(2 rows)
+
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2000
+(1 row)
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count 
+-------
+  2006
+(1 row)
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+ c1 | c2 
+----+----
+  1 |  1
+(1 row)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000000..8c0c376429
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,10 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000000..4420fdbd8f
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,80 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     0
+(1 row)
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count 
+-------
+     1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |        0 |         0 |             0
+ gtt_stats  | gtt_pkey  |        1 |         0 |             0
+(2 rows)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+reindex table gtt;
+reindex index gtt_pkey;
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible 
+------------+-----------+----------+-----------+---------------
+ gtt_stats  | gtt       |       55 |     10000 |             0
+ gtt_stats  | gtt_pkey  |       30 |     10000 |             0
+(2 rows)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats  | gtt       | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ gtt_stats  | gtt       | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE:  drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index e5ab11275d..23b2863fec 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1359,6 +1359,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 7be89178f0..d039f0fbb1 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -130,3 +130,10 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000000..2c8e5868d1
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,8 @@
+
+
+reset search_path;
+
+select pg_sleep(5);
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000000..fd8b4d3e3b
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,253 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+commit;
+-- 0 row
+select * from gtt6;
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+
+-- ok
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or  relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int)  on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+commit;
+select * from gtt_s_1 order by c1;
+select * from gtt_s_2 order by c1;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000000..d05745e313
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,44 @@
+
+
+set search_path=gtt,sys;
+
+select nextval('gtt_with_seq_c2_seq');
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000000..81a60392c2
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,154 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+rollback;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+commit;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+select count(*) from gtt_t_kenyon;
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000000..dbe84d1b80
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,19 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000000..d61b0ff828
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,46 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+reindex table gtt;
+
+reindex index gtt_pkey;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
-- 
2.27.0

0003-gtt-v51-doc.patchapplication/octet-stream; name=0003-gtt-v51-doc.patchDownload
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 15aed2f251..d2fa2219cf 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -169,32 +169,67 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     <listitem>
      <para>
       If specified, the table is created as a temporary table.
-      Temporary tables are automatically dropped at the end of a
-      session, or optionally at the end of the current transaction
-      (see <literal>ON COMMIT</literal> below).  The default
-      search_path includes the temporary schema first and so identically
-      named existing permanent tables are not chosen for new plans
+      Optionally, <literal>GLOBAL</literal> or <literal>LOCAL</literal>
+      can be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
+      They represent two types of temporary tables supported by <productname>PostgreSQL</productname>:
+      global temporary table and local temporary table. Without specified
+      GLOBAL or LOCAL, a local temporary table is created by default.
+     </para>
+
+    <para>
+     Both types of temporary tables’ data are truncated at the
+     end of a session or optionally at the end of the current transaction.
+     (see <literal>ON COMMIT</literal> below). For global temporary table,
+     its schema is reserved and reused by future sessions or transactions.
+     For local temporary table, both its data and its schema are dropped.
+    </para>
+
+    <variablelist>
+     <varlistentry>
+      <term><literal>Global Temporary Table</literal></term>
+      <listitem>
+       <para>
+        Global temporary table are defined just once and automatically exist
+        (starting with empty contents) in every session that needs them.
+        The schema definition of temporary tables is persistent and shared among sessions.
+        However, the data in temporary tables are kept private to sessions themselves,
+        even though they use same name and same schema.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>Local Temporary Table</literal></term>
+     <listitem>
+     <para>
+      Local temporary table are automatically dropped at the end of a
+      session (include schema and data). Future sessions need to create
+      their own temporary tables when they are used.
+     </para>
+     <para>
+      The default search_path includes the temporary schema first and so
+      identically named existing permanent tables are not chosen for new plans
       while the temporary table exists, unless they are referenced
       with schema-qualified names. Any indexes created on a temporary
       table are automatically temporary as well.
      </para>
+     </listitem>
+     </varlistentry>
+    </variablelist>
 
-     <para>
-      The <link linkend="autovacuum">autovacuum daemon</link> cannot
-      access and therefore cannot vacuum or analyze temporary tables.
-      For this reason, appropriate vacuum and analyze operations should be
-      performed via session SQL commands.  For example, if a temporary
-      table is going to be used in complex queries, it is wise to run
-      <command>ANALYZE</command> on the temporary table after it is populated.
-     </para>
+    <para>
+     The <link linkend="autovacuum">autovacuum daemon</link> cannot
+     access and therefore cannot vacuum or analyze temporary tables.
+     For this reason, appropriate vacuum and analyze operations should be
+     performed via session SQL commands.  For example, if a temporary
+     table is going to be used in complex queries, it is wise to run
+     <command>ANALYZE</command> on the temporary table after it is populated.
+    </para>
+    <para>
+     The Temporary table resembles the SQL standard, but has some differences.
+     see <xref linkend="sql-createtable-compatibility"/> below.
+    </para>
 
-     <para>
-      Optionally, <literal>GLOBAL</literal> or <literal>LOCAL</literal>
-      can be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
-      This presently makes no difference in <productname>PostgreSQL</productname>
-      and is deprecated; see
-      <xref linkend="sql-createtable-compatibility"/> below.
-     </para>
     </listitem>
    </varlistentry>
 
@@ -2137,13 +2172,17 @@ CREATE TABLE cities_partdef
    <title>Temporary Tables</title>
 
    <para>
-    Although the syntax of <literal>CREATE TEMPORARY TABLE</literal>
-    resembles that of the SQL standard, the effect is not the same.  In the
-    standard,
-    temporary tables are defined just once and automatically exist (starting
-    with empty contents) in every session that needs them.
-    <productname>PostgreSQL</productname> instead
-    requires each session to issue its own <literal>CREATE TEMPORARY
+    Although the syntax of <literal>CREATE GLOBAL/LOCAL TEMPORARY TABLE</literal>
+    resembles that of the SQL standard, the effect is not the same.
+    The global temporary table follows the SQL standards while local temporary
+    table does not.
+   </para>
+
+   <para>
+    First, in the standard, both global and local temporary tables are defined just
+    once and automatically exist (starting with empty contents) in every session
+    that needs them. For local temporary tables, <productname>PostgreSQL</productname>
+    instead requires each session to issue its own <literal>CREATE LOCAL TEMPORARY
     TABLE</literal> command for each temporary table to be used.  This allows
     different sessions to use the same temporary table name for different
     purposes, whereas the standard's approach constrains all instances of a
@@ -2151,29 +2190,14 @@ CREATE TABLE cities_partdef
    </para>
 
    <para>
-    The standard's definition of the behavior of temporary tables is
-    widely ignored.  <productname>PostgreSQL</productname>'s behavior
-    on this point is similar to that of several other SQL databases.
-   </para>
-
-   <para>
-    The SQL standard also distinguishes between global and local temporary
+    Second, the SQL standard distinguishes between global and local temporary
     tables, where a local temporary table has a separate set of contents for
     each SQL module within each session, though its definition is still shared
-    across sessions.  Since <productname>PostgreSQL</productname> does not
+    across sessions. Since <productname>PostgreSQL</productname> does not
     support SQL modules, this distinction is not relevant in
     <productname>PostgreSQL</productname>.
    </para>
 
-   <para>
-    For compatibility's sake, <productname>PostgreSQL</productname> will
-    accept the <literal>GLOBAL</literal> and <literal>LOCAL</literal> keywords
-    in a temporary table declaration, but they currently have no effect.
-    Use of these keywords is discouraged, since future versions of
-    <productname>PostgreSQL</productname> might adopt a more
-    standard-compliant interpretation of their meaning.
-   </para>
-
    <para>
     The <literal>ON COMMIT</literal> clause for temporary tables
     also resembles the SQL standard, but has some differences.
@@ -2181,7 +2205,8 @@ CREATE TABLE cities_partdef
     default behavior is <literal>ON COMMIT DELETE ROWS</literal>.  However, the
     default behavior in <productname>PostgreSQL</productname> is
     <literal>ON COMMIT PRESERVE ROWS</literal>.  The <literal>ON COMMIT
-    DROP</literal> option does not exist in SQL.
+    DROP</literal> option does not exist in SQL and is not supported by
+    global temporary table.
    </para>
   </refsect2>
 
-- 
2.27.0

0001-gtt-v51.patchapplication/octet-stream; name=0001-gtt-v51.patchDownload
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index dba32ceff3..68cb22b568 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -159,6 +159,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * In order to avoid consistency problems, the global temporary table
+	 * uses ShareUpdateExclusiveLock.
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temporary table on commit options",
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},
 	/* list terminator */
 	{{NULL}}
 };
@@ -1834,6 +1847,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)},
@@ -1978,11 +1993,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
-	/*
-	 * autovacuum_enabled, autovacuum_analyze_threshold and
-	 * autovacuum_analyze_scale_factor are supported for partitioned tables.
-	 */
-
 	return default_reloptions(reloptions, validate, RELOPT_KIND_PARTITIONED);
 }
 
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index 43ba03b6eb..49f1052fdb 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1023,7 +1023,7 @@ gistproperty(Oid index_oid, int attno,
 XLogRecPtr
 gistGetFakeLSN(Relation rel)
 {
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	if (RELATION_IS_TEMP(rel))
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 0752fb38a9..5c85d777f4 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -151,7 +151,7 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	if (!RELATION_IS_TEMP(index))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index beb8f20708..421e22428d 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -589,7 +589,7 @@ heapam_relation_set_new_filenode(Relation rel,
 	 */
 	*minmulti = GetOldestMultiXactId();
 
-	srel = RelationCreateStorage(*newrnode, persistence);
+	srel = RelationCreateStorage(*newrnode, persistence, rel);
 
 	/*
 	 * If required, set up an init fork for an unlogged table so that it can
@@ -641,7 +641,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(RelationGetSmgr(rel), dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 2c04b69221..5511ac908c 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -63,6 +63,7 @@
 #include "access/xlog.h"
 #include "catalog/index.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -510,6 +511,14 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 	TransactionId FreezeLimit;
 	MultiXactId MultiXactCutoff;
 
+	/*
+	 * not every AM requires these to be valid, but regular heap does.
+	 * Transaction information for the global temp table will be stored
+	 * in the local hash table, not the catalog.
+	 */
+	Assert(RELATION_IS_GLOBAL_TEMP(rel) ^ TransactionIdIsNormal(rel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(rel) ^ MultiXactIdIsValid(rel->rd_rel->relminmxid));
+
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
 	{
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index ebec8fa5b8..84766b3a33 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -677,6 +678,14 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		/*
+		 * If a global temporary table storage file is not initialized in the
+		 * current backend, its index does not have a root page, just returns 0.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 5fcd004e1b..58b994cef5 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -212,7 +212,8 @@ Boot_CreateStmt:
 												   mapped_relation,
 												   true,
 												   &relfrozenxid,
-												   &relminmxid);
+												   &relminmxid,
+												   false);
 						elog(DEBUG4, "bootstrap relation created");
 					}
 					else
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e77361..8c21979625 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -44,6 +44,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/README.gtt b/src/backend/catalog/README.gtt
new file mode 100644
index 0000000000..bedc85c8df
--- /dev/null
+++ b/src/backend/catalog/README.gtt
@@ -0,0 +1,165 @@
+Global Temporary Table(GTT)
+==============
+
+Feature description
+--------------------------------
+
+Previously, temporary tables are defined once and automatically
+exist (starting with empty contents) in every session before using them.
+
+The temporary table implementation in PostgreSQL, known as Local temp tables(LTT),
+did not fully comply with the SQL standard. This version added the support of
+Global Temporary Table .
+
+The metadata of Global Temporary Table is persistent and shared among sessions.
+The data stored in the Global temporary table is independent of sessions. This
+means, when a session creates a Global Temporary Table and writes some data.
+Other sessions cannot see those data, but they have an empty Global Temporary
+Table with same schema.
+
+Like local temporary table, Global Temporary Table supports ON COMMIT PRESERVE ROWS
+or ON COMMIT DELETE ROWS clause, so that data in the temporary table can be
+cleaned up or preserved automatically when a session exits or a transaction COMMITs.
+
+Unlike Local Temporary Table, Global Temporary Table does not support
+ON COMMIT DROP clauses.
+
+In following paragraphs, we use GTT for Global Temporary Table and LTT for
+local temporary table.
+
+Main design ideas
+-----------------------------------------
+
+STORAGE & BUFFER
+
+In general, GTT and LTT use the same storage and buffer design and
+implementation. The storage files for both types of temporary tables are named
+as t_backendid_relfilenode, and the local buffer is used to cache the data.
+
+The schema of GTTs is shared among sessions while their data are not. We build
+a new mechanisms to manage those non-shared data and their statistics.
+Here is the summary of changes:
+
+1) CATALOG
+GTTs store session-specific data. The storage information of GTTs'data, their
+transaction information, and their statistics are not stored in the catalog.
+
+2) STORAGE INFO & STATISTICS & TRANSACTION
+In order to maintain durability and availability of GTTs'session-specific data,
+their storage information, statistics, and transaction information is managed
+in a local hash table tt_storage_local_hash.
+
+3) DDL
+A shared hash table active_gtt_shared_hash is added to track the state of the
+GTT in a different session. This information is recorded in the hash table
+during the DDL execution of the GTT.
+
+4) LOCK
+The data stored in a GTT can only be modified or accessed by owning session.
+The statements that only modify data in a GTT do not need a high level of table
+locking.
+The operations making those changes include truncate GTT, Vacuum/Cluster GTT,
+and Lock GTT.
+
+Detailed design
+-----------------------------------------
+
+1. CATALOG
+1.1 relpersistence
+define RELPERSISTENCEGLOBALTEMP 'g'
+Mark Global Temporary Table in pg_class relpersistence to 'g'. The relpersistence
+of indexes created on the GTT, sequences on GTT and toast tables on GTT are
+also set to 'g'
+
+1.2 on commit clause
+LTT's status associated with on commit DELETE ROWS and on commit PRESERVE ROWS
+is not stored in catalog. Instead, GTTs need a bool value on_commit_delete_rows
+in reloptions which is shared among sessions.
+
+1.3 gram.y
+GTT is already supported in syntax tree. We remove the warning message
+"GLOBAL is deprecated in temporary table creation" and mark
+relpersistence = RELPERSISTENCEGLOBALTEMP.
+
+2. STORAGE INFO & STATISTICS DATA & TRANSACTION INFO
+2.1. gtt_storage_local_hash
+Each backend creates a local hashtable gtt_storage_local_hash to track a GTT's
+storage file information, statistics, and transaction information.
+
+2.2 GTT storage file info track
+1) When one session inserts data into a GTT for the first time, record the
+storage info to gtt_storage_local_hash.
+2) Use beforeshmemexit to ensure that all files of session GTT are deleted when
+the session exits.
+
+2.3 statistics info
+1) relpages reltuples relallvisible
+2) The statistics of each column from pg_statistic
+All the above information is stored in gtt_storage_local_hash.
+When doing vacuum or analyze, GTT's statistic is updated, which is used by
+the SQL planner.
+The statistics summarizes only data in the current session.
+
+2.3 transaction info track
+frozenxid minmulti from pg_class is stored to gtt_storage_local_hash.
+
+4 DDL
+4.1. active_gtt_shared_hash
+This is the hash table created in shared memory to trace the GTT files initialized
+in each session. Each hash entry contains a bitmap that records the backendid of
+the initialized GTT file. With this hash table, we know which backend/session
+is using this GTT. Such information is used during GTT's DDL operations.
+
+4.1 DROP GTT
+One GTT is allowed to be deleted when there is only one session using the table
+and the session is the current session.
+After holding the AccessExclusiveLock lock on GTT, active_gtt_shared_hash
+is checked to ensure that.
+
+4.2 ALTER GTT/DROP INDEX ON GTT
+Same as drop GTT.
+
+4.3 CREATE INDEX ON GTT
+1) create index on GTT statements build index based on local data in a session.
+2) After the index is created, record the index metadata to the catalog.
+3) Other sessions can enable or disable the local GTT index.
+
+5 LOCK
+
+5.1 TRUNCATE GTT
+The truncate GTT command uses RowExclusiveLock, not AccessExclusiveLock, because
+this command only cleans up local data and local buffers in current session.
+
+5.2 CLUSTER GTT/VACUUM FULL GTT
+Same as truncate GTT.
+
+5.3 Lock GTT
+A lock GTT statement does not hold any table locks.
+
+6 MVCC commit log(clog) cleanup
+
+The GTT storage file contains transaction information. Queries for GTT data rely
+on transaction information such as clog. The transaction information required by
+each session may be completely different. We need to ensure that the transaction
+information of the GTT data is not cleaned up during its lifetime and that
+transaction resources are recycled at the instance level.
+
+6.1 The session level GTT oldest frozenxid
+1) To manage all GTT transaction information, add session level oldest frozenxid
+in each session. When one GTT is created or removed, record the session level
+oldest frozenxid and store it in MyProc.
+2) When vacuum advances the database's frozenxid, session level oldest frozenxid
+should be considered. This is acquired by searching all of MyProc. This way,
+we can avoid the clog required by GTTs to be cleaned.
+
+6.2 vacuum GTT
+Users can perform vacuum over a GTT to clean up local data in the GTT.
+
+6.3 autovacuum GTT
+Autovacuum skips all GTTs, because the data in GTTs is only visible in current
+session.
+
+7 OTHERS
+Parallel query
+Planner does not produce parallel query plans for SQL related to GTT. Because
+GTT private data cannot be accessed across processes.
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index aa7d4d5456..595cb03eb4 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -504,6 +504,7 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 83746d3fd9..8cf1385cfe 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -62,6 +62,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,
@@ -306,7 +308,8 @@ heap_create(const char *relname,
 			bool mapped_relation,
 			bool allow_system_table_mods,
 			TransactionId *relfrozenxid,
-			MultiXactId *relminmxid)
+			MultiXactId *relminmxid,
+			bool skip_create_storage)
 {
 	bool		create_storage;
 	Relation	rel;
@@ -371,7 +374,9 @@ heap_create(const char *relname,
 	 * storage is already created, so don't do it here.  Also don't create it
 	 * for relkinds without physical storage.
 	 */
-	if (!RELKIND_HAS_STORAGE(relkind) || OidIsValid(relfilenode))
+	if (!RELKIND_HAS_STORAGE(relkind) ||
+		OidIsValid(relfilenode) ||
+		skip_create_storage)
 		create_storage = false;
 	else
 	{
@@ -427,7 +432,7 @@ heap_create(const char *relname,
 
 			case RELKIND_INDEX:
 			case RELKIND_SEQUENCE:
-				RelationCreateStorage(rel->rd_node, relpersistence);
+				RelationCreateStorage(rel->rd_node, relpersistence, rel);
 				break;
 
 			case RELKIND_RELATION:
@@ -998,6 +1003,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -1036,8 +1042,21 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/*
+	 * The transaction information of the global temporary table is stored
+	 * in the local hash table, not in catalog.
+	 */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1303,7 +1322,8 @@ heap_create_with_catalog(const char *relname,
 							   mapped_relation,
 							   allow_system_table_mods,
 							   &relfrozenxid,
-							   &relminmxid);
+							   &relminmxid,
+							   false);
 
 	Assert(relid == RelationGetRelid(new_rel_desc));
 
@@ -1410,6 +1430,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1993,6 +2014,19 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/*
+	 * Only when other sessions are not using this Global temporary table,
+	 * is it allowed to DROP it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot drop global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3277,7 +3311,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3289,7 +3323,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3335,8 +3369,16 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		/*
+		 * Truncate global temporary table only clears local data,
+		 * so only low-level locks need to be held.
+		 */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3369,6 +3411,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3377,23 +3420,47 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	/* For global temporary table only */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		/*
+		 * If this GTT is not initialized in current backend, there is
+		 * no needs to anything.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(rel)))
+			return;
+
+		/*
+		 * Truncate GTT only clears local data, so only low-level locks
+		 * need to be held.
+		 */
+		lockmode = RowExclusiveLock;
+	}
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	/*
+	 * After the data is cleaned up on the GTT, the transaction information
+	 * for the data(stored in local hash table) is also need reset.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(RelationGetRelid(rel), 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 26bfa74ce7..c3fe8950a0 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -54,6 +54,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"
@@ -732,6 +733,29 @@ index_create(Relation heapRelation,
 	char		relkind;
 	TransactionId relfrozenxid;
 	MultiXactId relminmxid;
+	bool		skip_create_storage = false;
+
+	/* For global temporary table only */
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temporary table with concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot reindex global temporary tables concurrently")));
+
+		/*
+		 * For the case that some backend is applied relcache message to create
+		 * an index on a global temporary table, if this table in the current
+		 * backend are not initialized, the creation of index storage on the
+		 * table are also skipped.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+		{
+			skip_create_storage = true;
+			flags |= INDEX_CREATE_SKIP_BUILD;
+		}
+	}
 
 	/* constraint flags can only be set when a constraint is requested */
 	Assert((constr_flags == 0) ||
@@ -939,7 +963,8 @@ index_create(Relation heapRelation,
 								mapped_relation,
 								allow_system_table_mods,
 								&relfrozenxid,
-								&relminmxid);
+								&relminmxid,
+								skip_create_storage);
 
 	Assert(relfrozenxid == InvalidTransactionId);
 	Assert(relminmxid == InvalidMultiXactId);
@@ -2107,7 +2132,7 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 * lock (see comments in RemoveRelations), and a non-concurrent DROP is
 	 * more efficient.
 	 */
-	Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+	Assert(!RelpersistenceTsTemp(get_rel_persistence(indexId)) ||
 		   (!concurrent && !concurrent_lock_mode));
 
 	/*
@@ -2139,6 +2164,20 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/*
+	 * Allow to drop index on global temporary table when only current
+	 * backend use it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation) &&
+		is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+	{
+		ereport(ERROR,
+			(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+			 errmsg("cannot drop index %s or global temporary table %s",
+					RelationGetRelationName(userIndexRelation), RelationGetRelationName(userHeapRelation)),
+			 errhint("Because the index is created on the global temporary table and other backend attached it.")));
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2747,6 +2786,7 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = RELATION_IS_GLOBAL_TEMP(rel);
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2841,20 +2881,37 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
-		{
-			rd_rel->relpages = (int32) relpages;
-			dirty = true;
-		}
-		if (rd_rel->reltuples != (float4) reltuples)
+		/* For global temporary table */
+		if (is_gtt)
 		{
-			rd_rel->reltuples = (float4) reltuples;
-			dirty = true;
+			/* Update GTT'statistics into local relcache */
+			rel->rd_rel->relpages = (int32) relpages;
+			rel->rd_rel->reltuples = (float4) reltuples;
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+
+			/* Update GTT'statistics into local hashtable */
+			up_gtt_relstats(RelationGetRelid(rel), relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+		else
 		{
-			rd_rel->relallvisible = (int32) relallvisible;
-			dirty = true;
+			if (rd_rel->relpages != (int32) relpages)
+			{
+				rd_rel->relpages = (int32) relpages;
+				dirty = true;
+			}
+
+			if (rd_rel->reltuples != (float4) reltuples)
+			{
+				rd_rel->reltuples = (float4) reltuples;
+				dirty = true;
+			}
+
+			if (rd_rel->relallvisible != (int32) relallvisible)
+			{
+				rd_rel->relallvisible = (int32) relallvisible;
+				dirty = true;
+			}
 		}
 	}
 
@@ -2967,6 +3024,26 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, progress_index, progress_vals);
 	}
 
+	/* For build index on global temporary table */
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		/*
+		 * If the storage for the index in this session is not initialized,
+		 * it needs to be created.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			/* Before create init storage, fix the local Relcache first */
+			force_enable_gtt_index(indexRelation);
+
+			Assert(gtt_storage_attached(RelationGetRelid(heapRelation)));
+
+			/* Init storage for index */
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3521,6 +3598,20 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	if (!OidIsValid(heapId))
 		return;
 
+	/*
+	 * For reindex on global temporary table, If the storage for the index
+	 * in current backend is not initialized, nothing is done.
+	 */
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		!gtt_storage_attached(indexId))
+	{
+		/* Suppress use of the target index while rebuilding it */
+		SetReindexProcessing(heapId, indexId);
+		/* Re-allow use of target index */
+		ResetReindexProcessing();
+		return;
+	}
+
 	if ((params->options & REINDEXOPT_MISSING_OK) != 0)
 		heapRelation = try_table_open(heapId, ShareLock);
 	else
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index fd767fc5cf..a438082e45 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -656,6 +656,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/* Do not allow create global temporary table in temporary schemas */
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp table in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index c5ad28d71f..707068a6fd 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,6 +27,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -61,6 +62,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			temprelOid;			/* InvalidOid if not a global temporary rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -115,7 +117,7 @@ AddPendingSync(const RelFileNode *rnode)
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -126,7 +128,12 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/*
+		 * Global temporary table and local temporary table use same
+		 * design on storage module.
+		 */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -154,6 +161,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
@@ -165,6 +173,21 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		AddPendingSync(&rnode);
 	}
 
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		Assert(rel && RELATION_IS_GLOBAL_TEMP(rel));
+
+		/*
+		 * Remember the reloid of global temporary table, which is used for
+		 * transaction commit or rollback.
+		 * see smgrDoPendingDeletes.
+		 */
+		pending->temprelOid = RelationGetRelid(rel);
+
+		/* Remember global temporary table storage info to localhash */
+		remember_gtt_storage_info(rnode, rel);
+	}
+
 	return srel;
 }
 
@@ -201,11 +224,20 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/*
+	 * Remember the reloid of global temporary table, which is used for
+	 * transaction commit or rollback.
+	 * see smgrDoPendingDeletes.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->temprelOid = RelationGetRelid(rel);
+
 	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
@@ -618,6 +650,7 @@ smgrDoPendingDeletes(bool isCommit)
 	int			nrels = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -647,14 +680,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->temprelOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -664,12 +701,21 @@ smgrDoPendingDeletes(bool isCommit)
 
 	if (nrels > 0)
 	{
+		int	i;
+
 		smgrdounlinkall(srels, nrels, false);
 
-		for (int i = 0; i < nrels; i++)
+		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* Delete global temporary table info in localhash */
+			if (gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000000..6aec275c73
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1651 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  The body implementation of Global Temparary table.
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *	  See src/backend/catalog/GTT_README for Global temparary table's
+ *	  requirements and design.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_info_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+int		vacuum_gtt_defer_check_age = 0;
+
+/*
+ * The Global temporary table's shared hash table data structure
+ */
+typedef struct gtt_ctl_data
+{
+	LWLock		lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+/* record this global temporary table in which backends are being used */
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+/*
+ * The Global temporary table's local hash table data structure
+ */
+/* Record the storage information and statistical information of the global temporary table */
+typedef struct
+{
+	Oid			relfilenode;
+	Oid			spcnode;
+
+	/* pg_class relstat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+
+	/* pg_statistic column stat */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+
+	Oid			oldrelid;			/* remember the source of relid, before the switch relfilenode. */
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_free_statistics(gtt_relfilenode *rnode);
+static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok);
+static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok);
+static Bitmapset *copy_active_gtt_bitmap(Oid relid);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+/*
+ * Calculate shared hash table entry size for GTT.
+ */
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int 	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	/* hash entry header size */
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	/*
+	 * hash entry data size
+	 * this is a bitmap in shared memory, each backend have a bit.
+	 */
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+/*
+ * Calculate shared hash table max size for GTT.
+ */
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	/* shared hash header size */
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	/* hash entry size */
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	/* max size */
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+/*
+ * Initialization shared hash table for GTT.
+ */
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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();
+	}
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(gtt_fnode);
+	info.entrysize = action_gtt_shared_hash_entry_size();
+	active_gtt_shared_hash =
+		ShmemInitHash("active gtt shared hash",
+						gtt_shared_ctl->max_entry,
+						gtt_shared_ctl->max_entry,
+						&info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+/*
+ * Record GTT relid to shared hash table, which means that current backend is using this GTT.
+ */
+static void
+gtt_storage_checkin(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			found;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *)&(fnode), 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_global_temporary_table.")));
+	}
+
+	if (!found)
+	{
+		int			wordnum;
+
+		/* init bitmap */
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	/* record itself in bitmap */
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+/*
+ * Remove the GTT relid record from the shared hash table which means that current backend is
+ * not use this GTT.
+ */
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode				fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	if (!skiplock)
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		if (!skiplock)
+			LWLockRelease(&gtt_shared_ctl->lock);
+
+		if (isCommit)
+			elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+	/* remove itself from bitmap */
+	bms_del_member(entry->map, MyBackendId);
+
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+
+	if (!skiplock)
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+	return;
+}
+
+/*
+ * Gets usage information for a GTT from shared hash table.
+ * The information is in the form of bitmap.
+ * Quickly copy the entire bitmap from shared memory and return it.
+ * that to avoid holding locks for a long time.
+ */
+static Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	Bitmapset	*map_copy = NULL;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		return NULL;
+	}
+
+	Assert(entry->map);
+
+	/* copy the entire bitmap */
+	if (!bms_is_empty(entry->map))
+		map_copy = bms_copy(entry->map);
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return map_copy;
+}
+
+/*
+ * Check if there are other backends using this GTT besides the current backend.
+ */
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool		in_use = false;
+	int			num_use = 0;
+	gtt_fnode	fnode;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		return false;
+	}
+
+	Assert(entry->map);
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+	/* how many backend are using this GTT */
+	num_use = bms_num_members(entry->map);
+	if (num_use == 0)
+		in_use = false;
+	else if (num_use == 1)
+	{
+		/* check if this is itself */
+		if(bms_is_member(MyBackendId, entry->map))
+			in_use = false;
+		else
+			in_use = true;
+	}
+	else
+		in_use = true;
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return in_use;
+}
+
+/*
+ * Record GTT information to local hash.
+ * They include GTT storage info, transaction info and statistical info.
+ */
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+	gtt_local_hash_entry	*entry;
+	MemoryContext			oldcontext;
+	gtt_relfilenode			*new_node = NULL;
+	Oid						relid = RelationGetRelid(rel);
+	int 					natts = 0;
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	/* First time through: initialize the hash table */
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		HASHCTL		ctl;
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_info_context =
+			AllocSetContextCreate(CacheMemoryContext,
+								"gtt info context",
+								ALLOCSET_DEFAULT_SIZES);
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		ctl.hcxt = gtt_info_context;
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+	}
+
+	Assert(CacheMemoryContext);
+	Assert(gtt_info_context);
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	entry = gtt_search_by_relid(relid, true);
+	if (!entry)
+	{
+		bool		found = false;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash,
+						(void *) &relid, HASH_ENTER, &found);
+
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+		entry->oldrelid = InvalidOid;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			/* record the on commit clause */
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			gtt_storage_checkin(relid);
+		}
+	}
+
+	/* record storage info relstat columnstats and transaction info to relfilenode list */
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	new_node->natts = 0;
+	new_node->attnum = NULL;
+	new_node->att_stat_tups = NULL;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* init column stats structure */
+	natts = RelationGetNumberOfAttributes(rel);
+	new_node->attnum = palloc0(sizeof(int) * natts);
+	new_node->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+	new_node->natts = natts;
+
+	/* only heap have transaction info */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+
+		/**/
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+
+	/* Registration callbacks are used to trigger cleanup during process exit */
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+/*
+ * Remove GTT information from local hash when transaction commit/rollback.
+ */
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	gtt_relfilenode *d_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+		return;
+	}
+
+	d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+		else if (entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+			gtt_relfilenode *gttnode2 = NULL;
+
+			/*
+			 * For cluster GTT rollback.
+			 * We need to roll back the exchange relfilenode operation.
+			 */
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+			Assert(gttnode2->relfilenode == rnode.relNode);
+			Assert(list_length(entry->relfilenode_list) == 1);
+			/* rollback switch relfilenode */
+			gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+									   entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+									   false);
+			/* clean up footprint */
+			entry2->oldrelid = InvalidOid;
+
+			/* temp relfilenode need free */
+			d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+			Assert(d_rnode);
+		}
+		else
+		{
+			/* rollback transaction */
+			if (entry->relfilenode_list == NIL)
+			{
+				if (entry->relkind == RELKIND_RELATION ||
+					entry->relkind == RELKIND_SEQUENCE)
+					gtt_storage_checkout(relid, false, isCommit);
+
+				hash_search(gtt_storage_local_hash,
+						(void *) &(relid), HASH_REMOVE, NULL);
+			}
+
+			return;
+		}
+	}
+
+	/* Clean up transaction info from Local order list and MyProc */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+
+		/* this is valid relfrozenxid */
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	/* delete relfilenode from rel entry */
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	gtt_free_statistics(d_rnode);
+
+	if (entry->relfilenode_list == NIL)
+	{
+		/* this means we truncate this GTT at current backend */
+
+		/* tell shared hash that current backend will no longer use this GTT */
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(relid, false, isCommit);
+
+		if (isCommit && entry->oldrelid != InvalidOid)
+		{
+			gtt_local_hash_entry *entry2 = NULL;
+
+			/* commit transaction at cluster GTT, need clean up footprint */
+			entry2 = gtt_search_by_relid(entry->oldrelid, false);
+			entry2->oldrelid = InvalidOid;
+		}
+
+		hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_REMOVE, NULL);
+	}
+
+	return;
+}
+
+/*
+ * Check if current backend is using this GTT.
+ */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool found = false;
+	gtt_local_hash_entry *entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (!OidIsValid(relid))
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry)
+		found = true;
+
+	return found;
+}
+
+/*
+ * When backend exit, bulk cleaning all GTT storage and local buffer of this backend.
+ */
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS				status;
+	gtt_local_hash_entry		*entry;
+	SMgrRelation	*srels = NULL;
+	Oid				*relids = NULL;
+	char			*relkinds = NULL;
+	int			nrels = 0,
+				nfiles = 0,
+				maxrels = 0,
+				maxfiles = 0,
+				i = 0;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	/* Search all relfilenode for GTT in current backend */
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel;
+			RelFileNode		rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel = smgropen(rnode, MyBackendId);
+
+			if (maxfiles == 0)
+			{
+				maxfiles = 32;
+				srels = palloc(sizeof(SMgrRelation) * maxfiles);
+			}
+			else if (maxfiles <= nfiles)
+			{
+				maxfiles *= 2;
+				srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+			}
+
+			srels[nfiles++] = srel;
+		}
+
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			relids  = palloc(sizeof(Oid) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			relids  = repalloc(relids , sizeof(Oid) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		relkinds[nrels] = entry->relkind;
+		relids[nrels] = entry->relid;
+		nrels++;
+	}
+
+	/* drop local buffer and storage */
+	if (nfiles > 0)
+	{
+		/* Need to ensure we have a usable transaction. */
+                AbortOutOfAnyTransaction();
+
+		smgrdounlinkall(srels, nfiles, false);
+		for (i = 0; i < nfiles; i++)
+			smgrclose(srels[i]);
+
+		pfree(srels);
+	}
+
+	if (nrels)
+	{
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+		for (i = 0; i < nrels; i++)
+		{
+			/* tell shared hash */
+			if (relkinds[i] == RELKIND_RELATION ||
+				relkinds[i] == RELKIND_SEQUENCE)
+				gtt_storage_checkout(relids[i], true, false);
+		}
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+		pfree(relids);
+		pfree(relkinds);
+	}
+
+	/* set to global area */
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;
+
+	return;
+}
+
+/*
+ * Update GTT relstats(relpage/reltuple/relallvisible)
+ * to local hash.
+ */
+void
+up_gtt_relstats(Oid relid,
+					BlockNumber num_pages,
+					double num_tuples,
+					BlockNumber num_all_visible_pages,
+					TransactionId relfrozenxid,
+					TransactionId relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (!OidIsValid(relid))
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (num_pages > 0 &&
+		gtt_rnode->relpages != (int32)num_pages)
+		gtt_rnode->relpages = (int32)num_pages;
+
+	if (num_tuples > 0 &&
+		gtt_rnode->reltuples != (float4)num_tuples)
+		gtt_rnode->reltuples = (float4)num_tuples;
+
+	/* only heap contain transaction information and relallvisible */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (num_all_visible_pages > 0 &&
+			gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+		{
+			gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+		}
+
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNextTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			/* set to local order list */
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			/* set to global area */
+			set_gtt_session_relfrozenxid();
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search GTT relstats(relpage/reltuple/relallvisible)
+ * from local has.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update GTT info(definition is same as pg_statistic)
+ * to local hash.
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	MemoryContext			oldcontext;
+	int						i = 0;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return;
+
+	Assert(entry->relid == reloid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (gtt_rnode->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	/* switch context to gtt_info_context for store tuple at heap_form_tuple */
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == 0)
+		{
+			gtt_rnode->attnum[i] = attnum;
+			break;
+		}
+		else if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			heap_freetuple(gtt_rnode->att_stat_tups[i]);
+			gtt_rnode->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < gtt_rnode->natts);
+	Assert(gtt_rnode->att_stat_tups[i] == NULL);
+	gtt_rnode->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search GTT statistic info(definition is same as pg_statistic)
+ * from local hash.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	int						i = 0;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return NULL;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return NULL;
+
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			return gtt_rnode->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Insert a RelfrozenXID into the list and keep the list in order.
+ */
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell		*cell;
+	int				i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Remove a RelfrozenXID from order list gtt_session_relfrozenxid_list.
+ */
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+/*
+ * Update of backend Level oldest relfrozenxid to MyProc.
+ * This makes each backend's oldest RelFrozenxID globally visible.
+ */
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->backend_gtt_frozenxid != gtt_frozenxid)
+		MyProc->backend_gtt_frozenxid = gtt_frozenxid;
+}
+
+/*
+ * Get GTT column level data statistics.
+ */
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo 	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate *tupstore;
+	HeapTuple		tuple;
+	Relation		rel = NULL;
+	Oid				reloid = PG_GETARG_OID(0);
+	int				attnum = PG_GETARG_INT32(1);
+	char			rel_persistence;
+	TupleDesc	  	tupdesc;
+	MemoryContext 	oldcontext;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	/* get data from local hash */
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum	values[31];
+		bool	isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, NoLock);
+	relation_close(pg_tatistic, AccessShareLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get GTT table level data statistics.
+ */
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate	*tupstore;
+	TupleDesc		tupdesc;
+	MemoryContext	oldcontext;
+	HeapTuple		tuple;
+	Oid				reloid = PG_GETARG_OID(0);
+	Oid				relnode = 0;
+	char			rel_persistence;
+	BlockNumber		relpages = 0;
+	BlockNumber		relallvisible = 0;
+	uint32			relfrozenxid = 0;
+	uint32			relminmxid = 0;
+	double			reltuples = 0;
+	Relation		rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[6];
+		bool	isnull[6];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get a list of backend pids that are currently using this GTT.
+ */
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	PGPROC			*proc = NULL;
+	Bitmapset		*map = NULL;
+	Tuplestorestate *tupstore;
+	TupleDesc		tupdesc;
+	MemoryContext	oldcontext;
+	HeapTuple		tuple;
+	Oid				reloid = PG_GETARG_OID(0);
+	char			rel_persistence;
+	Relation		rel = NULL;
+	pid_t			pid = 0;
+	int				backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	/* get data from share hash */
+	map = copy_active_gtt_bitmap(reloid);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			/* backendid map to process pid */
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get backend level oldest relfrozenxid of each backend using GTT in current database.
+ */
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate *tupstore;
+	int				*pids = NULL;
+	uint32			*xids = NULL;
+	TupleDesc		tupdesc;
+	MemoryContext	oldcontext;
+	HeapTuple		tuple;
+	int				num_xid = MaxBackends + 1;
+	int				i = 0;
+	int				j = 0;
+	uint32			oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+
+	/* Get backend level oldest relfrozenxid in all backend that in MyDatabaseId use GTT */
+	oldest = list_all_backend_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+/*
+ * In order to build the GTT index, force enable GTT'index.
+ */
+void
+force_enable_gtt_index(Relation index)
+{
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(RelationGetRelid(index)));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+/*
+ * Fix the local state of the GTT's index.
+ */
+void
+gtt_fix_index_backend_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid heapOid = index->rd_index->indrelid;
+
+	/* Must be GTT */
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	/*
+	 * If this GTT is not initialized in the current backend,
+	 * its index status is temporarily set to invalid(local relcache).
+	 */
+	if (gtt_storage_attached(heapOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+/*
+ * During the SQL initialization of the executor (InitPlan)
+ * Initialize storage of GTT GTT'indexes and build empty index.
+ */
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int			i;
+	Oid			toastrelid;
+
+	if (!(operation == CMD_UTILITY || operation == CMD_INSERT))
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	/* Each GTT is initialized once in each backend */
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	/* init heap storage */
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+
+		index_build(relation, index, info, true, false);
+	}
+
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+
+		/* init index storage */
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid			indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+			/* build empty index */
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+/*
+ * Release the data structure memory used to store GTT storage info.
+ */
+static void
+gtt_free_statistics(gtt_relfilenode *rnode)
+{
+	int i;
+
+	Assert(rnode);
+
+	for (i = 0; i < rnode->natts; i++)
+	{
+		if (rnode->att_stat_tups[i])
+		{
+			heap_freetuple(rnode->att_stat_tups[i]);
+			rnode->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (rnode->attnum)
+		pfree(rnode->attnum);
+
+	if (rnode->att_stat_tups)
+		pfree(rnode->att_stat_tups);
+
+	pfree(rnode);
+
+	return;
+}
+
+/*
+ * Get the current relfilenode of this GTT.
+ */
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
+/*
+ * For cluster GTT.
+ * Exchange new and old relfilenode, leave footprints ensure rollback capability.
+ */
+void
+gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint)
+{
+	gtt_local_hash_entry	*entry1;
+	gtt_local_hash_entry	*entry2;
+	gtt_relfilenode			*gtt_rnode1 = NULL;
+	gtt_relfilenode			*gtt_rnode2 = NULL;
+	MemoryContext			oldcontext;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry1 = gtt_search_by_relid(rel1, false);
+	gtt_rnode1 = gtt_search_relfilenode(entry1, relfilenode1, false);
+
+	entry2 = gtt_search_by_relid(rel2, false);
+	gtt_rnode2 = gtt_search_relfilenode(entry2, relfilenode2, false);
+
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+	entry1->relfilenode_list = list_delete_ptr(entry1->relfilenode_list, gtt_rnode1);
+	entry2->relfilenode_list = lappend(entry2->relfilenode_list, gtt_rnode1);
+
+	entry2->relfilenode_list = list_delete_ptr(entry2->relfilenode_list, gtt_rnode2);
+	entry1->relfilenode_list = lappend(entry1->relfilenode_list, gtt_rnode2);
+	MemoryContextSwitchTo(oldcontext);
+
+	if (footprint)
+	{
+		entry1->oldrelid = rel2;
+		entry2->oldrelid = rel1;
+	}
+
+	return;
+}
+
+/*
+ * Get a relfilenode used by this GTT during the transaction life cycle.
+ */
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok)
+{
+	gtt_relfilenode		*rnode = NULL;
+	ListCell			*lc;
+
+	Assert(entry);
+
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == relfilenode)
+		{
+			rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (!missing_ok && rnode == NULL)
+		elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+	return rnode;
+}
+
+/*
+ * Get one GTT info from local hash.
+ */
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry == NULL && !missing_ok)
+		elog(ERROR, "relid %u not found in local hash", relid);
+
+	return entry;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 55f6e3711d..fc75533263 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 0c9591415e..2575c084c2 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -104,7 +105,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -185,6 +186,17 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -601,14 +613,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]));
 		}
 
 		/*
@@ -1651,7 +1664,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;
@@ -1753,31 +1766,48 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/*
+		 * For global temporary table,
+		 * Update column statistic to localhash, not catalog.
+		 */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 6487a9e3fc..9fa0d136ed 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/toasting.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
@@ -73,6 +74,12 @@ static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
 							bool verbose, bool *pSwapToastByContent,
 							TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
 static List *get_tables_to_cluster(MemoryContext cluster_context);
+static void gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables);
 
 
 /*---------------------------------------------------------------------------
@@ -390,6 +397,18 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap) &&
+		!gtt_storage_attached(RelationGetRelid(OldHeap)))
+	{
+		relation_close(OldHeap, AccessExclusiveLock);
+		pgstat_progress_end_command();
+		return;
+	}
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
@@ -774,6 +793,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	BlockNumber num_pages;
 	int			elevel = verbose ? INFO : DEBUG2;
 	PGRUsage	ru0;
+	bool		is_gtt = false;
+	uint32		gtt_relfrozenxid = 0;
+	uint32		gtt_relminmxid = 0;
 
 	pg_rusage_init(&ru0);
 
@@ -787,6 +809,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	else
 		OldIndex = NULL;
 
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		is_gtt = true;
+
 	/*
 	 * Their tuple descriptors should be exactly alike, but here we only need
 	 * assume that they have the same number of columns.
@@ -854,20 +879,38 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 						  &OldestXmin, &FreezeXid, NULL, &MultiXactCutoff,
 						  NULL);
 
-	/*
-	 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
-	 * backwards, so take the max.
-	 */
-	if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
-		TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
-		FreezeXid = OldHeap->rd_rel->relfrozenxid;
+	if (is_gtt)
+	{
+		/* Gets transaction information for global temporary table from localhash. */
+		get_gtt_relstats(OIDOldHeap,
+					NULL, NULL, NULL,
+					&gtt_relfrozenxid, &gtt_relminmxid);
+
+		if (TransactionIdIsValid(gtt_relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, gtt_relfrozenxid))
+			FreezeXid = gtt_relfrozenxid;
+
+		if (MultiXactIdIsValid(gtt_relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, gtt_relminmxid))
+			MultiXactCutoff = gtt_relminmxid;
+	}
+	else
+	{
+		/*
+		 * FreezeXid will become the table's new relfrozenxid, and that mustn't go
+		 * backwards, so take the max.
+		 */
+		if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
+			TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
+			FreezeXid = OldHeap->rd_rel->relfrozenxid;
 
-	/*
-	 * MultiXactCutoff, similarly, shouldn't go backwards either.
-	 */
-	if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
-		MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
-		MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+		/*
+		 * MultiXactCutoff, similarly, shouldn't go backwards either.
+		 */
+		if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
+			MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
+			MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+	}
 
 	/*
 	 * Decide whether to use an indexscan or seqscan-and-optional-sort to scan
@@ -935,6 +978,15 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	table_close(OldHeap, NoLock);
 	table_close(NewHeap, NoLock);
 
+	/* Update relstats of global temporary table to localhash. */
+	if (is_gtt)
+	{
+		up_gtt_relstats(RelationGetRelid(NewHeap), num_pages, num_tuples, 0,
+						InvalidTransactionId, InvalidMultiXactId);
+		CommandCounterIncrement();
+		return;
+	}
+
 	/* Update pg_class to reflect the correct values of pages and tuples. */
 	relRelation = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1371,10 +1423,22 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 	 * Swap the contents of the heap relations (including any toast tables).
 	 * Also set old heap's relfrozenxid to frozenXid.
 	 */
-	swap_relation_files(OIDOldHeap, OIDNewHeap,
+	if (newrelpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		Assert(!is_system_catalog);
+		/* For global temporary table modify data in localhash, not pg_class */
+		gtt_swap_relation_files(OIDOldHeap, OIDNewHeap,
+								(OIDOldHeap == RelationRelationId),
+								swap_toast_by_content, is_internal,
+								frozenXid, cutoffMulti, mapped_tables);
+	}
+	else
+	{
+		swap_relation_files(OIDOldHeap, OIDNewHeap,
 						(OIDOldHeap == RelationRelationId),
 						swap_toast_by_content, is_internal,
 						frozenXid, cutoffMulti, mapped_tables);
+	}
 
 	/*
 	 * If it's a system catalog, queue a sinval message to flush all catcaches
@@ -1582,3 +1646,146 @@ get_tables_to_cluster(MemoryContext cluster_context)
 
 	return rvs;
 }
+
+/*
+ * For global temporary table, storage information is stored in localhash,
+ * This function like swap_relation_files except that update storage information,
+ * in the localhash, not pg_class.
+ */
+static void
+gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+					bool swap_toast_by_content,
+					bool is_internal,
+					TransactionId frozenXid,
+					MultiXactId cutoffMulti,
+					Oid *mapped_tables)
+{
+	Relation	relRelation;
+	Oid			relfilenode1,
+				relfilenode2;
+	Relation	rel1;
+	Relation	rel2;
+
+	relRelation = table_open(RelationRelationId, RowExclusiveLock);
+
+	rel1 = relation_open(r1, AccessExclusiveLock);
+	rel2 = relation_open(r2, AccessExclusiveLock);
+
+	relfilenode1 = gtt_fetch_current_relfilenode(r1);
+	relfilenode2 = gtt_fetch_current_relfilenode(r2);
+
+	Assert(OidIsValid(relfilenode1) && OidIsValid(relfilenode2));
+	gtt_switch_rel_relfilenode(r1, relfilenode1, r2, relfilenode2, true);
+
+	CacheInvalidateRelcache(rel1);
+	CacheInvalidateRelcache(rel2);
+
+	InvokeObjectPostAlterHookArg(RelationRelationId, r1, 0,
+								 InvalidOid, is_internal);
+	InvokeObjectPostAlterHookArg(RelationRelationId, r2, 0,
+								 InvalidOid, true);
+
+	if (rel1->rd_rel->reltoastrelid || rel2->rd_rel->reltoastrelid)
+	{
+		if (swap_toast_by_content)
+		{
+			if (rel1->rd_rel->reltoastrelid && rel2->rd_rel->reltoastrelid)
+			{
+				gtt_swap_relation_files(rel1->rd_rel->reltoastrelid,
+									rel2->rd_rel->reltoastrelid,
+									target_is_pg_class,
+									swap_toast_by_content,
+									is_internal,
+									frozenXid,
+									cutoffMulti,
+									mapped_tables);
+			}
+			else
+				elog(ERROR, "cannot swap toast files by content when there's only one");
+		}
+		else
+		{
+			ObjectAddress baseobject,
+						toastobject;
+			long		count;
+
+			if (IsSystemRelation(rel1))
+				elog(ERROR, "cannot swap toast files by links for system catalogs");
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel1->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				count = deleteDependencyRecordsFor(RelationRelationId,
+												   rel2->rd_rel->reltoastrelid,
+												   false);
+				if (count != 1)
+					elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+						 count);
+			}
+
+			/* Register new dependencies */
+			baseobject.classId = RelationRelationId;
+			baseobject.objectSubId = 0;
+			toastobject.classId = RelationRelationId;
+			toastobject.objectSubId = 0;
+
+			if (rel1->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r1;
+				toastobject.objectId = rel1->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+
+			if (rel2->rd_rel->reltoastrelid)
+			{
+				baseobject.objectId = r2;
+				toastobject.objectId = rel2->rd_rel->reltoastrelid;
+				recordDependencyOn(&toastobject, &baseobject,
+								   DEPENDENCY_INTERNAL);
+			}
+		}
+	}
+
+	if (swap_toast_by_content &&
+		rel1->rd_rel->relkind == RELKIND_TOASTVALUE &&
+		rel2->rd_rel->relkind == RELKIND_TOASTVALUE)
+	{
+		Oid			toastIndex1,
+					toastIndex2;
+
+		/* Get valid index for each relation */
+		toastIndex1 = toast_get_valid_index(r1,
+											AccessExclusiveLock);
+		toastIndex2 = toast_get_valid_index(r2,
+											AccessExclusiveLock);
+
+		gtt_swap_relation_files(toastIndex1,
+							toastIndex2,
+							target_is_pg_class,
+							swap_toast_by_content,
+							is_internal,
+							InvalidTransactionId,
+							InvalidMultiXactId,
+							mapped_tables);
+	}
+
+	relation_close(rel1, NoLock);
+	relation_close(rel2, NoLock);
+
+	table_close(relRelation, RowExclusiveLock);
+
+	RelationCloseSmgrByOid(r1);
+	RelationCloseSmgrByOid(r2);
+
+	CommandCounterIncrement();
+}
+
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 6b33951e0c..5f757b00de 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -290,7 +290,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP_ON_CURRENT_SESSION(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, whereClause,
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 40a54ad0bd..7895e7d99b 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -30,6 +30,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/copyfrom_internal.h"
 #include "commands/progress.h"
@@ -659,6 +660,9 @@ CopyFrom(CopyFromState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	/* Check and init global temporary table storage in current backend */
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	/*
 	 * Set up a ModifyTableState so we can let FDW(s) init themselves for
 	 * foreign-table result relation(s).
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index c14ca27c5e..5f5cb2bbc0 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -570,7 +570,7 @@ DefineIndex(Oid relationId,
 	 * is more efficient.  Do this before any use of the concurrent option is
 	 * done.
 	 */
-	if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+	if (stmt->concurrent && !RelpersistenceTsTemp(get_rel_persistence(relationId)))
 		concurrent = true;
 	else
 		concurrent = false;
@@ -2598,7 +2598,7 @@ ReindexIndex(RangeVar *indexRelation, ReindexParams *params, bool isTopLevel)
 	if (relkind == RELKIND_PARTITIONED_INDEX)
 		ReindexPartitions(indOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 persistence != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(persistence))
 		ReindexRelationConcurrently(indOid, params);
 	else
 	{
@@ -2707,7 +2707,7 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 	if (get_rel_relkind(heapOid) == RELKIND_PARTITIONED_TABLE)
 		ReindexPartitions(heapOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(get_rel_persistence(heapOid)))
 	{
 		result = ReindexRelationConcurrently(heapOid, params);
 
@@ -3122,7 +3122,7 @@ ReindexMultipleInternal(List *relids, ReindexParams *params)
 			   relkind != RELKIND_PARTITIONED_TABLE);
 
 		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			relpersistence != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(relpersistence))
 		{
 			ReindexParams newparams = *params;
 
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 62465bacd8..519c9ea82e 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -57,7 +57,10 @@ LockTableCommand(LockStmt *lockstmt)
 										  RangeVarCallbackForLockTable,
 										  (void *) &lockstmt->mode);
 
-		if (get_rel_relkind(reloid) == RELKIND_VIEW)
+		/* Lock table command ignores global temporary table. */
+		if (get_rel_persistence(reloid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+		else if (get_rel_relkind(reloid) == RELKIND_VIEW)
 			LockViewRecurse(reloid, lockstmt->mode, lockstmt->nowait, NIL);
 		else if (recurse)
 			LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait);
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 72bfdc07a4..5b1bfcd117 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -275,8 +278,6 @@ ResetSequence(Oid seq_relid)
 	Buffer		buf;
 	HeapTupleData seqdatatuple;
 	HeapTuple	tuple;
-	HeapTuple	pgstuple;
-	Form_pg_sequence pgsform;
 	int64		startv;
 
 	/*
@@ -287,12 +288,7 @@ ResetSequence(Oid seq_relid)
 	init_sequence(seq_relid, &elm, &seq_rel);
 	(void) read_seq_tuple(seq_rel, &buf, &seqdatatuple);
 
-	pgstuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seq_relid));
-	if (!HeapTupleIsValid(pgstuple))
-		elog(ERROR, "cache lookup failed for sequence %u", seq_relid);
-	pgsform = (Form_pg_sequence) GETSTRUCT(pgstuple);
-	startv = pgsform->seqstart;
-	ReleaseSysCache(pgstuple);
+	startv = get_seqence_start_value(seq_relid);
 
 	/*
 	 * Copy the existing sequence tuple.
@@ -451,6 +447,15 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 
 	init_sequence(relid, &elm, &seqrel);
 
+	if (RELATION_IS_GLOBAL_TEMP(seqrel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(seqrel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary sequence %s when other backend attached it.",
+						RelationGetRelationName(seqrel))));
+	}
+
 	rel = table_open(SequenceRelationId, RowExclusiveLock);
 	seqtuple = SearchSysCacheCopy1(SEQRELID,
 								   ObjectIdGetDatum(relid));
@@ -611,7 +616,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +941,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1153,6 +1158,14 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	/* Initializes the storage for sequence which the global temporary table belongs. */
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1927,3 +1940,51 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+/*
+ * Get the startValue of the sequence from syscache.
+ */
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+/*
+ * Initialize sequence which global temporary table belongs.
+ */
+void
+gtt_init_seq(Relation rel)
+{
+	Datum		value[SEQ_COL_LASTCOL] = {0};
+	bool		null[SEQ_COL_LASTCOL] = {false};
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index a16e749506..ba01c0fe62 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -601,7 +602,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
 static char GetAttributeCompression(Oid atttypid, char *compression);
-
+static OnCommitAction gtt_oncommit_option(List *options);
 
 /* ----------------------------------------------------------------
  *		DefineRelation
@@ -646,6 +647,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -657,7 +659,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * Check consistency of arguments
 	 */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !RelpersistenceTsTemp(stmt->relation->relpersistence))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -687,7 +689,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	if (RelpersistenceTsTemp(stmt->relation->relpersistence)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -788,6 +790,56 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* For global temporary table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+	{
+		/* Check parent table */
+		if (inheritOids)
+		{
+			Oid			parent = linitial_oid(inheritOids);
+			Relation	relation = table_open(parent, NoLock);
+
+			if (!RELATION_IS_GLOBAL_TEMP(relation))
+				elog(ERROR, "The parent table must be global temporary table");
+
+			table_close(relation, NoLock);
+		}
+
+		/* Check oncommit clause and save to reloptions */
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temporary table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1414,7 +1466,7 @@ RemoveRelations(DropStmt *drop)
 		 * relation persistence cannot be known without its OID.
 		 */
 		if (drop->concurrent &&
-			get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(get_rel_persistence(relOid)))
 		{
 			Assert(list_length(drop->objects) == 1 &&
 				   drop->removeType == OBJECT_INDEX);
@@ -1623,7 +1675,16 @@ ExecuteTruncate(TruncateStmt *stmt)
 		Relation	rel;
 		bool		recurse = rv->inh;
 		Oid			myrelid;
-		LOCKMODE	lockmode = AccessExclusiveLock;
+		LOCKMODE	lockmode;
+
+		/*
+		 * Truncate global temp table only cleans up the data in current backend,
+		 * only low-level locks are required.
+		 */
+		if (rv->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+		else
+			lockmode = AccessExclusiveLock;
 
 		myrelid = RangeVarGetRelidExtended(rv, lockmode,
 										   0, RangeVarCallbackForTruncate,
@@ -1942,6 +2003,14 @@ ExecuteTruncateGuts(List *explicit_rels,
 			continue;
 		}
 
+		/*
+		 * Skip the global temporary table that is not initialized for storage
+		 * in current backend.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			continue;
+
 		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
@@ -4010,6 +4079,16 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temporary table only current backend use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -5369,6 +5448,42 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot rewrite temporary tables of other sessions")));
 
+			if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+			{
+				if (tab->chgPersistence)
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot change global temporary table persistence setting")));
+
+				/*
+				 * The storage for the global temporary table needs to be initialized
+				 * before rewrite table.
+				 */
+				if(!gtt_storage_attached(tab->relid))
+				{
+					ResultRelInfo *resultRelInfo;
+					MemoryContext oldcontext;
+					MemoryContext ctx_alter_gtt;
+
+					ctx_alter_gtt = AllocSetContextCreate(CurrentMemoryContext,
+											"gtt alter table", ALLOCSET_DEFAULT_SIZES);
+					oldcontext = MemoryContextSwitchTo(ctx_alter_gtt);
+
+					resultRelInfo = makeNode(ResultRelInfo);
+					InitResultRelInfo(resultRelInfo, OldHeap,
+									1, NULL, 0);
+					if (resultRelInfo->ri_RelationDesc->rd_rel->relhasindex &&
+						resultRelInfo->ri_IndexRelationDescs == NULL)
+						ExecOpenIndices(resultRelInfo, false);
+
+					init_gtt_storage(CMD_UTILITY, resultRelInfo);
+					ExecCloseIndices(resultRelInfo);
+
+					MemoryContextSwitchTo(oldcontext);
+					MemoryContextDelete(ctx_alter_gtt);
+				}
+			}
+
 			/*
 			 * Select destination tablespace (same as original unless user
 			 * requested a change)
@@ -8985,6 +9100,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -13661,6 +13782,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -13860,6 +13984,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temporary table");
+
 	/* Check first if relation can be moved to new tablespace */
 	if (!CheckRelationTableSpaceMove(rel, newTableSpace))
 	{
@@ -14163,7 +14290,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(RelationGetSmgr(rel), dstrel, MAIN_FORKNUM,
@@ -15761,6 +15888,7 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
 	switch (rel->rd_rel->relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -18713,3 +18841,40 @@ GetAttributeCompression(Oid atttypid, char *compression)
 
 	return cmethod;
 }
+
+/*
+ * Parse the on commit clause for the temporary table
+ */
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			bool	res = false;
+			char	*sval = defGetString(def);
+
+			/* It has to be a Boolean value */
+			if (!parse_bool(sval, &res))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
+
+			if (res)
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 5c4bc15b44..1e0512cf9b 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"
@@ -1315,6 +1316,22 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = RELATION_IS_GLOBAL_TEMP(relation);
+
+	 /* For global temporary table */
+	if (is_gtt)
+	{
+		/* Store relation statistics and transaction information to the localhash */
+		up_gtt_relstats(RelationGetRelid(relation),
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+
+		/* Update relation statistics to local relcache */
+		relation->rd_rel->relpages = (int32) num_pages;
+		relation->rd_rel->reltuples = (float4) num_tuples;
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1328,17 +1345,23 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (!is_gtt &&
+		pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (!is_gtt &&
+		pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (!is_gtt &&
+		pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1383,7 +1406,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNextTransactionId(),
@@ -1394,7 +1418,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1502,6 +1527,13 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/*
+		 * The relfrozenxid for a global temporary talble is stored in localhash,
+		 * not pg_class, See list_all_session_gtt_frozenxids()
+		 */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1559,6 +1591,43 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/* If enable global temporary table */
+	if (max_active_gtt > 0)
+	{
+		TransactionId	safe_age;
+		/*  */
+		TransactionId	oldest_gtt_frozenxid =
+			list_all_backend_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid))
+		{
+			safe_age = oldest_gtt_frozenxid + vacuum_gtt_defer_check_age;
+			if (safe_age < FirstNormalTransactionId)
+				safe_age += FirstNormalTransactionId;
+
+			/*
+			 * We tolerate that the minimum age of gtt is less than
+			 * the minimum age of conventional tables, otherwise it will
+			 * throw warning message.
+			 */
+			if (TransactionIdIsNormal(safe_age) &&
+				TransactionIdPrecedes(safe_age, newFrozenXid))
+			{
+				ereport(WARNING,
+					(errmsg("global temp table oldest relfrozenxid %u is the oldest in the entire db",
+							oldest_gtt_frozenxid),
+					 errdetail("The oldest relfrozenxid in pg_class is %u", newFrozenXid),
+					 errhint("If they differ greatly, please consider cleaning up the data in global temp table.")));
+			}
+
+			/*
+			 * We need to ensure that the clog required by gtt is not cleand.
+			 */
+			if (TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+				newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1910,6 +1979,19 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	/*
+	 * Skip those global temporary table that are not initialized in
+	 * current backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel) &&
+		!gtt_storage_attached(RelationGetRelid(rel)))
+	{
+		relation_close(rel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 4df05a0b33..4c181e2e14 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -527,6 +527,12 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	/* Global temporary table are not sensible. */
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b3ce4bae53..611e3f18a7 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -784,6 +784,10 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		/* This is one kind of temp table */
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 5c723bc54e..a7edceb1a5 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
@@ -533,6 +534,9 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	/* Init storage for partitioned global temporary table in current backend */
+	init_gtt_storage(mtstate->operation, leaf_part_rri);
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index c24684aa6f..bb14314e81 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -38,6 +38,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -633,6 +634,9 @@ ExecInsert(ModifyTableState *mtstate,
 		resultRelInfo->ri_IndexRelationDescs == NULL)
 		ExecOpenIndices(resultRelInfo, onconflict != ONCONFLICT_NONE);
 
+	/* Init storage for global temporary table in current backend */
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	/*
 	 * BEFORE ROW INSERT Triggers.
 	 *
@@ -2810,6 +2814,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		i++;
 	}
 
+
 	/*
 	 * Now we may initialize the subplan.
 	 */
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 671117314a..f68c82c3f1 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -48,7 +48,7 @@
 #include "partitioning/partprune.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/lsyscache.h"
-
+#include "utils/rel.h"
 
 /* results of subquery_is_pushdown_safe */
 typedef struct pushdown_safety_info
@@ -619,7 +619,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			if (RelpersistenceTsTemp(get_rel_persistence(rte->relid)))
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 1868c4eff4..d4a958f8e3 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -5917,7 +5917,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
-	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+	if (RELATION_IS_TEMP(heap) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index c5194fdbbf..38d7c65854 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -30,6 +30,7 @@
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temporary table in current backend */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 438b077004..a9d4ed1878 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2894,6 +2894,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 10da5c5c51..0232f59e99 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3403,17 +3403,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; }
@@ -11661,19 +11655,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 7465919044..48b8a68984 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -82,6 +82,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);
 
 
 /*
@@ -3665,3 +3666,53 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * Like function isQueryUsingTempRelation_walker
+ * return true if any relation underlying
+ * the query is a global temporary table.
+ */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* Check if the query uses global temporary table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 675e400839..967af004aa 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -447,6 +447,13 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	/*
+	 * If a sequence is bound to a global temporary table, then the sequence
+	 * must been "global temporary"
+	 */
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
@@ -3326,6 +3333,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		cxt.isforeign = false;
 	}
 	cxt.relation = stmt->relation;
+	/* Sets the table persistence to the context */
+	cxt.relation->relpersistence = RelationGetRelPersistence(rel);
 	cxt.rel = rel;
 	cxt.inhRelations = NIL;
 	cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 912ef9cb54..b8c40f2498 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2158,6 +2158,14 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/*
+			 * Aotuvacuum cannot vacuum the private data stored in each backend
+			 * that belongs to global temporary table, so skip them.
+			 */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2224,7 +2232,7 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		if (RelpersistenceTsTemp(classForm->relpersistence))
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 86ef607ff3..2619f5e919 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -2938,6 +2939,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * Returns 0 if this global temporary table is not initialized in current
+	 * backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 3e4ec53a97..09f676d6a6 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -23,6 +23,7 @@
 #include "access/syncscan.h"
 #include "access/twophase.h"
 #include "commands/async.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
@@ -150,6 +151,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
@@ -222,6 +224,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* For global temporary table shared hashtable */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 4c91e721d0..1b3a204333 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -65,6 +65,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -5100,3 +5101,78 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temporary table.
+ */
+int
+list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct *arrayP = procArray;
+	TransactionId result = InvalidTransactionId;
+	int			index;
+	uint8			flags = 0;
+	int			i = 0;
+
+	/* return 0 if feature is disabled */
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+		*n = 0;
+	}
+
+	/* Disable in standby node */
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		uint8           statusFlags = ProcGlobal->statusFlags[index];
+
+		if (statusFlags & flags)
+			continue;
+
+		/* Fetch all backend that is belonging to MyDatabaseId */
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->backend_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->backend_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->backend_gtt_frozenxid, result))
+				result = proc->backend_gtt_frozenxid;
+
+			/* save backend pid and backend level oldest relfrozenxid */
+			if (max_size > 0)
+			{
+				pids[i] = proc->pid;
+				xids[i] = proc->backend_gtt_frozenxid;
+				i++;
+			}
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (max_size > 0)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 862097352b..4edd3b31f7 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -176,7 +176,9 @@ static const char *const BuiltinTrancheNames[] = {
 	/* LWTRANCHE_PARALLEL_APPEND: */
 	"ParallelAppend",
 	/* LWTRANCHE_PER_XACT_PREDICATE_LIST: */
-	"PerXactPredicateList"
+	"PerXactPredicateList",
+	/* LWTRANCHE_GTT_CTL */
+	"GlobalTempTableControl"
 };
 
 StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 2575ea1ca0..16e7e0825e 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -393,6 +393,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;	/* init backend level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
@@ -578,6 +579,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;	/* init backend level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index d5a7fb13f3..8225cf6219 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -982,6 +982,13 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/*
+			 * For global temporary table ,each backend has its own storage,
+			 * also only sees its own storage. Use Backendid to identify them.
+			 */
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 0c8c05f6c2..699507a24c 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -108,6 +108,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -5115,12 +5116,26 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								/* For global temporary table, get statistic data from localhash */
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -5368,15 +5383,28 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6800,6 +6828,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;
@@ -6817,6 +6846,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6828,6 +6865,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;
@@ -6843,6 +6882,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7761,6 +7808,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))
@@ -7773,6 +7822,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7785,6 +7843,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.
@@ -7804,6 +7864,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 6bba5f8ec4..fa81808df6 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -3113,6 +3114,19 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		/* For global temporary table, get statistic data from localhash */
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3..9f13fbe487 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -65,6 +65,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1116,6 +1117,28 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+
+				/* For global temporary table, get relstat data from localhash */
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+
+				/* And put them to local relcache */
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1173,6 +1196,8 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			/* The state of the global temporary table's index may need to be set */
+			gtt_fix_index_backend_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -1300,7 +1325,22 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+			/*
+			 * For global temporary table, get the latest relfilenode
+			 * from localhash and put it in relcache.
+			 */
+			if (OidIsValid(newrelnode) &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2251,6 +2291,9 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		/* The state of the global temporary table's index may need to be set */
+		gtt_fix_index_backend_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3489,6 +3532,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3598,28 +3645,38 @@ void
 RelationSetNewRelfilenode(Relation relation, char persistence)
 {
 	Oid			newrelfilenode;
-	Relation	pg_class;
-	HeapTuple	tuple;
+	Relation	pg_class = NULL;
+	HeapTuple	tuple = NULL;
 	Form_pg_class classform;
 	MultiXactId minmulti = InvalidMultiXactId;
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
+	/*
+	 * For global temporary table, storage information for the table is
+	 * maintained locally, not in catalog.
+	 */
+	bool		update_catalog = !RELATION_IS_GLOBAL_TEMP(relation);
 
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
 
-	/*
-	 * Get a writable copy of the pg_class tuple for the given relation.
-	 */
-	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+	memset(&classform, 0, sizeof(classform));
 
-	tuple = SearchSysCacheCopy1(RELOID,
-								ObjectIdGetDatum(RelationGetRelid(relation)));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "could not find tuple for relation %u",
-			 RelationGetRelid(relation));
-	classform = (Form_pg_class) GETSTRUCT(tuple);
+	if (update_catalog)
+	{
+		/*
+		 * Get a writable copy of the pg_class tuple for the given relation.
+		 */
+		pg_class = table_open(RelationRelationId, RowExclusiveLock);
+
+		tuple = SearchSysCacheCopy1(RELOID,
+									ObjectIdGetDatum(RelationGetRelid(relation)));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u",
+				 RelationGetRelid(relation));
+		classform = (Form_pg_class) GETSTRUCT(tuple);
+	}
 
 	/*
 	 * Schedule unlinking of the old storage at transaction commit.
@@ -3645,7 +3702,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;
@@ -3665,6 +3722,18 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 			break;
 	}
 
+	/* For global temporary table */
+	if (!update_catalog)
+	{
+		Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+		Assert(RELATION_IS_GLOBAL_TEMP(relation));
+		Assert(!RelationIsMapped(relation));
+
+		/* Make cache invalid and set new relnode to local cache. */
+		CacheInvalidateRelcache(relation);
+		relation->rd_node.relNode = relnode;
+	}
 	/*
 	 * If we're dealing with a mapped index, pg_class.relfilenode doesn't
 	 * change; instead we have to send the update to the relation mapper.
@@ -3674,7 +3743,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	 * possibly-inaccurate values of relpages etc, but those will be fixed up
 	 * later.
 	 */
-	if (RelationIsMapped(relation))
+	else if (RelationIsMapped(relation))
 	{
 		/* This case is only supported for indexes */
 		Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3720,9 +3789,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 		CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 	}
 
-	heap_freetuple(tuple);
+	if (update_catalog)
+	{
+		heap_freetuple(tuple);
 
-	table_close(pg_class, RowExclusiveLock);
+		table_close(pg_class, RowExclusiveLock);
+	}
 
 	/*
 	 * Make the pg_class row change or relation map change visible.  This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index a2e0f8de7e..b1a50e82d9 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -44,6 +44,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "commands/prepare.h"
 #include "commands/trigger.h"
@@ -152,6 +153,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temporary table feature.
+ * table schema are still saved in catalog.
+ *
+ * num > 0 means allows the database to manage multiple active tables at the same time.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2125,6 +2138,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 "
@@ -2673,6 +2695,16 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"vacuum_gtt_defer_check_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("The defer check age of GTT, used to check expired data after vacuum."),
+			NULL
+		},
+		&vacuum_gtt_defer_check_age,
+		10000, 0, 1000000,
+		NULL, NULL, NULL
+	},
+
 	/*
 	 * See also CheckRequiredParameterValues() if this parameter changes
 	 */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 34b91bb226..eb27727ff7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2527,6 +2527,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
 		dopt->no_unlogged_table_data)
 		return;
 
+	/* Don't dump data in global temporary table/sequence */
+	if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		return;
+
 	/* Check that the data is not explicitly excluded */
 	if (simple_oid_list_member(&tabledata_exclude_oids,
 							   tbinfo->dobj.catId.oid))
@@ -15962,6 +15966,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
 		char	   *foreign = "";
+		char	   *table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -16015,9 +16020,15 @@ dumpTableSchema(Archive *fout, const 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);
 
@@ -16381,6 +16392,15 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 			}
 		}
 
+		/*
+		 * Transaction information for the global temporary table is not stored
+		 * in the pg_class.
+		 */
+		if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			Assert(tbinfo->frozenxid == 0);
+			Assert(tbinfo->minmxid == 0);
+		}
 		/*
 		 * In binary_upgrade mode, arrange to restore the old relfrozenxid and
 		 * relminmxid of all vacuumable relations.  (While vacuum.c processes
@@ -16388,7 +16408,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		 * of other relations; so this "if" lacks RELKIND_TOASTVALUE, and the
 		 * child toast table is handled below.)
 		 */
-		if (dopt->binary_upgrade &&
+		else if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
 			 tbinfo->relkind == RELKIND_MATVIEW))
 		{
@@ -17391,6 +17411,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	PQExpBuffer query = createPQExpBuffer();
 	PQExpBuffer delqry = createPQExpBuffer();
 	char	   *qseqname;
+	bool	   global_temp_seq = false;
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
@@ -17400,9 +17421,12 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
-						  "FROM pg_catalog.pg_sequence "
-						  "WHERE seqrelid = '%u'::oid",
+						  "seqcache, seqcycle, "
+						  "c.relpersistence "
+						  "FROM pg_catalog.pg_sequence s, "
+						  "pg_catalog.pg_class c "
+						  "WHERE seqrelid = '%u'::oid "
+						  "and s.seqrelid = c.oid",
 						  tbinfo->dobj.catId.oid);
 	}
 	else if (fout->remoteVersion >= 80400)
@@ -17447,6 +17471,9 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
 
+	if (fout->remoteVersion >= 140000)
+		global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
 	if (strcmp(seqtype, "smallint") == 0)
@@ -17524,9 +17551,13 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	}
 	else
 	{
-		appendPQExpBuffer(query,
-						  "CREATE SEQUENCE %s\n",
-						  fmtQualifiedDumpable(tbinfo));
+		appendPQExpBuffer(query, "CREATE ");
+
+		if (global_temp_seq)
+			appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+		appendPQExpBuffer(query, "SEQUENCE %s\n",
+								  fmtQualifiedDumpable(tbinfo));
 
 		if (strcmp(seqtype, "bigint") != 0)
 			appendPQExpBuffer(query, "    AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 0c47a6b8cc..1760f372b5 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -88,7 +88,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -178,7 +178,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 5d9a26cf82..2de11d5d70 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -447,8 +449,17 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
 			 "         ON c.relnamespace = n.oid "
 			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
+			 CppAsString2(RELKIND_MATVIEW) ") AND ");
+
+	if (skip_gtt)
+	{
+		/* exclude global temp tables */
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+			"    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND ");
+	}
+
 	/* exclude possible orphaned temp tables */
+	snprintf(query + strlen(query), sizeof(query) - strlen(query),
 			 "    ((n.nspname !~ '^pg_temp_' AND "
 			 "      n.nspname !~ '^pg_toast_temp_' AND "
 			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index e23b8ca88d..729a9c61e8 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -407,7 +407,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -638,7 +638,10 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+			/* exclude global temp tables */
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -649,7 +652,10 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+		/* exclude global temp tables */
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index f7eb2349e6..54fe4e38c4 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -387,7 +387,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index ba658f731b..92e5f1f035 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -4067,7 +4067,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index d6bf725971..140870197f 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1055,6 +1055,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2488,6 +2490,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2696,6 +2701,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE", "SEQUENCE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 6ce480b49c..a0ccfb3d77 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -59,7 +59,8 @@ extern Relation heap_create(const char *relname,
 							bool mapped_relation,
 							bool allow_system_table_mods,
 							TransactionId *relfrozenxid,
-							MultiXactId *relminmxid);
+							MultiXactId *relminmxid,
+							bool skip_create_storage);
 
 extern Oid	heap_create_with_catalog(const char *relname,
 									 Oid relnamespace,
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index fef9945ed8..9176b7dcc0 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -172,6 +172,7 @@ DECLARE_INDEX(pg_class_tblspc_relfilenode_index, 3455, ClassTblspcRelfilenodeInd
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 8cd0252082..dddf4ba3b9 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5671,6 +5671,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '9874',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '9875',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '9876',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '9877',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 0ab32b44e9..92e9f8ba48 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,7 +22,7 @@
 /* GUC variables */
 extern int	wal_skip_threshold;
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationPreTruncate(Relation rel);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000000..d48162c6b8
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,46 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern int		vacuum_gtt_defer_check_age;
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Oid relid,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void force_enable_gtt_index(Relation index);
+extern void gtt_fix_index_backend_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+extern void gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index 40544dd4c7..7b66d808fc 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 8336c2c5a2..bddcfe7256 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index c86ccdaf60..6b395551c1 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -399,6 +399,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index a8f052e484..4b4ed1a13a 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -189,6 +189,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_SHARED_TIDBITMAP,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_PER_XACT_PREDICATE_LIST,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index be67d8a861..e2f8bb5162 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -157,6 +157,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId backend_gtt_frozenxid;	/* backend level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index b01fa52139..8efffa55ac 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -94,4 +94,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index a7c3a4958e..ce98b47d74 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -282,6 +282,10 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+/* end */
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b4faa1c123..a74558a838 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	SMgrRelation rd_smgr;		/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -326,6 +326,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -608,11 +609,13 @@ RelationGetSmgr(Relation rel)
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -620,6 +623,7 @@ RelationGetSmgr(Relation rel)
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -632,6 +636,30 @@ RelationGetSmgr(Relation rel)
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP_ON_CURRENT_SESSION
+ *		Test a rel is either local temp relation of this session
+ *		or global temp relation.
+ */
+#define RELATION_IS_TEMP_ON_CURRENT_SESSION(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is local temp relation or global temporary relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RelpersistenceTsTemp
+ *		Test a relpersistence is local temp relation or global temporary relation.
+ */
+#define RelpersistenceTsTemp(relpersistence) \
+	(relpersistence == RELPERSISTENCE_TEMP || \
+	 relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -677,6 +705,19 @@ RelationGetSmgr(Relation rel)
 	 (relation)->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&	\
 	 !IsCatalogRelation(relation))
 
+/* For global temporary table */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/* Get on commit clause value only for global temporary table */
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+/* Get relpersistence for relation */
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
-- 
2.27.0

#317Tony Zhu
tony.zhu@ww-it.cn
In reply to: wenjing (#316)
Re: [Proposal] Global temporary tables

Hi Wenjing

would you please rebase the code?

Thank you very much
Tony

The new status of this patch is: Waiting on Author

#318wenjing zeng
wjzeng2012@gmail.com
In reply to: Tony Zhu (#317)
Re: [Proposal] Global temporary tables

2021年7月28日 23:09,Tony Zhu <tony.zhu@ww-it.cn> 写道:

Hi Wenjing

would you please rebase the code?

Thank you for your attention.
According to the test, the latest pgmaster code can merge the latest patch and pass the test.
https://www.travis-ci.com/github/wjzeng/postgres/builds <https://www.travis-ci.com/github/wjzeng/postgres/builds&gt;
If you have any questions, please give me feedback.

Wenjing

Show quoted text

Thank you very much
Tony

The new status of this patch is: Waiting on Author

#319ZHU XIAN WEN
tony.zhu@ww-it.cn
In reply to: wenjing zeng (#318)
Re: [Proposal] Global temporary tables

Hi WenJing

Thanks for the feedback,

I have tested the code, it seems okay, and regression tests got pass

and I have reviewed the code, and I don't find any issue anymore

Hello all

Review and comments for the patches V51 is welcome.

if there is no feedback, I'm going to changed the status to 'Ready for
Committer' on Aug 25

big thanks

Tony

Show quoted text

On 2021/7/29 23:19, wenjing zeng wrote:

2021年7月28日 23:09,Tony Zhu <tony.zhu@ww-it.cn> 写道:

Hi Wenjing

would you please rebase the code?

Thank you for your attention.
According to the test, the latest pgmaster code can merge the latest patch and pass the test.
https://www.travis-ci.com/github/wjzeng/postgres/builds <https://www.travis-ci.com/github/wjzeng/postgres/builds&gt;
If you have any questions, please give me feedback.

Wenjing

Thank you very much
Tony

The new status of this patch is: Waiting on Author

#320Pavel Stehule
pavel.stehule@gmail.com
In reply to: wenjing (#316)
Re: [Proposal] Global temporary tables

Hi

looks so this patch is broken again. Please, can you do rebase?

Regards

Pavel

čt 16. 9. 2021 v 8:28 odesílatel wenjing <wjzeng2012@gmail.com> napsal:

Show quoted text
#321wenjing
wjzeng2012@gmail.com
In reply to: Pavel Stehule (#320)
3 attachment(s)
Re: [Proposal] Global temporary tables

Pavel Stehule <pavel.stehule@gmail.com> 于2021年9月16日周四 下午2:30写道:

Hi

looks so this patch is broken again. Please, can you do rebase?

GTT update to V52 and merge with the latest code.

Wenjing

Show quoted text

Regards

Pavel

čt 16. 9. 2021 v 8:28 odesílatel wenjing <wjzeng2012@gmail.com> napsal:

Attachments:

0003-gtt-v52-doc.patchapplication/octet-stream; name=0003-gtt-v52-doc.patchDownload
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 473a0a4aeb..e510bde8ac 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -169,32 +169,67 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     <listitem>
      <para>
       If specified, the table is created as a temporary table.
-      Temporary tables are automatically dropped at the end of a
-      session, or optionally at the end of the current transaction
-      (see <literal>ON COMMIT</literal> below).  The default
-      search_path includes the temporary schema first and so identically
-      named existing permanent tables are not chosen for new plans
+      Optionally, <literal>GLOBAL</literal> or <literal>LOCAL</literal>
+      can be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
+      They represent two types of temporary tables supported by <productname>PostgreSQL</productname>:
+      global temporary table and local temporary table. Without specified
+      GLOBAL or LOCAL, a local temporary table is created by default.
+     </para>
+
+    <para>
+     Both types of temporary tables’ data are truncated at the
+     end of a session or optionally at the end of the current transaction.
+     (see <literal>ON COMMIT</literal> below). For global temporary table,
+     its schema is reserved and reused by future sessions or transactions.
+     For local temporary table, both its data and its schema are dropped.
+    </para>
+
+    <variablelist>
+     <varlistentry>
+      <term><literal>Global Temporary Table</literal></term>
+      <listitem>
+       <para>
+        Global temporary table are defined just once and automatically exist
+        (starting with empty contents) in every session that needs them.
+        The schema definition of temporary tables is persistent and shared among sessions.
+        However, the data in temporary tables are kept private to sessions themselves,
+        even though they use same name and same schema.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>Local Temporary Table</literal></term>
+     <listitem>
+     <para>
+      Local temporary table are automatically dropped at the end of a
+      session (include schema and data). Future sessions need to create
+      their own temporary tables when they are used.
+     </para>
+     <para>
+      The default search_path includes the temporary schema first and so
+      identically named existing permanent tables are not chosen for new plans
       while the temporary table exists, unless they are referenced
       with schema-qualified names. Any indexes created on a temporary
       table are automatically temporary as well.
      </para>
+     </listitem>
+     </varlistentry>
+    </variablelist>
 
-     <para>
-      The <link linkend="autovacuum">autovacuum daemon</link> cannot
-      access and therefore cannot vacuum or analyze temporary tables.
-      For this reason, appropriate vacuum and analyze operations should be
-      performed via session SQL commands.  For example, if a temporary
-      table is going to be used in complex queries, it is wise to run
-      <command>ANALYZE</command> on the temporary table after it is populated.
-     </para>
+    <para>
+     The <link linkend="autovacuum">autovacuum daemon</link> cannot
+     access and therefore cannot vacuum or analyze temporary tables.
+     For this reason, appropriate vacuum and analyze operations should be
+     performed via session SQL commands.  For example, if a temporary
+     table is going to be used in complex queries, it is wise to run
+     <command>ANALYZE</command> on the temporary table after it is populated.
+    </para>
+    <para>
+     The Temporary table resembles the SQL standard, but has some differences.
+     see <xref linkend="sql-createtable-compatibility"/> below.
+    </para>
 
-     <para>
-      Optionally, <literal>GLOBAL</literal> or <literal>LOCAL</literal>
-      can be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
-      This presently makes no difference in <productname>PostgreSQL</productname>
-      and is deprecated; see
-      <xref linkend="sql-createtable-compatibility"/> below.
-     </para>
     </listitem>
    </varlistentry>
 
@@ -2133,13 +2168,17 @@ CREATE TABLE cities_partdef
    <title>Temporary Tables</title>
 
    <para>
-    Although the syntax of <literal>CREATE TEMPORARY TABLE</literal>
-    resembles that of the SQL standard, the effect is not the same.  In the
-    standard,
-    temporary tables are defined just once and automatically exist (starting
-    with empty contents) in every session that needs them.
-    <productname>PostgreSQL</productname> instead
-    requires each session to issue its own <literal>CREATE TEMPORARY
+    Although the syntax of <literal>CREATE GLOBAL/LOCAL TEMPORARY TABLE</literal>
+    resembles that of the SQL standard, the effect is not the same.
+    The global temporary table follows the SQL standards while local temporary
+    table does not.
+   </para>
+
+   <para>
+    First, in the standard, both global and local temporary tables are defined just
+    once and automatically exist (starting with empty contents) in every session
+    that needs them. For local temporary tables, <productname>PostgreSQL</productname>
+    instead requires each session to issue its own <literal>CREATE LOCAL TEMPORARY
     TABLE</literal> command for each temporary table to be used.  This allows
     different sessions to use the same temporary table name for different
     purposes, whereas the standard's approach constrains all instances of a
@@ -2147,29 +2186,14 @@ CREATE TABLE cities_partdef
    </para>
 
    <para>
-    The standard's definition of the behavior of temporary tables is
-    widely ignored.  <productname>PostgreSQL</productname>'s behavior
-    on this point is similar to that of several other SQL databases.
-   </para>
-
-   <para>
-    The SQL standard also distinguishes between global and local temporary
+    Second, the SQL standard distinguishes between global and local temporary
     tables, where a local temporary table has a separate set of contents for
     each SQL module within each session, though its definition is still shared
-    across sessions.  Since <productname>PostgreSQL</productname> does not
+    across sessions. Since <productname>PostgreSQL</productname> does not
     support SQL modules, this distinction is not relevant in
     <productname>PostgreSQL</productname>.
    </para>
 
-   <para>
-    For compatibility's sake, <productname>PostgreSQL</productname> will
-    accept the <literal>GLOBAL</literal> and <literal>LOCAL</literal> keywords
-    in a temporary table declaration, but they currently have no effect.
-    Use of these keywords is discouraged, since future versions of
-    <productname>PostgreSQL</productname> might adopt a more
-    standard-compliant interpretation of their meaning.
-   </para>
-
    <para>
     The <literal>ON COMMIT</literal> clause for temporary tables
     also resembles the SQL standard, but has some differences.
@@ -2177,7 +2201,8 @@ CREATE TABLE cities_partdef
     default behavior is <literal>ON COMMIT DELETE ROWS</literal>.  However, the
     default behavior in <productname>PostgreSQL</productname> is
     <literal>ON COMMIT PRESERVE ROWS</literal>.  The <literal>ON COMMIT
-    DROP</literal> option does not exist in SQL.
+    DROP</literal> option does not exist in SQL and is not supported by
+    global temporary table.
    </para>
   </refsect2>
 
-- 
2.25.1

0001-gtt-v52.patchapplication/octet-stream; name=0001-gtt-v52.patchDownload
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index b5602f5323..7d61086078 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -159,6 +159,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * In order to avoid consistency problems, the global temporary table
+	 * uses ShareUpdateExclusiveLock.
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temporary table on commit options",
+			RELOPT_KIND_HEAP,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},
 	/* list terminator */
 	{{NULL}}
 };
@@ -1834,6 +1847,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)},
@@ -1979,6 +1994,7 @@ bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
 	/*
+	 * return default_reloptions(reloptions, validate, RELOPT_KIND_PARTITIONED);
 	 * There are no options for partitioned tables yet, but this is able to do
 	 * some validation.
 	 */
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index 43ba03b6eb..49f1052fdb 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1023,7 +1023,7 @@ gistproperty(Oid index_oid, int attno,
 XLogRecPtr
 gistGetFakeLSN(Relation rel)
 {
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	if (RELATION_IS_TEMP(rel))
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index eb3810494f..cbd2290958 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -151,7 +151,7 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	if (!RELATION_IS_TEMP(index))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 9befe012a9..26fce0c4b8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -593,7 +593,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
@@ -645,7 +645,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(RelationGetSmgr(rel), dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 9eaf07649e..41db273abe 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -63,6 +63,7 @@
 #include "access/xlog.h"
 #include "catalog/index.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -508,6 +509,14 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 	TransactionId FreezeLimit;
 	MultiXactId MultiXactCutoff;
 
+	/*
+	 * not every AM requires these to be valid, but regular heap does.
+	 * Transaction information for the global temp table will be stored
+	 * in the local hash table, not the catalog.
+	 */
+	Assert(RELATION_IS_GLOBAL_TEMP(rel) ^ TransactionIdIsNormal(rel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(rel) ^ MultiXactIdIsValid(rel->rd_rel->relminmxid));
+
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
 	{
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index ebec8fa5b8..84766b3a33 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -677,6 +678,14 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		/*
+		 * If a global temporary table storage file is not initialized in the
+		 * current backend, its index does not have a root page, just returns 0.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 5fcd004e1b..58b994cef5 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -212,7 +212,8 @@ Boot_CreateStmt:
 												   mapped_relation,
 												   true,
 												   &relfrozenxid,
-												   &relminmxid);
+												   &relminmxid,
+												   false);
 						elog(DEBUG4, "bootstrap relation created");
 					}
 					else
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e77361..8c21979625 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -44,6 +44,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/README.gtt b/src/backend/catalog/README.gtt
new file mode 100644
index 0000000000..bedc85c8df
--- /dev/null
+++ b/src/backend/catalog/README.gtt
@@ -0,0 +1,165 @@
+Global Temporary Table(GTT)
+==============
+
+Feature description
+--------------------------------
+
+Previously, temporary tables are defined once and automatically
+exist (starting with empty contents) in every session before using them.
+
+The temporary table implementation in PostgreSQL, known as Local temp tables(LTT),
+did not fully comply with the SQL standard. This version added the support of
+Global Temporary Table .
+
+The metadata of Global Temporary Table is persistent and shared among sessions.
+The data stored in the Global temporary table is independent of sessions. This
+means, when a session creates a Global Temporary Table and writes some data.
+Other sessions cannot see those data, but they have an empty Global Temporary
+Table with same schema.
+
+Like local temporary table, Global Temporary Table supports ON COMMIT PRESERVE ROWS
+or ON COMMIT DELETE ROWS clause, so that data in the temporary table can be
+cleaned up or preserved automatically when a session exits or a transaction COMMITs.
+
+Unlike Local Temporary Table, Global Temporary Table does not support
+ON COMMIT DROP clauses.
+
+In following paragraphs, we use GTT for Global Temporary Table and LTT for
+local temporary table.
+
+Main design ideas
+-----------------------------------------
+
+STORAGE & BUFFER
+
+In general, GTT and LTT use the same storage and buffer design and
+implementation. The storage files for both types of temporary tables are named
+as t_backendid_relfilenode, and the local buffer is used to cache the data.
+
+The schema of GTTs is shared among sessions while their data are not. We build
+a new mechanisms to manage those non-shared data and their statistics.
+Here is the summary of changes:
+
+1) CATALOG
+GTTs store session-specific data. The storage information of GTTs'data, their
+transaction information, and their statistics are not stored in the catalog.
+
+2) STORAGE INFO & STATISTICS & TRANSACTION
+In order to maintain durability and availability of GTTs'session-specific data,
+their storage information, statistics, and transaction information is managed
+in a local hash table tt_storage_local_hash.
+
+3) DDL
+A shared hash table active_gtt_shared_hash is added to track the state of the
+GTT in a different session. This information is recorded in the hash table
+during the DDL execution of the GTT.
+
+4) LOCK
+The data stored in a GTT can only be modified or accessed by owning session.
+The statements that only modify data in a GTT do not need a high level of table
+locking.
+The operations making those changes include truncate GTT, Vacuum/Cluster GTT,
+and Lock GTT.
+
+Detailed design
+-----------------------------------------
+
+1. CATALOG
+1.1 relpersistence
+define RELPERSISTENCEGLOBALTEMP 'g'
+Mark Global Temporary Table in pg_class relpersistence to 'g'. The relpersistence
+of indexes created on the GTT, sequences on GTT and toast tables on GTT are
+also set to 'g'
+
+1.2 on commit clause
+LTT's status associated with on commit DELETE ROWS and on commit PRESERVE ROWS
+is not stored in catalog. Instead, GTTs need a bool value on_commit_delete_rows
+in reloptions which is shared among sessions.
+
+1.3 gram.y
+GTT is already supported in syntax tree. We remove the warning message
+"GLOBAL is deprecated in temporary table creation" and mark
+relpersistence = RELPERSISTENCEGLOBALTEMP.
+
+2. STORAGE INFO & STATISTICS DATA & TRANSACTION INFO
+2.1. gtt_storage_local_hash
+Each backend creates a local hashtable gtt_storage_local_hash to track a GTT's
+storage file information, statistics, and transaction information.
+
+2.2 GTT storage file info track
+1) When one session inserts data into a GTT for the first time, record the
+storage info to gtt_storage_local_hash.
+2) Use beforeshmemexit to ensure that all files of session GTT are deleted when
+the session exits.
+
+2.3 statistics info
+1) relpages reltuples relallvisible
+2) The statistics of each column from pg_statistic
+All the above information is stored in gtt_storage_local_hash.
+When doing vacuum or analyze, GTT's statistic is updated, which is used by
+the SQL planner.
+The statistics summarizes only data in the current session.
+
+2.3 transaction info track
+frozenxid minmulti from pg_class is stored to gtt_storage_local_hash.
+
+4 DDL
+4.1. active_gtt_shared_hash
+This is the hash table created in shared memory to trace the GTT files initialized
+in each session. Each hash entry contains a bitmap that records the backendid of
+the initialized GTT file. With this hash table, we know which backend/session
+is using this GTT. Such information is used during GTT's DDL operations.
+
+4.1 DROP GTT
+One GTT is allowed to be deleted when there is only one session using the table
+and the session is the current session.
+After holding the AccessExclusiveLock lock on GTT, active_gtt_shared_hash
+is checked to ensure that.
+
+4.2 ALTER GTT/DROP INDEX ON GTT
+Same as drop GTT.
+
+4.3 CREATE INDEX ON GTT
+1) create index on GTT statements build index based on local data in a session.
+2) After the index is created, record the index metadata to the catalog.
+3) Other sessions can enable or disable the local GTT index.
+
+5 LOCK
+
+5.1 TRUNCATE GTT
+The truncate GTT command uses RowExclusiveLock, not AccessExclusiveLock, because
+this command only cleans up local data and local buffers in current session.
+
+5.2 CLUSTER GTT/VACUUM FULL GTT
+Same as truncate GTT.
+
+5.3 Lock GTT
+A lock GTT statement does not hold any table locks.
+
+6 MVCC commit log(clog) cleanup
+
+The GTT storage file contains transaction information. Queries for GTT data rely
+on transaction information such as clog. The transaction information required by
+each session may be completely different. We need to ensure that the transaction
+information of the GTT data is not cleaned up during its lifetime and that
+transaction resources are recycled at the instance level.
+
+6.1 The session level GTT oldest frozenxid
+1) To manage all GTT transaction information, add session level oldest frozenxid
+in each session. When one GTT is created or removed, record the session level
+oldest frozenxid and store it in MyProc.
+2) When vacuum advances the database's frozenxid, session level oldest frozenxid
+should be considered. This is acquired by searching all of MyProc. This way,
+we can avoid the clog required by GTTs to be cleaned.
+
+6.2 vacuum GTT
+Users can perform vacuum over a GTT to clean up local data in the GTT.
+
+6.3 autovacuum GTT
+Autovacuum skips all GTTs, because the data in GTTs is only visible in current
+session.
+
+7 OTHERS
+Parallel query
+Planner does not produce parallel query plans for SQL related to GTT. Because
+GTT private data cannot be accessed across processes.
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index aa7d4d5456..595cb03eb4 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -504,6 +504,7 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 83746d3fd9..479b272232 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -62,6 +62,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,
@@ -306,7 +308,8 @@ heap_create(const char *relname,
 			bool mapped_relation,
 			bool allow_system_table_mods,
 			TransactionId *relfrozenxid,
-			MultiXactId *relminmxid)
+			MultiXactId *relminmxid,
+			bool skip_create_storage)
 {
 	bool		create_storage;
 	Relation	rel;
@@ -366,13 +369,26 @@ heap_create(const char *relname,
 			break;
 	}
 
+	/* For global temporary table, even if the storage is not initialized,
+	 * the relfilenode needs to be generated and put into the catalog.
+	 */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		if (skip_create_storage)
+			create_storage = false;
+
+		if (!OidIsValid(relfilenode))
+			relfilenode = relid;
+	}
 	/*
 	 * Decide whether to create storage. If caller passed a valid relfilenode,
 	 * storage is already created, so don't do it here.  Also don't create it
 	 * for relkinds without physical storage.
 	 */
-	if (!RELKIND_HAS_STORAGE(relkind) || OidIsValid(relfilenode))
+	else if (!RELKIND_HAS_STORAGE(relkind) || OidIsValid(relfilenode))
+	{
 		create_storage = false;
+	}
 	else
 	{
 		create_storage = true;
@@ -427,7 +443,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:
@@ -445,7 +461,8 @@ heap_create(const char *relname,
 	 * protected by the existence of a physical file; but for relations with
 	 * no files, add a pg_shdepend entry to account for that.
 	 */
-	if (!create_storage && reltablespace != InvalidOid)
+	if (!create_storage && reltablespace != InvalidOid &&
+		relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
 		recordDependencyOnTablespace(RelationRelationId, relid,
 									 reltablespace);
 
@@ -998,6 +1015,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -1036,8 +1054,21 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/*
+	 * The transaction information of the global temporary table is stored
+	 * in the local hash table, not in catalog.
+	 */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1303,7 +1334,8 @@ heap_create_with_catalog(const char *relname,
 							   mapped_relation,
 							   allow_system_table_mods,
 							   &relfrozenxid,
-							   &relminmxid);
+							   &relminmxid,
+							   false);
 
 	Assert(relid == RelationGetRelid(new_rel_desc));
 
@@ -1410,6 +1442,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1495,8 +1528,9 @@ heap_create_with_catalog(const char *relname,
 	/*
 	 * If there's a special on-commit action, remember it
 	 */
-	if (oncommit != ONCOMMIT_NOOP)
-		register_on_commit_action(relid, oncommit);
+	if (oncommit != ONCOMMIT_NOOP &&
+		relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+		register_on_commit_action(relid, oncommit, false);
 
 	/*
 	 * ok, the relation has been cataloged, so close our relations and return
@@ -1993,6 +2027,19 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/*
+	 * Only when other sessions are not using this Global temporary table,
+	 * is it allowed to DROP it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot drop global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3277,7 +3324,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3289,7 +3336,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3325,7 +3372,7 @@ RelationTruncateIndexes(Relation heapRelation)
  * ON COMMIT truncation of temporary tables, where it doesn't matter.
  */
 void
-heap_truncate(List *relids)
+heap_truncate(List *relids, bool is_global_temp)
 {
 	List	   *relations = NIL;
 	ListCell   *cell;
@@ -3335,8 +3382,23 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		/*
+		 * Truncate global temporary table only clears local data,
+		 * so only low-level locks need to be held.
+		 */
+		if (is_global_temp)
+		{
+			if (!gtt_storage_attached(rid))
+				continue;
+
+			lockmode = RowExclusiveLock;
+		}
+		else
+			lockmode = AccessExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3369,6 +3431,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3377,23 +3440,37 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	/*
+	 * Truncate GTT only clears local data, so only low-level locks
+	 * need to be held.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		lockmode = AccessShareLock;
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	/*
+	 * After the data is cleaned up on the GTT, the transaction information
+	 * for the data(stored in local hash table) is also need reset.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(RelationGetRelid(rel), 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 26bfa74ce7..a957b43dd6 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -54,6 +54,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"
@@ -732,6 +733,29 @@ index_create(Relation heapRelation,
 	char		relkind;
 	TransactionId relfrozenxid;
 	MultiXactId relminmxid;
+	bool		skip_create_storage = false;
+
+	/* For global temporary table only */
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temporary table with concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot reindex global temporary tables concurrently")));
+
+		/*
+		 * For the case that some backend is applied relcache message to create
+		 * an index on a global temporary table, if this table in the current
+		 * backend are not initialized, the creation of index storage on the
+		 * table are also skipped.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+		{
+			skip_create_storage = true;
+			flags |= INDEX_CREATE_SKIP_BUILD;
+		}
+	}
 
 	/* constraint flags can only be set when a constraint is requested */
 	Assert((constr_flags == 0) ||
@@ -939,7 +963,8 @@ index_create(Relation heapRelation,
 								mapped_relation,
 								allow_system_table_mods,
 								&relfrozenxid,
-								&relminmxid);
+								&relminmxid,
+								skip_create_storage);
 
 	Assert(relfrozenxid == InvalidTransactionId);
 	Assert(relminmxid == InvalidMultiXactId);
@@ -2107,7 +2132,7 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 * lock (see comments in RemoveRelations), and a non-concurrent DROP is
 	 * more efficient.
 	 */
-	Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+	Assert(!RelpersistenceTsTemp(get_rel_persistence(indexId)) ||
 		   (!concurrent && !concurrent_lock_mode));
 
 	/*
@@ -2139,6 +2164,21 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/*
+	 * Allow to drop index on global temporary table when only current
+	 * backend use it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation) &&
+		is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+	{
+		ereport(ERROR,
+			(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+			 errmsg("cannot drop index %s on global temporary table %s",
+					RelationGetRelationName(userIndexRelation), RelationGetRelationName(userHeapRelation)),
+					errdetail("Because the index is created on the global temporary table and other backend attached it."),
+					errhint("Please try detach all sessions using this temporary table and try again.")));
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2747,6 +2787,7 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = RELATION_IS_GLOBAL_TEMP(rel);
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2841,20 +2882,37 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
+		/* For global temporary table */
+		if (is_gtt)
 		{
-			rd_rel->relpages = (int32) relpages;
-			dirty = true;
-		}
-		if (rd_rel->reltuples != (float4) reltuples)
-		{
-			rd_rel->reltuples = (float4) reltuples;
-			dirty = true;
+			/* Update GTT'statistics into local relcache */
+			rel->rd_rel->relpages = (int32) relpages;
+			rel->rd_rel->reltuples = (float4) reltuples;
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+
+			/* Update GTT'statistics into local hashtable */
+			up_gtt_relstats(RelationGetRelid(rel), relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+		else
 		{
-			rd_rel->relallvisible = (int32) relallvisible;
-			dirty = true;
+			if (rd_rel->relpages != (int32) relpages)
+			{
+				rd_rel->relpages = (int32) relpages;
+				dirty = true;
+			}
+
+			if (rd_rel->reltuples != (float4) reltuples)
+			{
+				rd_rel->reltuples = (float4) reltuples;
+				dirty = true;
+			}
+
+			if (rd_rel->relallvisible != (int32) relallvisible)
+			{
+				rd_rel->relallvisible = (int32) relallvisible;
+				dirty = true;
+			}
 		}
 	}
 
@@ -2967,6 +3025,26 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, progress_index, progress_vals);
 	}
 
+	/* For build index on global temporary table */
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		/*
+		 * If the storage for the index in this session is not initialized,
+		 * it needs to be created.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			/* Before create init storage, fix the local Relcache first */
+			force_enable_gtt_index(indexRelation);
+
+			Assert(gtt_storage_attached(RelationGetRelid(heapRelation)));
+
+			/* Init storage for index */
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3508,6 +3586,8 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	PGRUsage	ru0;
 	bool		progress = ((params->options & REINDEXOPT_REPORT_PROGRESS) != 0);
 	bool		set_tablespace = false;
+	LOCKMODE	lockmode_on_heap = ShareLock;
+	LOCKMODE	lockmode_on_index = AccessExclusiveLock;
 
 	pg_rusage_init(&ru0);
 
@@ -3521,10 +3601,29 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	if (!OidIsValid(heapId))
 		return;
 
+	/*
+	 * For reindex on global temporary table, If the storage for the index
+	 * in current backend is not initialized, nothing is done.
+	 */
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		if (!gtt_storage_attached(indexId))
+		{
+			/* Suppress use of the target index while rebuilding it */
+			SetReindexProcessing(heapId, indexId);
+			/* Re-allow use of target index */
+			ResetReindexProcessing();
+			return;
+		}
+
+		lockmode_on_heap = AccessShareLock;
+		lockmode_on_index = AccessShareLock;
+	}
+
 	if ((params->options & REINDEXOPT_MISSING_OK) != 0)
-		heapRelation = try_table_open(heapId, ShareLock);
+		heapRelation = try_table_open(heapId, lockmode_on_heap);
 	else
-		heapRelation = table_open(heapId, ShareLock);
+		heapRelation = table_open(heapId, lockmode_on_heap);
 
 	/* if relation is gone, leave */
 	if (!heapRelation)
@@ -3550,7 +3649,7 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	 * Open the target index relation and get an exclusive lock on it, to
 	 * ensure that no one else is touching this particular index.
 	 */
-	iRel = index_open(indexId, AccessExclusiveLock);
+	iRel = index_open(indexId, lockmode_on_index);
 
 	if (progress)
 		pgstat_progress_update_param(PROGRESS_CREATEIDX_ACCESS_METHOD_OID,
@@ -3801,6 +3900,12 @@ reindex_relation(Oid relid, int flags, ReindexParams *params)
 	bool		result;
 	ListCell   *indexId;
 	int			i;
+	LOCKMODE	lockmode;
+
+	if (flags & REINDEX_REL_PROCESS_GLOBAL_TEMP)
+		lockmode = AccessShareLock;
+	else
+		lockmode = ShareLock;
 
 	/*
 	 * Open and lock the relation.  ShareLock is sufficient since we only need
@@ -3808,9 +3913,9 @@ reindex_relation(Oid relid, int flags, ReindexParams *params)
 	 * should match ReindexTable().
 	 */
 	if ((params->options & REINDEXOPT_MISSING_OK) != 0)
-		rel = try_table_open(relid, ShareLock);
+		rel = try_table_open(relid, lockmode);
 	else
-		rel = table_open(relid, ShareLock);
+		rel = table_open(relid, lockmode);
 
 	/* if relation is gone, leave */
 	if (!rel)
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 4de8400fd0..fe3fcc712c 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -656,6 +656,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/* Do not allow create global temporary table in temporary schemas */
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp table in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index c5ad28d71f..707068a6fd 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,6 +27,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -61,6 +62,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			temprelOid;			/* InvalidOid if not a global temporary rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -115,7 +117,7 @@ AddPendingSync(const RelFileNode *rnode)
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -126,7 +128,12 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/*
+		 * Global temporary table and local temporary table use same
+		 * design on storage module.
+		 */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -154,6 +161,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
@@ -165,6 +173,21 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		AddPendingSync(&rnode);
 	}
 
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		Assert(rel && RELATION_IS_GLOBAL_TEMP(rel));
+
+		/*
+		 * Remember the reloid of global temporary table, which is used for
+		 * transaction commit or rollback.
+		 * see smgrDoPendingDeletes.
+		 */
+		pending->temprelOid = RelationGetRelid(rel);
+
+		/* Remember global temporary table storage info to localhash */
+		remember_gtt_storage_info(rnode, rel);
+	}
+
 	return srel;
 }
 
@@ -201,11 +224,20 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/*
+	 * Remember the reloid of global temporary table, which is used for
+	 * transaction commit or rollback.
+	 * see smgrDoPendingDeletes.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->temprelOid = RelationGetRelid(rel);
+
 	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
@@ -618,6 +650,7 @@ smgrDoPendingDeletes(bool isCommit)
 	int			nrels = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -647,14 +680,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->temprelOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -664,12 +701,21 @@ smgrDoPendingDeletes(bool isCommit)
 
 	if (nrels > 0)
 	{
+		int	i;
+
 		smgrdounlinkall(srels, nrels, false);
 
-		for (int i = 0; i < nrels; i++)
+		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* Delete global temporary table info in localhash */
+			if (gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000000..384b4b07af
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1573 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  The body implementation of Global Temparary table.
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *	  See src/backend/catalog/GTT_README for Global temparary table's
+ *	  requirements and design.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_info_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+int		vacuum_gtt_defer_check_age = 0;
+
+/*
+ * The Global temporary table's shared hash table data structure
+ */
+typedef struct gtt_ctl_data
+{
+	LWLock		lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+/* record this global temporary table in which backends are being used */
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+/*
+ * The Global temporary table's local hash table data structure
+ */
+/* Record the storage information and statistical information of the global temporary table */
+typedef struct
+{
+	Oid			relfilenode;
+	Oid			spcnode;
+
+	/* pg_class relstat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+
+	/* pg_statistic column stat */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_free_statistics(gtt_relfilenode *rnode);
+static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok);
+static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok);
+static Bitmapset *copy_active_gtt_bitmap(Oid relid);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+/*
+ * Calculate shared hash table entry size for GTT.
+ */
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	/* hash entry header size */
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	/*
+	 * hash entry data size
+	 * this is a bitmap in shared memory, each backend have a bit.
+	 */
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+/*
+ * Calculate shared hash table max size for GTT.
+ */
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	/* shared hash header size */
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	/* hash entry size */
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	/* max size */
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+/*
+ * Initialization shared hash table for GTT.
+ */
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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();
+	}
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(gtt_fnode);
+	info.entrysize = action_gtt_shared_hash_entry_size();
+	active_gtt_shared_hash =
+		ShmemInitHash("active gtt shared hash",
+						gtt_shared_ctl->max_entry,
+						gtt_shared_ctl->max_entry,
+						&info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+/*
+ * Record GTT relid to shared hash table, which means that current backend is using this GTT.
+ */
+static void
+gtt_storage_checkin(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			found;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *)&(fnode), 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_global_temporary_table.")));
+	}
+
+	if (!found)
+	{
+		int			wordnum;
+
+		/* init bitmap */
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	/* record itself in bitmap */
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+/*
+ * Remove the GTT relid record from the shared hash table which means that current backend is
+ * not use this GTT.
+ */
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	if (!skiplock)
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		if (!skiplock)
+			LWLockRelease(&gtt_shared_ctl->lock);
+
+		if (isCommit)
+			elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+	/* remove itself from bitmap */
+	bms_del_member(entry->map, MyBackendId);
+
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+
+	if (!skiplock)
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+	return;
+}
+
+/*
+ * Gets usage information for a GTT from shared hash table.
+ * The information is in the form of bitmap.
+ * Quickly copy the entire bitmap from shared memory and return it.
+ * that to avoid holding locks for a long time.
+ */
+static Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	Bitmapset		*map_copy = NULL;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		return NULL;
+	}
+
+	Assert(entry->map);
+
+	/* copy the entire bitmap */
+	if (!bms_is_empty(entry->map))
+		map_copy = bms_copy(entry->map);
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return map_copy;
+}
+
+/*
+ * Check if there are other backends using this GTT besides the current backend.
+ */
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			in_use = false;
+	int			num_use = 0;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		return false;
+	}
+
+	Assert(entry->map);
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+	/* how many backend are using this GTT */
+	num_use = bms_num_members(entry->map);
+	if (num_use == 0)
+		in_use = false;
+	else if (num_use == 1)
+	{
+		/* check if this is itself */
+		if(bms_is_member(MyBackendId, entry->map))
+			in_use = false;
+		else
+			in_use = true;
+	}
+	else
+		in_use = true;
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return in_use;
+}
+
+/*
+ * Record GTT information to local hash.
+ * They include GTT storage info, transaction info and statistical info.
+ */
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+	gtt_local_hash_entry		*entry;
+	MemoryContext			oldcontext;
+	gtt_relfilenode			*new_node = NULL;
+	Oid				relid = RelationGetRelid(rel);
+	int				natts = 0;
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	/* First time through: initialize the hash table */
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		HASHCTL		ctl;
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_info_context =
+			AllocSetContextCreate(CacheMemoryContext,
+								"gtt info context",
+								ALLOCSET_DEFAULT_SIZES);
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		ctl.hcxt = gtt_info_context;
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+	}
+
+	Assert(CacheMemoryContext);
+	Assert(gtt_info_context);
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	entry = gtt_search_by_relid(relid, true);
+	if (!entry)
+	{
+		bool		found = false;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash,
+						(void *) &relid, HASH_ENTER, &found);
+
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			/* record the on commit clause */
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS, true);
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			gtt_storage_checkin(relid);
+		}
+	}
+
+	/* record storage info relstat columnstats and transaction info to relfilenode list */
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	new_node->natts = 0;
+	new_node->attnum = NULL;
+	new_node->att_stat_tups = NULL;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* init column stats structure */
+	natts = RelationGetNumberOfAttributes(rel);
+	new_node->attnum = palloc0(sizeof(int) * natts);
+	new_node->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+	new_node->natts = natts;
+
+	/* only heap have transaction info */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+
+		/**/
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+
+	/* Registration callbacks are used to trigger cleanup during process exit */
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+/*
+ * Remove GTT information from local hash when transaction commit/rollback.
+ */
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	gtt_relfilenode		*d_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+		return;
+	}
+
+	d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+		else
+		{
+			/* rollback transaction */
+			if (entry->relfilenode_list == NIL)
+			{
+				if (entry->relkind == RELKIND_RELATION ||
+					entry->relkind == RELKIND_SEQUENCE)
+					gtt_storage_checkout(relid, false, isCommit);
+
+				hash_search(gtt_storage_local_hash,
+						(void *) &(relid), HASH_REMOVE, NULL);
+			}
+
+			return;
+		}
+	}
+
+	/* Clean up transaction info from Local order list and MyProc */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+
+		/* this is valid relfrozenxid */
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	/* delete relfilenode from rel entry */
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	gtt_free_statistics(d_rnode);
+
+	if (entry->relfilenode_list == NIL)
+	{
+		/* this means we truncate this GTT at current backend */
+
+		/* tell shared hash that current backend will no longer use this GTT */
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(relid, false, isCommit);
+
+		hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_REMOVE, NULL);
+	}
+
+	return;
+}
+
+/*
+ * Check if current backend is using this GTT.
+ */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool			found = false;
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (!OidIsValid(relid))
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry)
+		found = true;
+
+	return found;
+}
+
+/*
+ * When backend exit, bulk cleaning all GTT storage and local buffer of this backend.
+ */
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS		status;
+	gtt_local_hash_entry	*entry;
+	SMgrRelation		*srels = NULL;
+	Oid			*relids = NULL;
+	char			*relkinds = NULL;
+	int			nrels = 0,
+				nfiles = 0,
+				maxrels = 0,
+				maxfiles = 0,
+				i = 0;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	/* Search all relfilenode for GTT in current backend */
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel;
+			RelFileNode		rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel = smgropen(rnode, MyBackendId);
+
+			if (maxfiles == 0)
+			{
+				maxfiles = 32;
+				srels = palloc(sizeof(SMgrRelation) * maxfiles);
+			}
+			else if (maxfiles <= nfiles)
+			{
+				maxfiles *= 2;
+				srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+			}
+
+			srels[nfiles++] = srel;
+		}
+
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			relids  = palloc(sizeof(Oid) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			relids  = repalloc(relids , sizeof(Oid) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		relkinds[nrels] = entry->relkind;
+		relids[nrels] = entry->relid;
+		nrels++;
+	}
+
+	/* drop local buffer and storage */
+	if (nfiles > 0)
+	{
+		/* Need to ensure we have a usable transaction. */
+                AbortOutOfAnyTransaction();
+
+		smgrdounlinkall(srels, nfiles, false);
+		for (i = 0; i < nfiles; i++)
+			smgrclose(srels[i]);
+
+		pfree(srels);
+	}
+
+	if (nrels)
+	{
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+		for (i = 0; i < nrels; i++)
+		{
+			/* tell shared hash */
+			if (relkinds[i] == RELKIND_RELATION ||
+				relkinds[i] == RELKIND_SEQUENCE)
+				gtt_storage_checkout(relids[i], true, false);
+		}
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+		pfree(relids);
+		pfree(relkinds);
+	}
+
+	/* set to global area */
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;
+
+	return;
+}
+
+/*
+ * Update GTT relstats(relpage/reltuple/relallvisible)
+ * to local hash.
+ */
+void
+up_gtt_relstats(Oid relid,
+					BlockNumber num_pages,
+					double num_tuples,
+					BlockNumber num_all_visible_pages,
+					TransactionId relfrozenxid,
+					TransactionId relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (!OidIsValid(relid))
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (num_pages > 0 &&
+		gtt_rnode->relpages != (int32)num_pages)
+		gtt_rnode->relpages = (int32)num_pages;
+
+	if (num_tuples > 0 &&
+		gtt_rnode->reltuples != (float4)num_tuples)
+		gtt_rnode->reltuples = (float4)num_tuples;
+
+	/* only heap contain transaction information and relallvisible */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (num_all_visible_pages > 0 &&
+			gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+		{
+			gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+		}
+
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNextTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			/* set to local order list */
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			/* set to global area */
+			set_gtt_session_relfrozenxid();
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search GTT relstats(relpage/reltuple/relallvisible)
+ * from local has.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update GTT info(definition is same as pg_statistic)
+ * to local hash.
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	MemoryContext		oldcontext;
+	int			i = 0;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return;
+
+	Assert(entry->relid == reloid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (gtt_rnode->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	/* switch context to gtt_info_context for store tuple at heap_form_tuple */
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == 0)
+		{
+			gtt_rnode->attnum[i] = attnum;
+			break;
+		}
+		else if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			heap_freetuple(gtt_rnode->att_stat_tups[i]);
+			gtt_rnode->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < gtt_rnode->natts);
+	Assert(gtt_rnode->att_stat_tups[i] == NULL);
+	gtt_rnode->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search GTT statistic info(definition is same as pg_statistic)
+ * from local hash.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	int			i = 0;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return NULL;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return NULL;
+
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			return gtt_rnode->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Insert a RelfrozenXID into the list and keep the list in order.
+ */
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int		i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Remove a RelfrozenXID from order list gtt_session_relfrozenxid_list.
+ */
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+/*
+ * Update of backend Level oldest relfrozenxid to MyProc.
+ * This makes each backend's oldest RelFrozenxID globally visible.
+ */
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->backend_gtt_frozenxid != gtt_frozenxid)
+		MyProc->backend_gtt_frozenxid = gtt_frozenxid;
+}
+
+/*
+ * Get GTT column level data statistics.
+ */
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate		*tupstore;
+	HeapTuple		tuple;
+	Relation		rel = NULL;
+	Oid			reloid = PG_GETARG_OID(0);
+	int			attnum = PG_GETARG_INT32(1);
+	char			rel_persistence;
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	/* get data from local hash */
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum		values[31];
+		bool		isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, NoLock);
+	relation_close(pg_tatistic, AccessShareLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get GTT table level data statistics.
+ */
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate	*tupstore;
+	TupleDesc	tupdesc;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid			reloid = PG_GETARG_OID(0);
+	Oid			relnode = 0;
+	char			rel_persistence;
+	BlockNumber		relpages = 0;
+	BlockNumber		relallvisible = 0;
+	uint32			relfrozenxid = 0;
+	uint32			relminmxid = 0;
+	double			reltuples = 0;
+	Relation		rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[6];
+		bool	isnull[6];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get a list of backend pids that are currently using this GTT.
+ */
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	PGPROC			*proc = NULL;
+	Bitmapset		*map = NULL;
+	Tuplestorestate		*tupstore;
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	HeapTuple		tuple;
+	Oid			reloid = PG_GETARG_OID(0);
+	char			rel_persistence;
+	Relation		rel = NULL;
+	pid_t			pid = 0;
+	int				backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	/* get data from share hash */
+	map = copy_active_gtt_bitmap(reloid);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			/* backendid map to process pid */
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get backend level oldest relfrozenxid of each backend using GTT in current database.
+ */
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate		*tupstore;
+	int			*pids = NULL;
+	uint32			*xids = NULL;
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	HeapTuple		tuple;
+	int			num_xid = MaxBackends + 1;
+	int			i = 0;
+	int			j = 0;
+	uint32			oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+
+	/* Get backend level oldest relfrozenxid in all backend that in MyDatabaseId use GTT */
+	oldest = list_all_backend_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+/*
+ * In order to build the GTT index, force enable GTT'index.
+ */
+void
+force_enable_gtt_index(Relation index)
+{
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(RelationGetRelid(index)));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+/*
+ * Fix the local state of the GTT's index.
+ */
+void
+gtt_fix_index_backend_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid heapOid = index->rd_index->indrelid;
+
+	/* Must be GTT */
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	/*
+	 * If this GTT is not initialized in the current backend,
+	 * its index status is temporarily set to invalid(local relcache).
+	 */
+	if (gtt_storage_attached(heapOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+/*
+ * During the SQL initialization of the executor (InitPlan)
+ * Initialize storage of GTT GTT'indexes and build empty index.
+ */
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int		i;
+	Oid		toastrelid;
+
+	if (!(operation == CMD_UTILITY || operation == CMD_INSERT))
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	/* Each GTT is initialized once in each backend */
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	/* init heap storage */
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+
+		index_build(relation, index, info, true, false);
+	}
+
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+
+		/* init index storage */
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid			indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+			/* build empty index */
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+/*
+ * Release the data structure memory used to store GTT storage info.
+ */
+static void
+gtt_free_statistics(gtt_relfilenode *rnode)
+{
+	int i;
+
+	Assert(rnode);
+
+	for (i = 0; i < rnode->natts; i++)
+	{
+		if (rnode->att_stat_tups[i])
+		{
+			heap_freetuple(rnode->att_stat_tups[i]);
+			rnode->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (rnode->attnum)
+		pfree(rnode->attnum);
+
+	if (rnode->att_stat_tups)
+		pfree(rnode->att_stat_tups);
+
+	pfree(rnode);
+
+	return;
+}
+
+/*
+ * Get the current relfilenode of this GTT.
+ */
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
+/*
+ * Get a relfilenode used by this GTT during the transaction life cycle.
+ */
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok)
+{
+	gtt_relfilenode		*rnode = NULL;
+	ListCell		*lc;
+
+	Assert(entry);
+
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == relfilenode)
+		{
+			rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (!missing_ok && rnode == NULL)
+		elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+	return rnode;
+}
+
+/*
+ * Get one GTT info from local hash.
+ */
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry == NULL && !missing_ok)
+		elog(ERROR, "relid %u not found in local hash", relid);
+
+	return entry;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 55f6e3711d..fc75533263 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 8bfb2ad958..500305e8ae 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -104,7 +105,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -185,6 +186,17 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -601,14 +613,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]));
 		}
 
 		/*
@@ -1633,7 +1646,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;
@@ -1735,31 +1748,48 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/*
+		 * For global temporary table,
+		 * Update column statistic to localhash, not catalog.
+		 */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 9d22f648a8..a44eefa1f6 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/toasting.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
@@ -390,6 +391,22 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+	{
+		if (gtt_storage_attached(RelationGetRelid(OldHeap)))
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("not support cluster global temporary table yet")));
+
+		relation_close(OldHeap, AccessExclusiveLock);
+		pgstat_progress_end_command();
+		return;
+	}
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
@@ -585,6 +602,8 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
 	TransactionId frozenXid;
 	MultiXactId cutoffMulti;
 
+	Assert(!RELATION_IS_GLOBAL_TEMP(OldHeap));
+
 	/* Mark the correct index as clustered */
 	if (OidIsValid(indexOid))
 		mark_index_clustered(OldHeap, indexOid, true);
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 53f4853141..c03191cce9 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -289,7 +289,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP_ON_CURRENT_SESSION(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, whereClause,
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 40a54ad0bd..7895e7d99b 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -30,6 +30,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/copyfrom_internal.h"
 #include "commands/progress.h"
@@ -659,6 +660,9 @@ CopyFrom(CopyFromState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	/* Check and init global temporary table storage in current backend */
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	/*
 	 * Set up a ModifyTableState so we can let FDW(s) init themselves for
 	 * foreign-table result relation(s).
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index c14ca27c5e..d65ae89535 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -111,6 +111,7 @@ struct ReindexIndexCallbackState
 {
 	ReindexParams params;		/* options from statement */
 	Oid			locked_table_oid;	/* tracks previously locked table */
+	LOCKMODE	lockmode;
 };
 
 /*
@@ -570,7 +571,7 @@ DefineIndex(Oid relationId,
 	 * is more efficient.  Do this before any use of the concurrent option is
 	 * done.
 	 */
-	if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+	if (stmt->concurrent && !RelpersistenceTsTemp(get_rel_persistence(relationId)))
 		concurrent = true;
 	else
 		concurrent = false;
@@ -2581,9 +2582,9 @@ ReindexIndex(RangeVar *indexRelation, ReindexParams *params, bool isTopLevel)
 	 */
 	state.params = *params;
 	state.locked_table_oid = InvalidOid;
+	state.lockmode = AccessShareLock;
 	indOid = RangeVarGetRelidExtended(indexRelation,
-									  (params->options & REINDEXOPT_CONCURRENTLY) != 0 ?
-									  ShareUpdateExclusiveLock : AccessExclusiveLock,
+									  AccessShareLock,
 									  0,
 									  RangeVarCallbackForReindexIndex,
 									  &state);
@@ -2594,11 +2595,25 @@ ReindexIndex(RangeVar *indexRelation, ReindexParams *params, bool isTopLevel)
 	 */
 	persistence = get_rel_persistence(indOid);
 	relkind = get_rel_relkind(indOid);
+	if (persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		LOCKMODE	lockmode;
+
+		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0)
+			lockmode = ShareUpdateExclusiveLock;
+		else
+			lockmode = AccessExclusiveLock;
+
+		/* lock heap first */
+		Assert(OidIsValid(state.locked_table_oid));
+		LockRelationOid(state.locked_table_oid, lockmode);
+		LockRelationOid(indOid, lockmode);
+	}
 
 	if (relkind == RELKIND_PARTITIONED_INDEX)
 		ReindexPartitions(indOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 persistence != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(persistence))
 		ReindexRelationConcurrently(indOid, params);
 	else
 	{
@@ -2620,15 +2635,7 @@ RangeVarCallbackForReindexIndex(const RangeVar *relation,
 {
 	char		relkind;
 	struct ReindexIndexCallbackState *state = arg;
-	LOCKMODE	table_lockmode;
-
-	/*
-	 * Lock level here should match table lock in reindex_index() for
-	 * non-concurrent case and table locks used by index_concurrently_*() for
-	 * concurrent case.
-	 */
-	table_lockmode = (state->params.options & REINDEXOPT_CONCURRENTLY) != 0 ?
-		ShareUpdateExclusiveLock : ShareLock;
+	LOCKMODE	table_lockmode = state->lockmode;
 
 	/*
 	 * If we previously locked some other index's heap, and the name we're
@@ -2689,6 +2696,8 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 {
 	Oid			heapOid;
 	bool		result;
+	char		relpersistence;
+	int 		reindex_flags = 0;
 
 	/*
 	 * The lock level used here should match reindex_relation().
@@ -2699,15 +2708,27 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 	 * locks on our temporary table.
 	 */
 	heapOid = RangeVarGetRelidExtended(relation,
-									   (params->options & REINDEXOPT_CONCURRENTLY) != 0 ?
-									   ShareUpdateExclusiveLock : ShareLock,
+									   AccessShareLock,
 									   0,
 									   RangeVarCallbackOwnsTable, NULL);
 
+	relpersistence = get_rel_persistence(heapOid);
+	if (relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		LOCKMODE	lockmode;
+
+		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0)
+			lockmode = ShareUpdateExclusiveLock;
+		else
+			lockmode = ShareLock;
+
+		LockRelationOid(heapOid, lockmode);
+	}
+
 	if (get_rel_relkind(heapOid) == RELKIND_PARTITIONED_TABLE)
 		ReindexPartitions(heapOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(relpersistence))
 	{
 		result = ReindexRelationConcurrently(heapOid, params);
 
@@ -2721,9 +2742,14 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 		ReindexParams newparams = *params;
 
 		newparams.options |= REINDEXOPT_REPORT_PROGRESS;
+
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			reindex_flags |= REINDEX_REL_PROCESS_GLOBAL_TEMP;
+
+		reindex_flags |= REINDEX_REL_PROCESS_TOAST;
+		reindex_flags |= REINDEX_REL_CHECK_CONSTRAINTS;
 		result = reindex_relation(heapOid,
-								  REINDEX_REL_PROCESS_TOAST |
-								  REINDEX_REL_CHECK_CONSTRAINTS,
+								  reindex_flags,
 								  &newparams);
 		if (!result)
 			ereport(NOTICE,
@@ -3122,7 +3148,7 @@ ReindexMultipleInternal(List *relids, ReindexParams *params)
 			   relkind != RELKIND_PARTITIONED_TABLE);
 
 		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			relpersistence != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(relpersistence))
 		{
 			ReindexParams newparams = *params;
 
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 62465bacd8..519c9ea82e 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -57,7 +57,10 @@ LockTableCommand(LockStmt *lockstmt)
 										  RangeVarCallbackForLockTable,
 										  (void *) &lockstmt->mode);
 
-		if (get_rel_relkind(reloid) == RELKIND_VIEW)
+		/* Lock table command ignores global temporary table. */
+		if (get_rel_persistence(reloid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+		else if (get_rel_relkind(reloid) == RELKIND_VIEW)
 			LockViewRecurse(reloid, lockstmt->mode, lockstmt->nowait, NIL);
 		else if (recurse)
 			LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait);
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 72bfdc07a4..9849f6bce6 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -220,9 +223,12 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	rel = table_open(seqoid, AccessExclusiveLock);
 	tupDesc = RelationGetDescr(rel);
 
-	/* now initialize the sequence's data */
-	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	if (!RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		/* now initialize the sequence's data */
+		tuple = heap_form_tuple(tupDesc, value, null);
+		fill_seq_with_data(rel, tuple);
+	}
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -275,8 +281,6 @@ ResetSequence(Oid seq_relid)
 	Buffer		buf;
 	HeapTupleData seqdatatuple;
 	HeapTuple	tuple;
-	HeapTuple	pgstuple;
-	Form_pg_sequence pgsform;
 	int64		startv;
 
 	/*
@@ -287,12 +291,7 @@ ResetSequence(Oid seq_relid)
 	init_sequence(seq_relid, &elm, &seq_rel);
 	(void) read_seq_tuple(seq_rel, &buf, &seqdatatuple);
 
-	pgstuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seq_relid));
-	if (!HeapTupleIsValid(pgstuple))
-		elog(ERROR, "cache lookup failed for sequence %u", seq_relid);
-	pgsform = (Form_pg_sequence) GETSTRUCT(pgstuple);
-	startv = pgsform->seqstart;
-	ReleaseSysCache(pgstuple);
+	startv = get_seqence_start_value(seq_relid);
 
 	/*
 	 * Copy the existing sequence tuple.
@@ -451,6 +450,15 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 
 	init_sequence(relid, &elm, &seqrel);
 
+	if (RELATION_IS_GLOBAL_TEMP(seqrel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(seqrel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary sequence %s when other backend attached it.",
+						RelationGetRelationName(seqrel))));
+	}
+
 	rel = table_open(SequenceRelationId, RowExclusiveLock);
 	seqtuple = SearchSysCacheCopy1(SEQRELID,
 								   ObjectIdGetDatum(relid));
@@ -611,7 +619,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +944,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1153,6 +1161,14 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	/* Initializes the storage for sequence which the global temporary table belongs. */
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1927,3 +1943,51 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+/*
+ * Get the startValue of the sequence from syscache.
+ */
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+/*
+ * Initialize sequence which global temporary table belongs.
+ */
+void
+gtt_init_seq(Relation rel)
+{
+	Datum		value[SEQ_COL_LASTCOL] = {0};
+	bool		null[SEQ_COL_LASTCOL] = {false};
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index dbee6ae199..a5629506e2 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -118,6 +119,7 @@ typedef struct OnCommitItem
 	 */
 	SubTransactionId creating_subid;
 	SubTransactionId deleting_subid;
+	bool			 is_global_temp;
 } OnCommitItem;
 
 static List *on_commits = NIL;
@@ -602,7 +604,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
 static char GetAttributeCompression(Oid atttypid, char *compression);
-
+static OnCommitAction gtt_oncommit_option(List *options);
 
 /* ----------------------------------------------------------------
  *		DefineRelation
@@ -647,6 +649,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -658,7 +661,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * Check consistency of arguments
 	 */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !RelpersistenceTsTemp(stmt->relation->relpersistence))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -688,7 +691,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	if (RelpersistenceTsTemp(stmt->relation->relpersistence)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -789,6 +792,50 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* For global temporary table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		if (!(relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE))
+			elog(ERROR, "Only support global temporary regular table.");
+
+		/* Check parent table */
+		if (inheritOids)
+			elog(ERROR, "Not support global temporary partition table or inherit table.");
+
+		/* Check oncommit clause and save to reloptions */
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temporary table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1415,7 +1462,7 @@ RemoveRelations(DropStmt *drop)
 		 * relation persistence cannot be known without its OID.
 		 */
 		if (drop->concurrent &&
-			get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(get_rel_persistence(relOid)))
 		{
 			Assert(list_length(drop->objects) == 1 &&
 				   drop->removeType == OBJECT_INDEX);
@@ -1624,9 +1671,9 @@ ExecuteTruncate(TruncateStmt *stmt)
 		Relation	rel;
 		bool		recurse = rv->inh;
 		Oid			myrelid;
-		LOCKMODE	lockmode = AccessExclusiveLock;
+		LOCKMODE	lockmode;
 
-		myrelid = RangeVarGetRelidExtended(rv, lockmode,
+		myrelid = RangeVarGetRelidExtended(rv, AccessShareLock,
 										   0, RangeVarCallbackForTruncate,
 										   NULL);
 
@@ -1634,9 +1681,21 @@ ExecuteTruncate(TruncateStmt *stmt)
 		if (list_member_oid(relids, myrelid))
 			continue;
 
-		/* open the relation, we already hold a lock on it */
+		/* open the relation, we need hold a low-level lock first */
 		rel = table_open(myrelid, NoLock);
 
+		/*
+		 * Truncate global temp table only cleans up the data in current backend,
+		 * only low-level locks are required.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel))
+			lockmode = AccessShareLock;
+		else
+		{
+			lockmode = AccessExclusiveLock;
+			LockRelationOid(myrelid, lockmode);
+		}
+
 		/*
 		 * RangeVarGetRelidExtended() has done most checks with its callback,
 		 * but other checks with the now-opened Relation remain.
@@ -1886,6 +1945,7 @@ ExecuteTruncateGuts(List *explicit_rels,
 	foreach(cell, rels)
 	{
 		Relation	rel = (Relation) lfirst(cell);
+		LOCKMODE	lockmode;
 
 		/* Skip partitioned tables as there is nothing to do */
 		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
@@ -1936,6 +1996,19 @@ ExecuteTruncateGuts(List *explicit_rels,
 			continue;
 		}
 
+		/*
+		 * Skip the global temporary table that is not initialized for storage
+		 * in current backend.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel))
+		{
+			lockmode = AccessShareLock;
+			if (!gtt_storage_attached(RelationGetRelid(rel)))
+				continue;
+		}
+		else
+			lockmode = AccessExclusiveLock;
+
 		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
@@ -1954,6 +2027,7 @@ ExecuteTruncateGuts(List *explicit_rels,
 			Oid			heap_relid;
 			Oid			toast_relid;
 			ReindexParams reindex_params = {0};
+			int			reindex_flags = 0;
 
 			/*
 			 * This effectively deletes all rows in the table, and may be done
@@ -1981,17 +2055,21 @@ ExecuteTruncateGuts(List *explicit_rels,
 			if (OidIsValid(toast_relid))
 			{
 				Relation	toastrel = relation_open(toast_relid,
-													 AccessExclusiveLock);
+													 lockmode);
 
 				RelationSetNewRelfilenode(toastrel,
 										  toastrel->rd_rel->relpersistence);
 				table_close(toastrel, NoLock);
 			}
 
+			reindex_flags = REINDEX_REL_PROCESS_TOAST;
+			if (RELATION_IS_GLOBAL_TEMP(rel))
+				reindex_flags |= REINDEX_REL_PROCESS_GLOBAL_TEMP;
+
 			/*
 			 * Reconstruct the indexes to match, and we're done.
 			 */
-			reindex_relation(heap_relid, REINDEX_REL_PROCESS_TOAST,
+			reindex_relation(heap_relid, reindex_flags,
 							 &reindex_params);
 		}
 
@@ -3998,6 +4076,16 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temporary table only current backend use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -5329,6 +5417,24 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 
 			rel = table_open(tab->relid, NoLock);
 			find_composite_type_dependencies(rel->rd_rel->reltype, rel, NULL);
+
+			if (RELATION_IS_GLOBAL_TEMP(rel) && tab->rewrite > 0)
+			{
+				if (tab->chgPersistence)
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot change global temporary table persistence setting")));
+
+				if(gtt_storage_attached(tab->relid))
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("Only support alter global temporary table in an empty context."),
+						 errhint("Please create a new connection and execute ALTER TABLE on the new connection.")));
+
+				/* There is no need to override the whole temp table */
+				tab->rewrite = 0;
+			}
+
 			table_close(rel, NoLock);
 		}
 
@@ -5380,6 +5486,8 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot rewrite temporary tables of other sessions")));
 
+			Assert(!RELATION_IS_GLOBAL_TEMP(OldHeap));
+
 			/*
 			 * Select destination tablespace (same as original unless user
 			 * requested a change)
@@ -9011,6 +9119,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -13709,6 +13823,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -13908,6 +14025,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temporary table");
+
 	/* Check first if relation can be moved to new tablespace */
 	if (!CheckRelationTableSpaceMove(rel, newTableSpace))
 	{
@@ -14211,7 +14331,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(RelationGetSmgr(rel), dstrel, MAIN_FORKNUM,
@@ -15809,6 +15929,7 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
 	switch (rel->rd_rel->relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -16223,7 +16344,7 @@ AlterSeqNamespaces(Relation classRel, Relation rel,
  * Register a newly-created relation's ON COMMIT action.
  */
 void
-register_on_commit_action(Oid relid, OnCommitAction action)
+register_on_commit_action(Oid relid, OnCommitAction action, bool is_gloal_temp)
 {
 	OnCommitItem *oc;
 	MemoryContext oldcxt;
@@ -16242,6 +16363,7 @@ register_on_commit_action(Oid relid, OnCommitAction action)
 	oc->oncommit = action;
 	oc->creating_subid = GetCurrentSubTransactionId();
 	oc->deleting_subid = InvalidSubTransactionId;
+	oc->is_global_temp = is_gloal_temp;
 
 	/*
 	 * We use lcons() here so that ON COMMIT actions are processed in reverse
@@ -16287,6 +16409,7 @@ PreCommit_on_commit_actions(void)
 	ListCell   *l;
 	List	   *oids_to_truncate = NIL;
 	List	   *oids_to_drop = NIL;
+	List	   *oids_to_truncate_gtt = NIL;
 
 	foreach(l, on_commits)
 	{
@@ -16310,7 +16433,12 @@ PreCommit_on_commit_actions(void)
 				 * tables, as they must still be empty.
 				 */
 				if ((MyXactFlags & XACT_FLAGS_ACCESSEDTEMPNAMESPACE))
-					oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid);
+				{
+					if (oc->is_global_temp)
+						oids_to_truncate_gtt = lappend_oid(oids_to_truncate_gtt, oc->relid);
+					else
+						oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid);
+				}
 				break;
 			case ONCOMMIT_DROP:
 				oids_to_drop = lappend_oid(oids_to_drop, oc->relid);
@@ -16327,7 +16455,10 @@ PreCommit_on_commit_actions(void)
 	 * exists at truncation time.
 	 */
 	if (oids_to_truncate != NIL)
-		heap_truncate(oids_to_truncate);
+		heap_truncate(oids_to_truncate, false);
+
+	if (oids_to_truncate_gtt != NIL)
+		heap_truncate(oids_to_truncate_gtt, true);
 
 	if (oids_to_drop != NIL)
 	{
@@ -17326,6 +17457,13 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot attach temporary relation of another session as partition")));
 
+	/* If the parent is permanent, so must be all of its partitions. */
+	if (attachrel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach a global temporary relation as partition of permanent relation \"%s\"",
+						RelationGetRelationName(rel))));
+
 	/* Check if there are any columns in attachrel that aren't in the parent */
 	tupleDesc = RelationGetDescr(attachrel);
 	natts = tupleDesc->natts;
@@ -18761,3 +18899,40 @@ GetAttributeCompression(Oid atttypid, char *compression)
 
 	return cmethod;
 }
+
+/*
+ * Parse the on commit clause for the temporary table
+ */
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			bool	res = false;
+			char	*sval = defGetString(def);
+
+			/* It has to be a Boolean value */
+			if (!parse_bool(sval, &res))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
+
+			if (res)
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 5c4bc15b44..1e0512cf9b 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"
@@ -1315,6 +1316,22 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = RELATION_IS_GLOBAL_TEMP(relation);
+
+	 /* For global temporary table */
+	if (is_gtt)
+	{
+		/* Store relation statistics and transaction information to the localhash */
+		up_gtt_relstats(RelationGetRelid(relation),
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+
+		/* Update relation statistics to local relcache */
+		relation->rd_rel->relpages = (int32) num_pages;
+		relation->rd_rel->reltuples = (float4) num_tuples;
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1328,17 +1345,23 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (!is_gtt &&
+		pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (!is_gtt &&
+		pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (!is_gtt &&
+		pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1383,7 +1406,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNextTransactionId(),
@@ -1394,7 +1418,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1502,6 +1527,13 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/*
+		 * The relfrozenxid for a global temporary talble is stored in localhash,
+		 * not pg_class, See list_all_session_gtt_frozenxids()
+		 */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1559,6 +1591,43 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/* If enable global temporary table */
+	if (max_active_gtt > 0)
+	{
+		TransactionId	safe_age;
+		/*  */
+		TransactionId	oldest_gtt_frozenxid =
+			list_all_backend_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid))
+		{
+			safe_age = oldest_gtt_frozenxid + vacuum_gtt_defer_check_age;
+			if (safe_age < FirstNormalTransactionId)
+				safe_age += FirstNormalTransactionId;
+
+			/*
+			 * We tolerate that the minimum age of gtt is less than
+			 * the minimum age of conventional tables, otherwise it will
+			 * throw warning message.
+			 */
+			if (TransactionIdIsNormal(safe_age) &&
+				TransactionIdPrecedes(safe_age, newFrozenXid))
+			{
+				ereport(WARNING,
+					(errmsg("global temp table oldest relfrozenxid %u is the oldest in the entire db",
+							oldest_gtt_frozenxid),
+					 errdetail("The oldest relfrozenxid in pg_class is %u", newFrozenXid),
+					 errhint("If they differ greatly, please consider cleaning up the data in global temp table.")));
+			}
+
+			/*
+			 * We need to ensure that the clog required by gtt is not cleand.
+			 */
+			if (TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+				newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1910,6 +1979,19 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	/*
+	 * Skip those global temporary table that are not initialized in
+	 * current backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel) &&
+		!gtt_storage_attached(RelationGetRelid(rel)))
+	{
+		relation_close(rel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 4df05a0b33..4c181e2e14 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -527,6 +527,12 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	/* Global temporary table are not sensible. */
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b3ce4bae53..611e3f18a7 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -784,6 +784,10 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		/* This is one kind of temp table */
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 5c723bc54e..a7edceb1a5 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
@@ -533,6 +534,9 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	/* Init storage for partitioned global temporary table in current backend */
+	init_gtt_storage(mtstate->operation, leaf_part_rri);
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d328856ae5..9267138e20 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -38,6 +38,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -633,6 +634,9 @@ ExecInsert(ModifyTableState *mtstate,
 		resultRelInfo->ri_IndexRelationDescs == NULL)
 		ExecOpenIndices(resultRelInfo, onconflict != ONCONFLICT_NONE);
 
+	/* Init storage for global temporary table in current backend */
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	/*
 	 * BEFORE ROW INSERT Triggers.
 	 *
@@ -2812,6 +2816,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		i++;
 	}
 
+
 	/*
 	 * Now we may initialize the subplan.
 	 */
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 296dd75c1b..d971aea254 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -48,7 +48,7 @@
 #include "partitioning/partprune.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/lsyscache.h"
-
+#include "utils/rel.h"
 
 /* results of subquery_is_pushdown_safe */
 typedef struct pushdown_safety_info
@@ -619,7 +619,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			if (RelpersistenceTsTemp(get_rel_persistence(rte->relid)))
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 1e42d75465..15a76b2326 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6070,7 +6070,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
-	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+	if (RELATION_IS_TEMP(heap) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index c5194fdbbf..38d7c65854 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -30,6 +30,7 @@
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temporary table in current backend */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 146ee8dd1e..2d4e9393f0 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2907,6 +2907,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 e3068a374e..159465e018 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3410,17 +3410,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; }
@@ -11683,19 +11677,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 c5c3f26ecf..2a2b278907 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -82,6 +82,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);
 
 
 /*
@@ -3665,3 +3666,53 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * Like function isQueryUsingTempRelation_walker
+ * return true if any relation underlying
+ * the query is a global temporary table.
+ */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* Check if the query uses global temporary table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 1d3ee53244..5e4feacd84 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -447,6 +447,13 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	/*
+	 * If a sequence is bound to a global temporary table, then the sequence
+	 * must been "global temporary"
+	 */
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
@@ -3326,6 +3333,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		cxt.isforeign = false;
 	}
 	cxt.relation = stmt->relation;
+	/* Sets the table persistence to the context */
+	cxt.relation->relpersistence = RelationGetRelPersistence(rel);
 	cxt.rel = rel;
 	cxt.inhRelations = NIL;
 	cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 3b3df8fa8c..ca6c68879d 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2111,6 +2111,14 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/*
+			 * Aotuvacuum cannot vacuum the private data stored in each backend
+			 * that belongs to global temporary table, so skip them.
+			 */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2177,7 +2185,7 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		if (RelpersistenceTsTemp(classForm->relpersistence))
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index e88e4e918b..10eaac892a 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlogutils.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -2934,6 +2935,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * Returns 0 if this global temporary table is not initialized in current
+	 * backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 9fa3e0631e..cc3eb928bc 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -23,6 +23,7 @@
 #include "access/syncscan.h"
 #include "access/twophase.h"
 #include "commands/async.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
@@ -143,6 +144,7 @@ CalculateShmemSize(int *num_semaphores)
 	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
@@ -246,6 +248,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* For global temporary table shared hashtable */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index bd3c7a47fe..2248137477 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -65,6 +65,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -5131,3 +5132,78 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temporary table.
+ */
+int
+list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct		*arrayP = procArray;
+	TransactionId		result = InvalidTransactionId;
+	int			index;
+	uint8			flags = 0;
+	int			i = 0;
+
+	/* return 0 if feature is disabled */
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+		*n = 0;
+	}
+
+	/* Disable in standby node */
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		uint8           statusFlags = ProcGlobal->statusFlags[index];
+
+		if (statusFlags & flags)
+			continue;
+
+		/* Fetch all backend that is belonging to MyDatabaseId */
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->backend_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->backend_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->backend_gtt_frozenxid, result))
+				result = proc->backend_gtt_frozenxid;
+
+			/* save backend pid and backend level oldest relfrozenxid */
+			if (max_size > 0)
+			{
+				pids[i] = proc->pid;
+				xids[i] = proc->backend_gtt_frozenxid;
+				i++;
+			}
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (max_size > 0)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 862097352b..4edd3b31f7 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -176,7 +176,9 @@ static const char *const BuiltinTrancheNames[] = {
 	/* LWTRANCHE_PARALLEL_APPEND: */
 	"ParallelAppend",
 	/* LWTRANCHE_PER_XACT_PREDICATE_LIST: */
-	"PerXactPredicateList"
+	"PerXactPredicateList",
+	/* LWTRANCHE_GTT_CTL */
+	"GlobalTempTableControl"
 };
 
 StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index b7d9da0aa9..8051f2053f 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -393,6 +393,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;	/* init backend level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
@@ -578,6 +579,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;	/* init backend level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index d5a7fb13f3..8225cf6219 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -982,6 +982,13 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/*
+			 * For global temporary table ,each backend has its own storage,
+			 * also only sees its own storage. Use Backendid to identify them.
+			 */
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 0c8c05f6c2..699507a24c 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -108,6 +108,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -5115,12 +5116,26 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								/* For global temporary table, get statistic data from localhash */
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -5368,15 +5383,28 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6800,6 +6828,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;
@@ -6817,6 +6846,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6828,6 +6865,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;
@@ -6843,6 +6882,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7761,6 +7808,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))
@@ -7773,6 +7822,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7785,6 +7843,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.
@@ -7804,6 +7864,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 4ebaa552a2..78c33d2ac8 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -3113,6 +3114,19 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		/* For global temporary table, get statistic data from localhash */
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3..94fdb998aa 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -65,6 +65,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1116,6 +1117,28 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+
+				/* For global temporary table, get relstat data from localhash */
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+
+				/* And put them to local relcache */
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1173,6 +1196,8 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			/* The state of the global temporary table's index may need to be set */
+			gtt_fix_index_backend_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -1300,7 +1325,22 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+			/*
+			 * For global temporary table, get the latest relfilenode
+			 * from localhash and put it in relcache.
+			 */
+			if (OidIsValid(newrelnode) &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2251,6 +2291,9 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		/* The state of the global temporary table's index may need to be set */
+		gtt_fix_index_backend_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3489,6 +3532,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3598,28 +3645,39 @@ void
 RelationSetNewRelfilenode(Relation relation, char persistence)
 {
 	Oid			newrelfilenode;
-	Relation	pg_class;
-	HeapTuple	tuple;
+	Relation	pg_class = NULL;
+	HeapTuple	tuple = NULL;
 	Form_pg_class classform;
 	MultiXactId minmulti = InvalidMultiXactId;
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
 
+	/*
+	 * For global temporary table, storage information for the table is
+	 * maintained locally, not in catalog.
+	 */
+	bool		update_catalog = !RELATION_IS_GLOBAL_TEMP(relation);
+
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
 
-	/*
-	 * Get a writable copy of the pg_class tuple for the given relation.
-	 */
-	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+	memset(&classform, 0, sizeof(classform));
 
-	tuple = SearchSysCacheCopy1(RELOID,
-								ObjectIdGetDatum(RelationGetRelid(relation)));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "could not find tuple for relation %u",
-			 RelationGetRelid(relation));
-	classform = (Form_pg_class) GETSTRUCT(tuple);
+	if (update_catalog)
+	{
+		/*
+		 * Get a writable copy of the pg_class tuple for the given relation.
+		 */
+		pg_class = table_open(RelationRelationId, RowExclusiveLock);
+
+		tuple = SearchSysCacheCopy1(RELOID,
+									ObjectIdGetDatum(RelationGetRelid(relation)));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u",
+				 RelationGetRelid(relation));
+		classform = (Form_pg_class) GETSTRUCT(tuple);
+	}
 
 	/*
 	 * Schedule unlinking of the old storage at transaction commit.
@@ -3645,7 +3703,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;
@@ -3665,6 +3723,18 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 			break;
 	}
 
+	/* For global temporary table */
+	if (!update_catalog)
+	{
+		Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+		Assert(RELATION_IS_GLOBAL_TEMP(relation));
+		Assert(!RelationIsMapped(relation));
+
+		/* Make cache invalid and set new relnode to local cache. */
+		CacheInvalidateRelcache(relation);
+		relation->rd_node.relNode = relnode;
+	}
 	/*
 	 * If we're dealing with a mapped index, pg_class.relfilenode doesn't
 	 * change; instead we have to send the update to the relation mapper.
@@ -3674,7 +3744,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	 * possibly-inaccurate values of relpages etc, but those will be fixed up
 	 * later.
 	 */
-	if (RelationIsMapped(relation))
+	else if (RelationIsMapped(relation))
 	{
 		/* This case is only supported for indexes */
 		Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3720,9 +3790,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 		CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 	}
 
-	heap_freetuple(tuple);
+	if (update_catalog)
+	{
+		heap_freetuple(tuple);
 
-	table_close(pg_class, RowExclusiveLock);
+		table_close(pg_class, RowExclusiveLock);
+	}
 
 	/*
 	 * Make the pg_class row change or relation map change visible.  This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index d2ce4a8450..7717ee2ca1 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -44,6 +44,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "commands/prepare.h"
 #include "commands/trigger.h"
@@ -152,6 +153,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temporary table feature.
+ * table schema are still saved in catalog.
+ *
+ * num > 0 means allows the database to manage multiple active tables at the same time.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2127,6 +2140,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 "
@@ -2697,6 +2719,16 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"vacuum_gtt_defer_check_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("The defer check age of GTT, used to check expired data after vacuum."),
+			NULL
+		},
+		&vacuum_gtt_defer_check_age,
+		10000, 0, 1000000,
+		NULL, NULL, NULL
+	},
+
 	/*
 	 * See also CheckRequiredParameterValues() if this parameter changes
 	 */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a485fb2d07..80658c4f3b 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2503,6 +2503,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
 		dopt->no_unlogged_table_data)
 		return;
 
+	/* Don't dump data in global temporary table/sequence */
+	if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		return;
+
 	/* Check that the data is not explicitly excluded */
 	if (simple_oid_list_member(&tabledata_exclude_oids,
 							   tbinfo->dobj.catId.oid))
@@ -15961,6 +15965,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
 		char	   *foreign = "";
+		char	   *table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -16014,9 +16019,15 @@ dumpTableSchema(Archive *fout, const 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);
 
@@ -16380,6 +16391,15 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 			}
 		}
 
+		/*
+		 * Transaction information for the global temporary table is not stored
+		 * in the pg_class.
+		 */
+		if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			Assert(tbinfo->frozenxid == 0);
+			Assert(tbinfo->minmxid == 0);
+		}
 		/*
 		 * In binary_upgrade mode, arrange to restore the old relfrozenxid and
 		 * relminmxid of all vacuumable relations.  (While vacuum.c processes
@@ -16387,7 +16407,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		 * of other relations; so this "if" lacks RELKIND_TOASTVALUE, and the
 		 * child toast table is handled below.)
 		 */
-		if (dopt->binary_upgrade &&
+		else if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
 			 tbinfo->relkind == RELKIND_MATVIEW))
 		{
@@ -17390,6 +17410,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	PQExpBuffer query = createPQExpBuffer();
 	PQExpBuffer delqry = createPQExpBuffer();
 	char	   *qseqname;
+	bool	   global_temp_seq = false;
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
@@ -17399,9 +17420,12 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
-						  "FROM pg_catalog.pg_sequence "
-						  "WHERE seqrelid = '%u'::oid",
+						  "seqcache, seqcycle, "
+						  "c.relpersistence "
+						  "FROM pg_catalog.pg_sequence s, "
+						  "pg_catalog.pg_class c "
+						  "WHERE seqrelid = '%u'::oid "
+						  "and s.seqrelid = c.oid",
 						  tbinfo->dobj.catId.oid);
 	}
 	else if (fout->remoteVersion >= 80400)
@@ -17446,6 +17470,9 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
 
+	if (fout->remoteVersion >= 140000)
+		global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
 	if (strcmp(seqtype, "smallint") == 0)
@@ -17523,9 +17550,13 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	}
 	else
 	{
-		appendPQExpBuffer(query,
-						  "CREATE SEQUENCE %s\n",
-						  fmtQualifiedDumpable(tbinfo));
+		appendPQExpBuffer(query, "CREATE ");
+
+		if (global_temp_seq)
+			appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+		appendPQExpBuffer(query, "SEQUENCE %s\n",
+								  fmtQualifiedDumpable(tbinfo));
 
 		if (strcmp(seqtype, "bigint") != 0)
 			appendPQExpBuffer(query, "    AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index ad5f391995..f381986009 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -88,7 +88,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -178,7 +178,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 5d9a26cf82..2de11d5d70 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -447,8 +449,17 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
 			 "         ON c.relnamespace = n.oid "
 			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
+			 CppAsString2(RELKIND_MATVIEW) ") AND ");
+
+	if (skip_gtt)
+	{
+		/* exclude global temp tables */
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+			"    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND ");
+	}
+
 	/* exclude possible orphaned temp tables */
+	snprintf(query + strlen(query), sizeof(query) - strlen(query),
 			 "    ((n.nspname !~ '^pg_temp_' AND "
 			 "      n.nspname !~ '^pg_toast_temp_' AND "
 			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 3628bd74a7..bbb9b5ea13 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -407,7 +407,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -645,7 +645,10 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+			/* exclude global temp tables */
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -656,7 +659,10 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+		/* exclude global temp tables */
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index ca0795f68f..018a2effd4 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -389,7 +389,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 90ff649be7..7a50197875 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -4067,7 +4067,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 5cd5838668..e528739a0c 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1059,6 +1059,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2542,6 +2544,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2755,6 +2760,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE", "SEQUENCE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 6ce480b49c..415ec38a80 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -59,7 +59,8 @@ extern Relation heap_create(const char *relname,
 							bool mapped_relation,
 							bool allow_system_table_mods,
 							TransactionId *relfrozenxid,
-							MultiXactId *relminmxid);
+							MultiXactId *relminmxid,
+							bool skip_create_storage);
 
 extern Oid	heap_create_with_catalog(const char *relname,
 									 Oid relnamespace,
@@ -85,7 +86,7 @@ extern Oid	heap_create_with_catalog(const char *relname,
 
 extern void heap_drop_with_catalog(Oid relid);
 
-extern void heap_truncate(List *relids);
+extern void heap_truncate(List *relids, bool is_global_temp);
 
 extern void heap_truncate_one_rel(Relation rel);
 
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 008f723e10..875b100389 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -157,6 +157,7 @@ extern void reindex_index(Oid indexId, bool skip_constraint_checks,
 #define REINDEX_REL_CHECK_CONSTRAINTS		0x04
 #define REINDEX_REL_FORCE_INDEXES_UNLOGGED	0x08
 #define REINDEX_REL_FORCE_INDEXES_PERMANENT 0x10
+#define REINDEX_REL_PROCESS_GLOBAL_TEMP		0x20
 
 extern bool reindex_relation(Oid relid, int flags, ReindexParams *params);
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index fef9945ed8..9176b7dcc0 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -172,6 +172,7 @@ DECLARE_INDEX(pg_class_tblspc_relfilenode_index, 3455, ClassTblspcRelfilenodeInd
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index d068d6532e..fd5089388f 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5735,6 +5735,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '9874',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '9875',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '9876',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '9877',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 0ab32b44e9..92e9f8ba48 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,7 +22,7 @@
 /* GUC variables */
 extern int	wal_skip_threshold;
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationPreTruncate(Relation rel);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000000..8a3d955871
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,45 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern int		vacuum_gtt_defer_check_age;
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Oid relid,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void force_enable_gtt_index(Relation index);
+extern void gtt_fix_index_backend_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index 40544dd4c7..7b66d808fc 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 336549cc5f..3e8167134b 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -86,7 +86,7 @@ extern void find_composite_type_dependencies(Oid typeOid,
 
 extern void check_of_type(HeapTuple typetuple);
 
-extern void register_on_commit_action(Oid relid, OnCommitAction action);
+extern void register_on_commit_action(Oid relid, OnCommitAction action, bool is_gloal_temp);
 extern void remove_on_commit_action(Oid relid);
 
 extern void PreCommit_on_commit_actions(void);
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 8336c2c5a2..bddcfe7256 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index c86ccdaf60..6b395551c1 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -399,6 +399,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index a8f052e484..4b4ed1a13a 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -189,6 +189,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_SHARED_TIDBITMAP,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_PER_XACT_PREDICATE_LIST,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index be67d8a861..e2f8bb5162 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -157,6 +157,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId backend_gtt_frozenxid;	/* backend level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index b01fa52139..8efffa55ac 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -94,4 +94,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index aa18d304ac..524c9d7de3 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -288,6 +288,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 b4faa1c123..a74558a838 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	SMgrRelation rd_smgr;		/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -326,6 +326,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -608,11 +609,13 @@ RelationGetSmgr(Relation rel)
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -620,6 +623,7 @@ RelationGetSmgr(Relation rel)
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -632,6 +636,30 @@ RelationGetSmgr(Relation rel)
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP_ON_CURRENT_SESSION
+ *		Test a rel is either local temp relation of this session
+ *		or global temp relation.
+ */
+#define RELATION_IS_TEMP_ON_CURRENT_SESSION(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is local temp relation or global temporary relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RelpersistenceTsTemp
+ *		Test a relpersistence is local temp relation or global temporary relation.
+ */
+#define RelpersistenceTsTemp(relpersistence) \
+	(relpersistence == RELPERSISTENCE_TEMP || \
+	 relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -677,6 +705,19 @@ RelationGetSmgr(Relation rel)
 	 (relation)->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&	\
 	 !IsCatalogRelation(relation))
 
+/* For global temporary table */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/* Get on commit clause value only for global temporary table */
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+/* Get relpersistence for relation */
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
-- 
2.25.1

0002-gtt-v52-regress.patchapplication/octet-stream; name=0002-gtt-v52-regress.patchDownload
diff --git a/src/test/isolation/expected/gtt-sequence.out b/src/test/isolation/expected/gtt-sequence.out
new file mode 100644
index 0000000000..31db2ebd42
--- /dev/null
+++ b/src/test/isolation/expected/gtt-sequence.out
@@ -0,0 +1,48 @@
+unused step name: s1_seq_restart
+Parsed test spec with 2 sessions
+
+starting permutation: s1_seq_next s2_seq_next s1_seq_next
+step s1_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      1
+(1 row)
+
+step s2_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      1
+(1 row)
+
+step s1_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      2
+(1 row)
+
+
+starting permutation: s1_select s2_select s1_insert s2_insert s1_select s2_select
+step s1_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+(0 rows)
+
+step s2_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+(0 rows)
+
+step s1_insert: insert into gtt_with_seq values(1);
+step s2_insert: insert into gtt_with_seq values(10);
+step s1_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+ 1| 3
+(1 row)
+
+step s2_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+10| 1
+(1 row)
+
diff --git a/src/test/isolation/expected/gtt-table.out b/src/test/isolation/expected/gtt-table.out
new file mode 100644
index 0000000000..67cf73e8e6
--- /dev/null
+++ b/src/test/isolation/expected/gtt-table.out
@@ -0,0 +1,633 @@
+unused step name: s1_analyze_c
+unused step name: s1_insert_c
+unused step name: s1_select_c
+unused step name: s2_analyze_c
+unused step name: s2_insert_c
+unused step name: s2_select_c
+unused step name: s2_select_d
+unused step name: s2_truncate_d
+unused step name: s3_analyze_c
+unused step name: s3_create_c
+unused step name: s3_insert_c
+unused step name: s3_select_c
+Parsed test spec with 3 sessions
+
+starting permutation: s1_update_d
+step s1_update_d: update gtt_on_commit_delete_row set b = 'update'
+
+starting permutation: s1_select_d s1_insert_d s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_begin s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+1|test1
+(1 row)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_begin s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+1|test1
+(1 row)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_select_d s1_truncate_d s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_begin s1_insert_d s1_select_d s1_truncate_d s1_select_d s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_begin s1_insert_d s1_select_d s1_truncate_d s1_select_d s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_insert_d s1_select_d s1_begin s1_insert_d s1_select_d s1_save_1 s1_truncate_d s1_insert_d s1_select_d s1_save_2 s1_truncate_d s1_insert_d s1_select_d s1_save_3 s1_rollback_to_save_2 s1_select_d s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_save_1: SAVEPOINT save1
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_save_2: SAVEPOINT save2
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+4|test1
+(1 row)
+
+step s1_save_3: SAVEPOINT save3
+step s1_rollback_to_save_2: rollback to savepoint save2
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+5|test1
+(2 rows)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_insert_d s1_select_d s1_begin s1_insert_d s1_select_d s1_save_1 s1_truncate_d s1_insert_d s1_select_d s1_save_2 s1_truncate_d s1_insert_d s1_select_d s1_save_3 s1_rollback_to_save_2 s1_select_d s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_save_1: SAVEPOINT save1
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_save_2: SAVEPOINT save2
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+4|test1
+(1 row)
+
+step s1_save_3: SAVEPOINT save3
+step s1_rollback_to_save_2: rollback to savepoint save2
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+5|test1
+(2 rows)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_update_p
+step s2_update_p: update gtt_on_commit_preserve_row set b = 'update'
+
+starting permutation: s2_select_p s2_insert_p s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_begin s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_begin s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_select_p s2_insert_p s2_select_p s2_truncate_p s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_select_p s2_insert_p s2_begin s2_insert_p s2_select_p s2_truncate_p s2_select_p s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_insert_p s2_begin s2_insert_p s2_select_p s2_truncate_p s2_select_p s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_insert_p s2_select_p s2_begin s2_insert_p s2_select_p s2_save_1 s2_truncate_p s2_insert_p s2_select_p s2_save_2 s2_truncate_p s2_insert_p s2_select_p s2_save_3 s2_rollback_to_save_2 s2_select_p s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_save_1: SAVEPOINT save1
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_save_2: SAVEPOINT save2
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+4|test10
+(1 row)
+
+step s2_save_3: SAVEPOINT save3
+step s2_rollback_to_save_2: rollback to savepoint save2
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+
+starting permutation: s2_insert_p s2_select_p s2_begin s2_insert_p s2_select_p s2_save_1 s2_truncate_p s2_insert_p s2_select_p s2_save_2 s2_truncate_p s2_insert_p s2_select_p s2_save_3 s2_rollback_to_save_2 s2_select_p s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_save_1: SAVEPOINT save1
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_save_2: SAVEPOINT save2
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+4|test10
+(1 row)
+
+step s2_save_3: SAVEPOINT save3
+step s2_rollback_to_save_2: rollback to savepoint save2
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_insert_p s2_insert_p s1_select_p s2_select_p
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_truncate_p s2_truncate_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_begin s1_insert_d s2_insert_d s1_truncate_d s2_insert_d s1_commit
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s2_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s2_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_commit: commit
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_reindex_p s2_reindex_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_reindex_p: reindex table gtt_on_commit_preserve_row
+step s2_reindex_p: reindex table gtt_on_commit_preserve_row
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_reindex_i_p s2_reindex_i_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_reindex_i_p: reindex index gtt_on_commit_preserve_row_pkey
+step s2_reindex_i_p: reindex index gtt_on_commit_preserve_row_pkey
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index f4c01006fc..746a17f824 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -96,3 +96,5 @@ test: plpgsql-toast
 test: truncate-conflict
 test: serializable-parallel
 test: serializable-parallel-2
+test: gtt-sequence
+test: gtt-table
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 88594a3cb5..ec643aadb5 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -80,9 +80,30 @@ disconnect_atexit(void)
 {
 	int			i;
 
-	for (i = 0; i < nconns; i++)
+	for (i = 1; i < nconns; i++)
 		if (conns[i].conn)
 			PQfinish(conns[i].conn);
+
+	if (parseresult.destroy)
+	{
+		PGresult   *res;
+
+		res = PQexec(conns[0].conn, parseresult.destroy);
+		if (PQresultStatus(res) == PGRES_TUPLES_OK)
+		{
+			printResultSet(res);
+		}
+		else if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		{
+			fprintf(stderr, "destroy failed: %s",
+					PQerrorMessage(conns[0].conn));
+			/* don't exit on teardown failure */
+		}
+		PQclear(res);
+	}
+
+	if (conns[0].conn)
+		PQfinish(conns[0].conn);
 }
 
 int
@@ -214,6 +235,24 @@ main(int argc, char **argv)
 	PQclear(res);
 	termPQExpBuffer(&wait_query);
 
+	if (parseresult.initialize)
+	{
+		PGresult   *res;
+
+		res = PQexec(conns[0].conn, parseresult.initialize);
+		if (PQresultStatus(res) == PGRES_TUPLES_OK)
+		{
+			printResultSet(res);
+		}
+		else if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		{
+			fprintf(stderr, "initialize failed: %s",
+					PQerrorMessage(conns[0].conn));
+			/* don't exit on teardown failure */
+		}
+		PQclear(res);
+	}
+
 	/*
 	 * Run the permutations specified in the spec, or all if none were
 	 * explicitly specified.
diff --git a/src/test/isolation/isolationtester.h b/src/test/isolation/isolationtester.h
index 5f300219c2..b5a29893da 100644
--- a/src/test/isolation/isolationtester.h
+++ b/src/test/isolation/isolationtester.h
@@ -81,6 +81,8 @@ typedef struct
 	int			nsessions;
 	Permutation **permutations;
 	int			npermutations;
+	char	   *initialize;
+	char	   *destroy;
 } TestSpec;
 
 extern TestSpec parseresult;
diff --git a/src/test/isolation/specparse.y b/src/test/isolation/specparse.y
index c25aa1a73f..2784f758ed 100644
--- a/src/test/isolation/specparse.y
+++ b/src/test/isolation/specparse.y
@@ -39,7 +39,7 @@ TestSpec		parseresult;			/* result of parsing is left here */
 }
 
 %type <ptr_list> setup_list
-%type <str>  opt_setup opt_teardown
+%type <str>  opt_setup opt_teardown opt_initialize opt_destroy
 %type <str> setup
 %type <ptr_list> step_list session_list permutation_list opt_permutation_list
 %type <ptr_list> permutation_step_list blocker_list
@@ -51,23 +51,27 @@ TestSpec		parseresult;			/* result of parsing is left here */
 
 %token <str> sqlblock identifier
 %token <integer> INTEGER
-%token NOTICES PERMUTATION SESSION SETUP STEP TEARDOWN TEST
+%token NOTICES PERMUTATION SESSION SETUP STEP TEARDOWN TEST INITIALIZE DESTROY
 
 %%
 
 TestSpec:
+			opt_initialize
 			setup_list
 			opt_teardown
+			opt_destroy
 			session_list
 			opt_permutation_list
 			{
-				parseresult.setupsqls = (char **) $1.elements;
-				parseresult.nsetupsqls = $1.nelements;
-				parseresult.teardownsql = $2;
-				parseresult.sessions = (Session **) $3.elements;
-				parseresult.nsessions = $3.nelements;
-				parseresult.permutations = (Permutation **) $4.elements;
-				parseresult.npermutations = $4.nelements;
+				parseresult.setupsqls = (char **) $2.elements;
+				parseresult.nsetupsqls = $2.nelements;
+				parseresult.teardownsql = $3;
+				parseresult.sessions = (Session **) $5.elements;
+				parseresult.nsessions = $5.nelements;
+				parseresult.permutations = (Permutation **) $6.elements;
+				parseresult.npermutations = $6.nelements;
+				parseresult.initialize = $1;
+				parseresult.destroy = $4;
 			}
 		;
 
@@ -100,6 +104,16 @@ opt_teardown:
 			| TEARDOWN sqlblock	{ $$ = $2; }
 		;
 
+opt_initialize:
+			/* EMPTY */			{ $$ = NULL; }
+			| INITIALIZE sqlblock	{ $$ = $2; }
+		;
+
+opt_destroy:
+			/* EMPTY */			{ $$ = NULL; }
+			| DESTROY sqlblock	{ $$ = $2; }
+		;
+
 session_list:
 			session_list session
 			{
diff --git a/src/test/isolation/specs/gtt-sequence.spec b/src/test/isolation/specs/gtt-sequence.spec
new file mode 100644
index 0000000000..88eece45e2
--- /dev/null
+++ b/src/test/isolation/specs/gtt-sequence.spec
@@ -0,0 +1,39 @@
+# Tests for global temporary relations
+
+initialize
+{
+  CREATE GLOBAL TEMPORARY TABLE if not exists gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+}
+
+destroy
+{
+  /* wait other backend exit */
+  select pg_sleep(1);
+
+  DROP TABLE gtt_with_seq;
+}
+
+# Session 1
+session "s1"
+step "s1_seq_next" { select nextval('gtt_with_seq_c2_seq'); }
+step "s1_seq_restart" { alter sequence gtt_with_seq_c2_seq RESTART; }
+step "s1_insert" { insert into gtt_with_seq values(1); }
+step "s1_select" { select * from gtt_with_seq order by c1,c2; }
+teardown
+{
+  TRUNCATE gtt_with_seq;
+}
+
+# Session 2
+session "s2"
+step "s2_seq_next" { select nextval('gtt_with_seq_c2_seq'); }
+step "s2_insert" { insert into gtt_with_seq values(10); }
+step "s2_select" { select * from gtt_with_seq order by c1,c2; }
+teardown
+{
+  TRUNCATE gtt_with_seq RESTART IDENTITY;
+}
+
+permutation "s1_seq_next" "s2_seq_next" "s1_seq_next"
+permutation "s1_select" "s2_select" "s1_insert" "s2_insert" "s1_select" "s2_select"
+
diff --git a/src/test/isolation/specs/gtt-table.spec b/src/test/isolation/specs/gtt-table.spec
new file mode 100644
index 0000000000..4c57b0a287
--- /dev/null
+++ b/src/test/isolation/specs/gtt-table.spec
@@ -0,0 +1,130 @@
+# Tests for global temporary relations
+
+initialize
+{
+  create global temp table gtt_on_commit_delete_row(a bigserial primary key, b text) on commit delete rows;
+  create global temp table gtt_on_commit_preserve_row(a bigserial primary key, b text) on commit preserve rows;
+  create global temp table gtt_test_createindex(a int, b char(1000)) on commit preserve rows;
+}
+
+destroy
+{
+  /* wait other backend exit */
+  select pg_sleep(1);
+
+  DROP TABLE gtt_on_commit_delete_row;
+  DROP TABLE gtt_on_commit_preserve_row;
+  DROP TABLE gtt_test_createindex;
+}
+
+# Session 1
+session "s1"
+step "s1_begin" {begin}
+step "s1_commit" {commit}
+step "s1_rollback" {rollback}
+step "s1_insert_d" {insert into gtt_on_commit_delete_row (b) values('test1')}
+step "s1_select_d" {select a,b from gtt_on_commit_delete_row order by a,b}
+step "s1_insert_p" {insert into gtt_on_commit_preserve_row (b) values('test20')}
+step "s1_select_p" {select a,b from gtt_on_commit_preserve_row order by a,b}
+step "s1_truncate_d" {truncate gtt_on_commit_delete_row}
+step "s1_truncate_p" {truncate gtt_on_commit_preserve_row}
+step "s1_update_d" {update gtt_on_commit_delete_row set b = 'update'}
+step "s1_save_1" {SAVEPOINT save1}
+step "s1_save_2" {SAVEPOINT save2}
+step "s1_save_3" {SAVEPOINT save3}
+step "s1_rollback_to_save_2" {rollback to savepoint save2}
+step "s1_reindex_p" {reindex table gtt_on_commit_preserve_row}
+step "s1_reindex_i_p" {reindex index gtt_on_commit_preserve_row_pkey}
+step "s1_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s1_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s1_analyze_c" {analyze gtt_test_createindex}
+teardown
+{
+  TRUNCATE gtt_on_commit_delete_row RESTART IDENTITY;
+  TRUNCATE gtt_on_commit_preserve_row RESTART IDENTITY;
+}
+
+# Session 2
+session "s2"
+step "s2_begin" {begin}
+step "s2_commit" {commit}
+step "s2_rollback" {rollback}
+step "s2_insert_p" {insert into gtt_on_commit_preserve_row (b) values('test10')}
+step "s2_select_p" {select a,b from gtt_on_commit_preserve_row order by a,b}
+step "s2_insert_d" {insert into gtt_on_commit_delete_row (b) values('test1')}
+step "s2_select_d" {select a,b from gtt_on_commit_delete_row order by a,b}
+step "s2_truncate_d" {truncate gtt_on_commit_delete_row}
+step "s2_truncate_p" {truncate gtt_on_commit_preserve_row}
+step "s2_update_p" {update gtt_on_commit_preserve_row set b = 'update'}
+step "s2_save_1" {SAVEPOINT save1}
+step "s2_save_2" {SAVEPOINT save2}
+step "s2_save_3" {SAVEPOINT save3}
+step "s2_rollback_to_save_2" {rollback to savepoint save2}
+step "s2_reindex_p" {reindex table gtt_on_commit_preserve_row}
+step "s2_reindex_i_p" {reindex index gtt_on_commit_preserve_row_pkey}
+step "s2_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s2_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s2_analyze_c" {analyze gtt_test_createindex}
+teardown
+{
+  TRUNCATE gtt_on_commit_delete_row RESTART IDENTITY;
+  TRUNCATE gtt_on_commit_preserve_row RESTART IDENTITY;
+}
+
+session "s3"
+step "s3_create_c" {create unique index idx_temp_table_a on gtt_test_createindex(a)}
+step "s3_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s3_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s3_analyze_c" {analyze gtt_test_createindex}
+
+
+#
+# test on commit delete temp table
+#
+
+# test update empty temp table
+permutation "s1_update_d"
+# test insert into temp table
+permutation "s1_select_d" "s1_insert_d" "s1_select_d"
+# test temp table in transaction(commit)
+permutation "s1_select_d" "s1_begin"    "s1_insert_d" "s1_select_d"   "s1_commit"   "s1_select_d" 
+# test temp table in transaction(rollback)
+permutation "s1_select_d" "s1_begin"    "s1_insert_d" "s1_select_d"   "s1_rollback" "s1_select_d" 
+# test truncate
+permutation "s1_select_d" "s1_insert_d" "s1_select_d" "s1_truncate_d" "s1_select_d"
+# test truncate in transaction block
+permutation "s1_select_d" "s1_insert_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_truncate_d" "s1_select_d"   "s1_insert_d" "s1_select_d" "s1_commit"   "s1_select_d" 
+permutation "s1_select_d" "s1_insert_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_truncate_d" "s1_select_d"   "s1_insert_d" "s1_select_d" "s1_rollback" "s1_select_d"
+# test temp table with subtransaction or savepoint
+permutation "s1_insert_d" "s1_select_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_save_1"     "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_2"   "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_3" "s1_rollback_to_save_2" "s1_select_d" "s1_insert_d" "s1_select_d" "s1_commit" "s1_select_d"
+permutation "s1_insert_d" "s1_select_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_save_1"     "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_2"   "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_3" "s1_rollback_to_save_2" "s1_select_d" "s1_insert_d" "s1_select_d" "s1_rollback" "s1_select_d"
+
+#
+# test on commit preserve table
+#
+
+# same as test on commit delete temp table
+permutation "s2_update_p"
+permutation "s2_select_p" "s2_insert_p" "s2_select_p"
+permutation "s2_select_p" "s2_begin"    "s2_insert_p" "s2_select_p"   "s2_commit"   "s2_select_p"
+permutation "s2_select_p" "s2_begin"    "s2_insert_p" "s2_select_p"   "s2_rollback" "s2_select_p"
+permutation "s2_select_p" "s2_insert_p" "s2_select_p" "s2_truncate_p" "s2_select_p"
+permutation "s2_select_p" "s2_insert_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_truncate_p" "s2_select_p"   "s2_insert_p" "s2_select_p" "s2_commit"   "s2_select_p" 
+permutation "s2_select_p" "s2_insert_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_truncate_p" "s2_select_p"   "s2_insert_p" "s2_select_p" "s2_rollback" "s2_select_p" 
+permutation "s2_insert_p" "s2_select_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_save_1"     "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_2"   "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_3" "s2_rollback_to_save_2" "s2_select_p" "s2_insert_p" "s2_select_p" "s2_commit" "s2_select_p"
+permutation "s2_insert_p" "s2_select_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_save_1"     "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_2"   "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_3" "s2_rollback_to_save_2" "s2_select_p" "s2_insert_p" "s2_select_p" "s2_rollback" "s2_select_p"
+
+#
+# test concurrent operation on temp table
+#
+
+#  test concurrent read
+permutation "s1_insert_p" "s2_insert_p" "s1_select_p" "s2_select_p" 
+#  test concurrent truncate
+permutation "s1_begin" "s2_begin"    "s1_insert_p" "s2_insert_p"   "s1_truncate_p" "s2_truncate_p"  "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+permutation "s1_begin" "s1_insert_d" "s2_insert_d" "s1_truncate_d" "s2_insert_d"   "s1_commit" 
+#  test concurrent reindex table
+permutation "s1_begin" "s2_begin" "s1_insert_p" "s2_insert_p" "s1_reindex_p"   "s2_reindex_p"   "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+#  test concurrent reindex index
+permutation "s1_begin" "s2_begin" "s1_insert_p" "s2_insert_p" "s1_reindex_i_p" "s2_reindex_i_p" "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+
diff --git a/src/test/isolation/specscanner.l b/src/test/isolation/specscanner.l
index d9fa6a5b54..697db97547 100644
--- a/src/test/isolation/specscanner.l
+++ b/src/test/isolation/specscanner.l
@@ -67,6 +67,8 @@ session			{ return SESSION; }
 setup			{ return SETUP; }
 step			{ return STEP; }
 teardown		{ return TEARDOWN; }
+initialize		 { return INITIALIZE; }
+destroy			 { return DESTROY; }
 
  /* Whitespace and comments */
 [\n]			{ yyline++; }
diff --git a/src/test/regress/expected/global_temporary_table.out b/src/test/regress/expected/global_temporary_table.out
new file mode 100644
index 0000000000..def015dcbd
--- /dev/null
+++ b/src/test/regress/expected/global_temporary_table.out
@@ -0,0 +1,506 @@
+--
+-- GLobal emparary table test case 
+--
+CREATE SCHEMA IF NOT EXISTS global_temporary_table;
+set search_path=global_temporary_table,sys;
+--
+--  test create global temp table basic syntax
+--
+create global temp table gtt_on_commit_default(a int primary key, b text);
+create global temp table gtt_on_commit_delete(a int primary key, b text) on commit delete rows;
+create global temp table gtt_on_commit_delete2(n int) with (on_commit_delete_rows='true');
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit PRESERVE rows;
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt_test_createindex(c0 tsvector,c1 varchar(100), c2 int);
+--
+-- test DML of temp table
+--
+-- update empty temp table
+update gtt_on_commit_delete set b ='test';
+begin;
+insert into gtt_on_commit_delete values (1);
+update gtt_on_commit_delete set b ='test';
+-- should 1 row
+select * from gtt_on_commit_delete;
+ a |  b   
+---+------
+ 1 | test
+(1 row)
+
+commit;
+-- data delete after transaction commit
+-- should 0 row
+select * from gtt_on_commit_delete;
+ a | b 
+---+---
+(0 rows)
+
+-- update empty temp table
+update gtt_on_commit_preserve set b ='test';
+insert into gtt_on_commit_preserve values (2);
+begin;
+insert into gtt_on_commit_preserve values (3);
+update gtt_on_commit_preserve set b ='test';
+-- should 2 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 2 | test
+ 3 | test
+(2 rows)
+
+delete from gtt_on_commit_preserve where a=2;
+commit;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 3 | test
+(1 row)
+
+begin;
+insert into gtt_on_commit_preserve values (4);
+-- temp table support truncate;
+truncate gtt_on_commit_preserve;
+select * from gtt_on_commit_preserve order by a;
+ a | b 
+---+---
+(0 rows)
+
+rollback;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 3 | test
+(1 row)
+
+--
+-- test unsupported global temp partition table
+--
+-- should fail
+CREATE global temp TABLE global_temp_partition_01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+on commit delete rows;
+ERROR:  Only support global temporary regular table.
+CREATE TABLE regular_partition_01_2019 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+);
+CREATE TABLE regular_partition01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time);
+-- should fail
+CREATE global temp TABLE temp_partition01_2018
+PARTITION OF regular_partition01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+ERROR:  Not support global temporary partition table or inherit table.
+CREATE global temp TABLE global_temp_partition_01_2021 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+)on commit delete rows;
+-- should fail
+ALTER TABLE regular_partition01 ATTACH PARTITION global_temp_partition_01_2021 FOR VALUES FROM ('2021-01-01 00:00:00') TO ('2022-01-01 00:00:00');
+ERROR:  cannot attach a global temporary relation as partition of permanent relation "regular_partition01"
+--
+-- test unsupported inherit table
+--
+create table 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 inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent);
+ERROR:  Not support global temporary partition table or inherit table.
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent_global_temp) on commit delete rows;
+ERROR:  Not support global temporary partition table or inherit table.
+--
+-- test DDL on global temp table
+--
+create index gtt_idx_1 on gtt_test_createindex using gin (c0);
+create index gtt_idx_2 on gtt_test_createindex using gist (c0);
+create index gtt_idx_3 on gtt_test_createindex using hash (c2);
+alter table gtt_test_rename rename to gtt_test_new;
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+create index CONCURRENTLY idx_b on gtt_on_commit_default (b);
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+insert into gtt_on_commit_default values(1,'test');
+create global temp table gtt_test_alter1 (a int primary key,b text);
+alter table gtt_test_alter1 alter a type varchar;
+-- should fail
+alter table gtt_on_commit_default alter a type varchar;
+ERROR:  Only support alter global temporary table in an empty context.
+HINT:  Please create a new connection and execute ALTER TABLE on the new connection.
+-- should fail
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+ERROR:  not support cluster global temporary table yet
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- should fail
+alter table gtt_on_commit_default SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temporary table
+-- should fail
+alter table gtt_on_commit_default set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table
+-- should fail
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- should fail
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- should fail
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit drop;
+ERROR:  global temporary table not support on commit drop clause
+-- should fail
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  could not create global temporary table with on commit and with clause at same time
+-- should fail
+CREATE MATERIALIZED VIEW mv_gtt_on_commit_default as select * from gtt_on_commit_default;
+ERROR:  materialized views must not use global temporary tables or views
+--
+-- test foreign key dependencies for global temp table
+--
+CREATE global temp TABLE temp_products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+CREATE global temp TABLE temp_orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+)on commit delete rows;
+-- should fail
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+CREATE global temp TABLE temp_orders_2 (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products(product_no),
+    quantity integer
+);
+--should fail
+insert into temp_orders values(1,1,1);
+ERROR:  insert or update on table "temp_orders" violates foreign key constraint "temp_orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "temp_products".
+insert into temp_products values(1,'test',1.0);
+begin;
+insert into temp_orders values(1,1,1);
+commit;
+-- should 1 row
+select count(*) from temp_products;
+ count 
+-------
+     1
+(1 row)
+
+-- should 0 row
+select count(*) from temp_orders;
+ count 
+-------
+     0
+(1 row)
+
+--
+-- test sequence on global temp table
+--
+create global temp table global_temp_with_serial (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+-- support insert data to temp table in read only transaction
+insert into global_temp_with_serial (b) values(1);
+select * from global_temp_with_serial;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_delete(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_preserve(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table temp_table_with_sequence_oncommit_delete add c2 int default nextval('seq_1');
+alter table temp_table_with_sequence_oncommit_preserve add c2 int default nextval('seq_1');
+begin;
+insert into temp_table_with_sequence_oncommit_delete (c1)values(1);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(2);
+insert into temp_table_with_sequence_oncommit_delete (c1)values(3);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(4);
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  3 |  3
+(2 rows)
+
+commit;
+-- should 0 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_preserve order by c1;
+ c1 | c2 
+----+----
+  2 |  2
+  4 |  4
+(2 rows)
+
+--
+-- test statistics on temp table
+--
+create global temp table temp_table_test_statistics(a int);
+insert into temp_table_test_statistics values(generate_series(1,100000));
+create index idx_test_1 on temp_table_test_statistics (a);
+create index idx_test_2 on temp_table_test_statistics((a*10));
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+                   QUERY PLAN                   
+------------------------------------------------
+ Bitmap Heap Scan on temp_table_test_statistics
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_test_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+                   QUERY PLAN                   
+------------------------------------------------
+ Bitmap Heap Scan on temp_table_test_statistics
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_test_2
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze temp_table_test_statistics;
+-- indexscan by idx_test_1
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Index Only Scan using idx_test_1 on temp_table_test_statistics
+   Index Cond: (a = 200000)
+(2 rows)
+
+-- indexscan by idx_test_2
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+                        QUERY PLAN                         
+-----------------------------------------------------------
+ Index Scan using idx_test_2 on temp_table_test_statistics
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+--
+-- test temp table with toast table
+--
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |                0 |        483328 |           98304 |                 581632
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            32768 |                  |         32768 |               0 |                  32768
+(3 rows)
+
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |             8192 |        499712 |          106496 |                 606208
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            40960 |                  |         40960 |               0 |                  40960
+(3 rows)
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,10),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',10);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |             8192 |                0 |         16384 |           32768 |                  49152
+ idx_gtt_t_kenyon_1 |            16384 |                  |         16384 |               0 |                  16384
+ idx_gtt_t_kenyon_2 |            16384 |                  |         16384 |               0 |                  16384
+(3 rows)
+
+rollback;
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |             8192 |        499712 |          106496 |                 606208
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            40960 |                  |         40960 |               0 |                  40960
+(3 rows)
+
+--
+-- test global temp table system view
+--
+create global temp table temp_table_test_systemview(a int primary key, b text) on commit PRESERVE rows;
+-- should empty, storage not initialized
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+ tablename 
+-----------
+(0 rows)
+
+-- should empty, storage not initialized
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into temp_table_test_systemview values(generate_series(1,10000),'test');
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+         tablename          
+----------------------------
+ temp_table_test_systemview
+(1 row)
+
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+       schemaname       |            tablename            | relpages | reltuples | relallvisible 
+------------------------+---------------------------------+----------+-----------+---------------
+ global_temporary_table | temp_table_test_systemview      |        0 |         0 |             0
+ global_temporary_table | temp_table_test_systemview_pkey |        1 |         0 |             0
+(2 rows)
+
+-- should empty
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+analyze temp_table_test_systemview;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+       schemaname       |            tablename            | relpages | reltuples | relallvisible 
+------------------------+---------------------------------+----------+-----------+---------------
+ global_temporary_table | temp_table_test_systemview      |       55 |     10000 |             0
+ global_temporary_table | temp_table_test_systemview_pkey |       30 |     10000 |             0
+(2 rows)
+
+-- get data after analyze;
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+       schemaname       |         tablename          | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------------------+----------------------------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ global_temporary_table | temp_table_test_systemview | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ global_temporary_table | temp_table_test_systemview | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+-- get all object info in current schema
+select relname ,relkind, relpersistence, reloptions from pg_class c, pg_namespace n where c.relnamespace = n.oid and n.nspname = 'global_temporary_table' order by relkind,relpersistence,relname;
+                     relname                     | relkind | relpersistence |          reloptions           
+-------------------------------------------------+---------+----------------+-------------------------------
+ global_temp_partition_01_2021_id_seq            | S       | g              | {on_commit_delete_rows=false}
+ global_temp_with_serial_a_seq                   | S       | g              | {on_commit_delete_rows=false}
+ regular_partition01_id_seq                      | S       | p              | 
+ regular_partition_01_2019_id_seq                | S       | p              | 
+ seq_1                                           | S       | p              | 
+ gtt_idx_1                                       | i       | g              | 
+ gtt_idx_2                                       | i       | g              | 
+ gtt_idx_3                                       | i       | g              | 
+ gtt_on_commit_default_pkey                      | i       | g              | 
+ gtt_on_commit_delete_pkey                       | i       | g              | 
+ gtt_on_commit_preserve_pkey                     | i       | g              | 
+ gtt_test_alter1_pkey                            | i       | g              | 
+ gtt_test_rename_pkey                            | i       | g              | 
+ idx_b                                           | i       | g              | 
+ idx_gtt_t_kenyon_1                              | i       | g              | 
+ idx_gtt_t_kenyon_2                              | i       | g              | 
+ idx_test_1                                      | i       | g              | 
+ idx_test_2                                      | i       | g              | 
+ products_pkey                                   | i       | g              | 
+ temp_orders_2_pkey                              | i       | g              | 
+ temp_orders_pkey                                | i       | g              | 
+ temp_products_pkey                              | i       | g              | 
+ temp_table_test_systemview_pkey                 | i       | g              | 
+ temp_table_with_sequence_oncommit_delete_pkey   | i       | g              | 
+ temp_table_with_sequence_oncommit_preserve_pkey | i       | g              | 
+ regular_partition01                             | p       | p              | 
+ global_temp_partition_01_2021                   | r       | g              | {on_commit_delete_rows=true}
+ global_temp_with_serial                         | r       | g              | {on_commit_delete_rows=false}
+ gtt_on_commit_default                           | r       | g              | {on_commit_delete_rows=false}
+ gtt_on_commit_delete                            | r       | g              | {on_commit_delete_rows=true}
+ gtt_on_commit_delete2                           | r       | g              | {on_commit_delete_rows=true}
+ gtt_on_commit_preserve                          | r       | g              | {on_commit_delete_rows=false}
+ gtt_t_kenyon                                    | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_alter1                                 | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_createindex                            | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_new                                    | r       | g              | {on_commit_delete_rows=false}
+ inherits_parent_global_temp                     | r       | g              | {on_commit_delete_rows=true}
+ products                                        | r       | g              | {on_commit_delete_rows=false}
+ temp_orders                                     | r       | g              | {on_commit_delete_rows=true}
+ temp_orders_2                                   | r       | g              | {on_commit_delete_rows=false}
+ temp_products                                   | r       | g              | {on_commit_delete_rows=false}
+ temp_table_test_statistics                      | r       | g              | {on_commit_delete_rows=false}
+ temp_table_test_systemview                      | r       | g              | {on_commit_delete_rows=false}
+ temp_table_with_sequence_oncommit_delete        | r       | g              | {on_commit_delete_rows=true}
+ temp_table_with_sequence_oncommit_preserve      | r       | g              | {on_commit_delete_rows=false}
+ foo                                             | r       | p              | 
+ inherits_parent                                 | r       | p              | 
+ regular_partition_01_2019                       | r       | p              | 
+(48 rows)
+
+reset search_path;
+drop schema global_temporary_table cascade;
+NOTICE:  drop cascades to 24 other objects
+DETAIL:  drop cascades to table global_temporary_table.gtt_on_commit_default
+drop cascades to table global_temporary_table.gtt_on_commit_delete
+drop cascades to table global_temporary_table.gtt_on_commit_delete2
+drop cascades to table global_temporary_table.gtt_on_commit_preserve
+drop cascades to table global_temporary_table.gtt_test_new
+drop cascades to table global_temporary_table.gtt_test_createindex
+drop cascades to table global_temporary_table.regular_partition_01_2019
+drop cascades to table global_temporary_table.regular_partition01
+drop cascades to table global_temporary_table.global_temp_partition_01_2021
+drop cascades to table global_temporary_table.inherits_parent
+drop cascades to table global_temporary_table.inherits_parent_global_temp
+drop cascades to table global_temporary_table.gtt_test_alter1
+drop cascades to table global_temporary_table.foo
+drop cascades to table global_temporary_table.temp_products
+drop cascades to table global_temporary_table.products
+drop cascades to table global_temporary_table.temp_orders
+drop cascades to table global_temporary_table.temp_orders_2
+drop cascades to table global_temporary_table.global_temp_with_serial
+drop cascades to sequence global_temporary_table.seq_1
+drop cascades to table global_temporary_table.temp_table_with_sequence_oncommit_delete
+drop cascades to table global_temporary_table.temp_table_with_sequence_oncommit_preserve
+drop cascades to table global_temporary_table.temp_table_test_statistics
+drop cascades to table global_temporary_table.gtt_t_kenyon
+drop cascades to table global_temporary_table.temp_table_test_systemview
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2fa00a3c29..e0001bc344 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1359,6 +1359,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 7be89178f0..db8095d30b 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -130,3 +130,6 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: global_temporary_table
diff --git a/src/test/regress/sql/global_temporary_table.sql b/src/test/regress/sql/global_temporary_table.sql
new file mode 100644
index 0000000000..cb49dc1af5
--- /dev/null
+++ b/src/test/regress/sql/global_temporary_table.sql
@@ -0,0 +1,284 @@
+--
+-- GLobal emparary table test case 
+--
+
+CREATE SCHEMA IF NOT EXISTS global_temporary_table;
+set search_path=global_temporary_table,sys;
+
+--
+--  test create global temp table basic syntax
+--
+create global temp table gtt_on_commit_default(a int primary key, b text);
+create global temp table gtt_on_commit_delete(a int primary key, b text) on commit delete rows;
+create global temp table gtt_on_commit_delete2(n int) with (on_commit_delete_rows='true');
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit PRESERVE rows;
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt_test_createindex(c0 tsvector,c1 varchar(100), c2 int);
+
+--
+-- test DML of temp table
+--
+
+-- update empty temp table
+update gtt_on_commit_delete set b ='test';
+begin;
+insert into gtt_on_commit_delete values (1);
+update gtt_on_commit_delete set b ='test';
+-- should 1 row
+select * from gtt_on_commit_delete;
+commit;
+-- data delete after transaction commit
+-- should 0 row
+select * from gtt_on_commit_delete;
+
+-- update empty temp table
+update gtt_on_commit_preserve set b ='test';
+insert into gtt_on_commit_preserve values (2);
+begin;
+insert into gtt_on_commit_preserve values (3);
+update gtt_on_commit_preserve set b ='test';
+-- should 2 row
+select * from gtt_on_commit_preserve order by a;
+delete from gtt_on_commit_preserve where a=2;
+commit;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+
+begin;
+insert into gtt_on_commit_preserve values (4);
+-- temp table support truncate;
+truncate gtt_on_commit_preserve;
+select * from gtt_on_commit_preserve order by a;
+rollback;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+
+--
+-- test unsupported global temp partition table
+--
+
+-- should fail
+CREATE global temp TABLE global_temp_partition_01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+on commit delete rows;
+
+CREATE TABLE regular_partition_01_2019 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+);
+
+CREATE TABLE regular_partition01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time);
+
+-- should fail
+CREATE global temp TABLE temp_partition01_2018
+PARTITION OF regular_partition01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE global_temp_partition_01_2021 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+)on commit delete rows;
+
+-- should fail
+ALTER TABLE regular_partition01 ATTACH PARTITION global_temp_partition_01_2021 FOR VALUES FROM ('2021-01-01 00:00:00') TO ('2022-01-01 00:00:00');
+
+--
+-- test unsupported inherit table
+--
+create table 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 inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent);
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent_global_temp) on commit delete rows;
+
+--
+-- test DDL on global temp table
+--
+create index gtt_idx_1 on gtt_test_createindex using gin (c0);
+create index gtt_idx_2 on gtt_test_createindex using gist (c0);
+create index gtt_idx_3 on gtt_test_createindex using hash (c2);
+alter table gtt_test_rename rename to gtt_test_new;
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+create index CONCURRENTLY idx_b on gtt_on_commit_default (b);
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+insert into gtt_on_commit_default values(1,'test');
+create global temp table gtt_test_alter1 (a int primary key,b text);
+alter table gtt_test_alter1 alter a type varchar;
+-- should fail
+alter table gtt_on_commit_default alter a type varchar;
+-- should fail
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) on commit delete rows;
+-- should fail
+alter table gtt_on_commit_default SET TABLESPACE pg_default;
+-- should fail
+alter table gtt_on_commit_default set ( on_commit_delete_rows='true');
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) with(on_commit_delete_rows=true);
+-- should fail
+create or replace global temp view gtt_v as select 5;
+create table foo();
+-- should fail
+alter table foo set (on_commit_delete_rows='true');
+-- should fail
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit drop;
+-- should fail
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+-- should fail
+CREATE MATERIALIZED VIEW mv_gtt_on_commit_default as select * from gtt_on_commit_default;
+
+--
+-- test foreign key dependencies for global temp table
+--
+CREATE global temp TABLE temp_products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+CREATE global temp TABLE temp_orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+)on commit delete rows;
+
+-- should fail
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+);
+
+CREATE global temp TABLE temp_orders_2 (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products(product_no),
+    quantity integer
+);
+
+--should fail
+insert into temp_orders values(1,1,1);
+
+insert into temp_products values(1,'test',1.0);
+begin;
+insert into temp_orders values(1,1,1);
+commit;
+-- should 1 row
+select count(*) from temp_products;
+-- should 0 row
+select count(*) from temp_orders;
+
+--
+-- test sequence on global temp table
+--
+create global temp table global_temp_with_serial (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+-- support insert data to temp table in read only transaction
+insert into global_temp_with_serial (b) values(1);
+select * from global_temp_with_serial;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_delete(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_preserve(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table temp_table_with_sequence_oncommit_delete add c2 int default nextval('seq_1');
+alter table temp_table_with_sequence_oncommit_preserve add c2 int default nextval('seq_1');
+begin;
+insert into temp_table_with_sequence_oncommit_delete (c1)values(1);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(2);
+insert into temp_table_with_sequence_oncommit_delete (c1)values(3);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(4);
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+commit;
+-- should 0 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_preserve order by c1;
+
+--
+-- test statistics on temp table
+--
+create global temp table temp_table_test_statistics(a int);
+insert into temp_table_test_statistics values(generate_series(1,100000));
+create index idx_test_1 on temp_table_test_statistics (a);
+create index idx_test_2 on temp_table_test_statistics((a*10));
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+analyze temp_table_test_statistics;
+-- indexscan by idx_test_1
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+-- indexscan by idx_test_2
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+
+--
+-- test temp table with toast table
+--
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,10),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',10);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+rollback;
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+
+--
+-- test global temp table system view
+--
+create global temp table temp_table_test_systemview(a int primary key, b text) on commit PRESERVE rows;
+-- should empty, storage not initialized
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+-- should empty, storage not initialized
+select count(*) from pg_list_gtt_relfrozenxids();
+insert into temp_table_test_systemview values(generate_series(1,10000),'test');
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+select count(*) from pg_list_gtt_relfrozenxids();
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+-- should empty
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+analyze temp_table_test_systemview;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+-- get data after analyze;
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+
+-- get all object info in current schema
+select relname ,relkind, relpersistence, reloptions from pg_class c, pg_namespace n where c.relnamespace = n.oid and n.nspname = 'global_temporary_table' order by relkind,relpersistence,relname;
+
+reset search_path;
+drop schema global_temporary_table cascade;
+
-- 
2.25.1

#322wenjing
wjzeng2012@gmail.com
In reply to: Ming Li (#315)
Re: [Proposal] Global temporary tables

2021年7月14日 10:56,Ming Li <mli@apache.org> 写道:

Hi Wenjing,

Some suggestions may help:

1) It seems that no test case covers the below scenario: 2 sessions attach
the same gtt, and insert/update/select concurrently. It is better to use
the test framework in src/test/isolation like the code changes in
https://commitfest.postgresql.org/24/2233/.

I rewrote the case under regress to make it easier to read.
and I used the Isolation module to add some concurrent cases and fix some
bugs.

Please check code(v52) and give me feedback.

Wenjing

2) CREATE GLOBAL TEMP SEQUENCE also need to be supported
in src/bin/psql/tab-complete.c

On Wed, Jul 14, 2021 at 10:36 AM wenjing <wjzeng2012@gmail.com> wrote:

Show quoted text

Rebase code based on the latest version.

Regards,
wenjing

#323wenjing
wjzeng2012@gmail.com
In reply to: Andrew Dunstan (#302)
Re: [Proposal] Global temporary tables

Andrew Dunstan <andrew@dunslane.net> 于2021年3月28日周日 下午9:07写道:

On 3/17/21 7:59 AM, wenjing wrote:

ok

The cause of the problem is that the name of the dependent function
(readNextTransactionID) has changed. I fixed it.

This patch(V43) is base on 9fd2952cf4920d563e9cea51634c5b364d57f71a

Wenjing

I have fixed this patch so that

a) it applies cleanly

b) it uses project best practice for catalog Oid assignment.

However, as noted elsewhere it fails the recovery TAP test.

I also note this:

diff --git a/src/test/regress/parallel_schedule
b/src/test/regress/parallel_schedule
index 312c11a4bd..d44fa62f4e 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -129,3 +129,10 @@ test: fast_default
# run stats by itself because its delay may be insufficient under heavy
load
test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean

Tests that need to run in parallel should use either the isolation
tester framework (which is explicitly for testing things concurrently)
or the TAP test framework.

Adding six test files to the regression test suite for this one feature
is not a good idea. You should have one regression test script ideally,
and it should be added as appropriate to both the parallel and serial
schedules (and not at the end). Any further tests should be added using
the other frameworks mentioned.

Thank you for your advice.
I have simplified the case in regress and put further tests into the
Isolation Tester Framework based on your suggestion.
And I found a few bugs and fixed them.

Please review the GTT v52 and give me feedback.
https://commitfest.postgresql.org/31/2349/

Wenjing

Show quoted text

cheers

andrew

--

Andrew Dunstan
EDB: https://www.enterprisedb.com

#324Tony Zhu
tony.zhu@ww-it.cn
In reply to: wenjing (#323)
Re: [Proposal] Global temporary tables

Hi Wenjing

we have reviewed the code, and done the regression tests, all tests is pass, we believe the feature code quality is ready for production ; and I will change the status to "Ready for commit"

#325wenjing
wjzeng2012@gmail.com
In reply to: Tony Zhu (#324)
4 attachment(s)
Re: [Proposal] Global temporary tables

2021年9月23日 21:55,Tony Zhu <tony.zhu@ww-it.cn> 写道:

Hi Wenjing

we have reviewed the code, and done the regression tests, all tests is
pass, we believe the feature code quality is ready for production ; and I
will change the status to "Ready for commit”

Thank you very much for your attention and testing.
As we communicated, I fixed several issues and attached the latest patch.

Wenjing

Attachments:

0002-gtt-v53-doc.patchapplication/octet-stream; name=0002-gtt-v53-doc.patchDownload
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 473a0a4aeb..e510bde8ac 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -169,32 +169,67 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     <listitem>
      <para>
       If specified, the table is created as a temporary table.
-      Temporary tables are automatically dropped at the end of a
-      session, or optionally at the end of the current transaction
-      (see <literal>ON COMMIT</literal> below).  The default
-      search_path includes the temporary schema first and so identically
-      named existing permanent tables are not chosen for new plans
+      Optionally, <literal>GLOBAL</literal> or <literal>LOCAL</literal>
+      can be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
+      They represent two types of temporary tables supported by <productname>PostgreSQL</productname>:
+      global temporary table and local temporary table. Without specified
+      GLOBAL or LOCAL, a local temporary table is created by default.
+     </para>
+
+    <para>
+     Both types of temporary tables’ data are truncated at the
+     end of a session or optionally at the end of the current transaction.
+     (see <literal>ON COMMIT</literal> below). For global temporary table,
+     its schema is reserved and reused by future sessions or transactions.
+     For local temporary table, both its data and its schema are dropped.
+    </para>
+
+    <variablelist>
+     <varlistentry>
+      <term><literal>Global Temporary Table</literal></term>
+      <listitem>
+       <para>
+        Global temporary table are defined just once and automatically exist
+        (starting with empty contents) in every session that needs them.
+        The schema definition of temporary tables is persistent and shared among sessions.
+        However, the data in temporary tables are kept private to sessions themselves,
+        even though they use same name and same schema.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>Local Temporary Table</literal></term>
+     <listitem>
+     <para>
+      Local temporary table are automatically dropped at the end of a
+      session (include schema and data). Future sessions need to create
+      their own temporary tables when they are used.
+     </para>
+     <para>
+      The default search_path includes the temporary schema first and so
+      identically named existing permanent tables are not chosen for new plans
       while the temporary table exists, unless they are referenced
       with schema-qualified names. Any indexes created on a temporary
       table are automatically temporary as well.
      </para>
+     </listitem>
+     </varlistentry>
+    </variablelist>
 
-     <para>
-      The <link linkend="autovacuum">autovacuum daemon</link> cannot
-      access and therefore cannot vacuum or analyze temporary tables.
-      For this reason, appropriate vacuum and analyze operations should be
-      performed via session SQL commands.  For example, if a temporary
-      table is going to be used in complex queries, it is wise to run
-      <command>ANALYZE</command> on the temporary table after it is populated.
-     </para>
+    <para>
+     The <link linkend="autovacuum">autovacuum daemon</link> cannot
+     access and therefore cannot vacuum or analyze temporary tables.
+     For this reason, appropriate vacuum and analyze operations should be
+     performed via session SQL commands.  For example, if a temporary
+     table is going to be used in complex queries, it is wise to run
+     <command>ANALYZE</command> on the temporary table after it is populated.
+    </para>
+    <para>
+     The Temporary table resembles the SQL standard, but has some differences.
+     see <xref linkend="sql-createtable-compatibility"/> below.
+    </para>
 
-     <para>
-      Optionally, <literal>GLOBAL</literal> or <literal>LOCAL</literal>
-      can be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
-      This presently makes no difference in <productname>PostgreSQL</productname>
-      and is deprecated; see
-      <xref linkend="sql-createtable-compatibility"/> below.
-     </para>
     </listitem>
    </varlistentry>
 
@@ -2133,13 +2168,17 @@ CREATE TABLE cities_partdef
    <title>Temporary Tables</title>
 
    <para>
-    Although the syntax of <literal>CREATE TEMPORARY TABLE</literal>
-    resembles that of the SQL standard, the effect is not the same.  In the
-    standard,
-    temporary tables are defined just once and automatically exist (starting
-    with empty contents) in every session that needs them.
-    <productname>PostgreSQL</productname> instead
-    requires each session to issue its own <literal>CREATE TEMPORARY
+    Although the syntax of <literal>CREATE GLOBAL/LOCAL TEMPORARY TABLE</literal>
+    resembles that of the SQL standard, the effect is not the same.
+    The global temporary table follows the SQL standards while local temporary
+    table does not.
+   </para>
+
+   <para>
+    First, in the standard, both global and local temporary tables are defined just
+    once and automatically exist (starting with empty contents) in every session
+    that needs them. For local temporary tables, <productname>PostgreSQL</productname>
+    instead requires each session to issue its own <literal>CREATE LOCAL TEMPORARY
     TABLE</literal> command for each temporary table to be used.  This allows
     different sessions to use the same temporary table name for different
     purposes, whereas the standard's approach constrains all instances of a
@@ -2147,29 +2186,14 @@ CREATE TABLE cities_partdef
    </para>
 
    <para>
-    The standard's definition of the behavior of temporary tables is
-    widely ignored.  <productname>PostgreSQL</productname>'s behavior
-    on this point is similar to that of several other SQL databases.
-   </para>
-
-   <para>
-    The SQL standard also distinguishes between global and local temporary
+    Second, the SQL standard distinguishes between global and local temporary
     tables, where a local temporary table has a separate set of contents for
     each SQL module within each session, though its definition is still shared
-    across sessions.  Since <productname>PostgreSQL</productname> does not
+    across sessions. Since <productname>PostgreSQL</productname> does not
     support SQL modules, this distinction is not relevant in
     <productname>PostgreSQL</productname>.
    </para>
 
-   <para>
-    For compatibility's sake, <productname>PostgreSQL</productname> will
-    accept the <literal>GLOBAL</literal> and <literal>LOCAL</literal> keywords
-    in a temporary table declaration, but they currently have no effect.
-    Use of these keywords is discouraged, since future versions of
-    <productname>PostgreSQL</productname> might adopt a more
-    standard-compliant interpretation of their meaning.
-   </para>
-
    <para>
     The <literal>ON COMMIT</literal> clause for temporary tables
     also resembles the SQL standard, but has some differences.
@@ -2177,7 +2201,8 @@ CREATE TABLE cities_partdef
     default behavior is <literal>ON COMMIT DELETE ROWS</literal>.  However, the
     default behavior in <productname>PostgreSQL</productname> is
     <literal>ON COMMIT PRESERVE ROWS</literal>.  The <literal>ON COMMIT
-    DROP</literal> option does not exist in SQL.
+    DROP</literal> option does not exist in SQL and is not supported by
+    global temporary table.
    </para>
   </refsect2>
 
-- 
2.30.1 (Apple Git-130)

0004-gtt-v53-regress.patchapplication/octet-stream; name=0004-gtt-v53-regress.patchDownload
diff --git a/src/test/isolation/expected/gtt-sequence.out b/src/test/isolation/expected/gtt-sequence.out
new file mode 100644
index 00000000000..31db2ebd423
--- /dev/null
+++ b/src/test/isolation/expected/gtt-sequence.out
@@ -0,0 +1,48 @@
+unused step name: s1_seq_restart
+Parsed test spec with 2 sessions
+
+starting permutation: s1_seq_next s2_seq_next s1_seq_next
+step s1_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      1
+(1 row)
+
+step s2_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      1
+(1 row)
+
+step s1_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      2
+(1 row)
+
+
+starting permutation: s1_select s2_select s1_insert s2_insert s1_select s2_select
+step s1_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+(0 rows)
+
+step s2_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+(0 rows)
+
+step s1_insert: insert into gtt_with_seq values(1);
+step s2_insert: insert into gtt_with_seq values(10);
+step s1_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+ 1| 3
+(1 row)
+
+step s2_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+10| 1
+(1 row)
+
diff --git a/src/test/isolation/expected/gtt-table.out b/src/test/isolation/expected/gtt-table.out
new file mode 100644
index 00000000000..5825773aa12
--- /dev/null
+++ b/src/test/isolation/expected/gtt-table.out
@@ -0,0 +1,675 @@
+Parsed test spec with 3 sessions
+
+starting permutation: s1_update_d
+step s1_update_d: update gtt_on_commit_delete_row set b = 'update'
+
+starting permutation: s1_select_d s1_insert_d s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_begin s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+1|test1
+(1 row)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_begin s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+1|test1
+(1 row)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_select_d s1_truncate_d s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_begin s1_insert_d s1_select_d s1_truncate_d s1_select_d s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_begin s1_insert_d s1_select_d s1_truncate_d s1_select_d s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_insert_d s1_select_d s1_begin s1_insert_d s1_select_d s1_save_1 s1_truncate_d s1_insert_d s1_select_d s1_save_2 s1_truncate_d s1_insert_d s1_select_d s1_save_3 s1_rollback_to_save_2 s1_select_d s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_save_1: SAVEPOINT save1
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_save_2: SAVEPOINT save2
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+4|test1
+(1 row)
+
+step s1_save_3: SAVEPOINT save3
+step s1_rollback_to_save_2: rollback to savepoint save2
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+5|test1
+(2 rows)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_insert_d s1_select_d s1_begin s1_insert_d s1_select_d s1_save_1 s1_truncate_d s1_insert_d s1_select_d s1_save_2 s1_truncate_d s1_insert_d s1_select_d s1_save_3 s1_rollback_to_save_2 s1_select_d s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_save_1: SAVEPOINT save1
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_save_2: SAVEPOINT save2
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+4|test1
+(1 row)
+
+step s1_save_3: SAVEPOINT save3
+step s1_rollback_to_save_2: rollback to savepoint save2
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+5|test1
+(2 rows)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_update_p
+step s2_update_p: update gtt_on_commit_preserve_row set b = 'update'
+
+starting permutation: s2_select_p s2_insert_p s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_begin s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_begin s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_select_p s2_insert_p s2_select_p s2_truncate_p s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_select_p s2_insert_p s2_begin s2_insert_p s2_select_p s2_truncate_p s2_select_p s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_insert_p s2_begin s2_insert_p s2_select_p s2_truncate_p s2_select_p s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_insert_p s2_select_p s2_begin s2_insert_p s2_select_p s2_save_1 s2_truncate_p s2_insert_p s2_select_p s2_save_2 s2_truncate_p s2_insert_p s2_select_p s2_save_3 s2_rollback_to_save_2 s2_select_p s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_save_1: SAVEPOINT save1
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_save_2: SAVEPOINT save2
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+4|test10
+(1 row)
+
+step s2_save_3: SAVEPOINT save3
+step s2_rollback_to_save_2: rollback to savepoint save2
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+
+starting permutation: s2_insert_p s2_select_p s2_begin s2_insert_p s2_select_p s2_save_1 s2_truncate_p s2_insert_p s2_select_p s2_save_2 s2_truncate_p s2_insert_p s2_select_p s2_save_3 s2_rollback_to_save_2 s2_select_p s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_save_1: SAVEPOINT save1
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_save_2: SAVEPOINT save2
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+4|test10
+(1 row)
+
+step s2_save_3: SAVEPOINT save3
+step s2_rollback_to_save_2: rollback to savepoint save2
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_insert_p s2_insert_p s1_select_p s2_select_p
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_truncate_p s2_truncate_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_begin s1_insert_d s2_insert_d s1_truncate_d s2_insert_d s1_commit
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s2_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s2_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_commit: commit
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_reindex_p s2_reindex_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_reindex_p: reindex table gtt_on_commit_preserve_row
+step s2_reindex_p: reindex table gtt_on_commit_preserve_row
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_reindex_i_p s2_reindex_i_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_reindex_i_p: reindex index gtt_on_commit_preserve_row_pkey
+step s2_reindex_i_p: reindex index gtt_on_commit_preserve_row_pkey
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_insert_c s3_create_c s3_insert_c s1_insert_c s1_analyze_c s2_analyze_c s3_analyze_c s1_select_c s2_select_c s3_select_c
+step s2_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s3_create_c: create unique index idx_temp_table_a on gtt_test_createindex(a)
+step s3_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s1_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s1_analyze_c: analyze gtt_test_createindex
+step s2_analyze_c: analyze gtt_test_createindex
+step s3_analyze_c: analyze gtt_test_createindex
+step s1_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                                               
+---------------------------------------------------------
+Index Scan using idx_temp_table_a on gtt_test_createindex
+  Index Cond: (a = 1)                                    
+(2 rows)
+
+step s2_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                      
+--------------------------------
+Seq Scan on gtt_test_createindex
+  Filter: (a = 1)               
+(2 rows)
+
+step s3_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                                               
+---------------------------------------------------------
+Index Scan using idx_temp_table_a on gtt_test_createindex
+  Index Cond: (a = 1)                                    
+(2 rows)
+
+
+starting permutation: s1_begin s2_begin s1_lock_p s2_lock_p s1_truncate_p s2_truncate_p s1_insert_p s2_insert_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_lock_p: LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE
+step s2_lock_p: LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE
+step s1_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index f4c01006fc1..746a17f824c 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -96,3 +96,5 @@ test: plpgsql-toast
 test: truncate-conflict
 test: serializable-parallel
 test: serializable-parallel-2
+test: gtt-sequence
+test: gtt-table
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 88594a3cb5d..ec643aadb5f 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -80,9 +80,30 @@ disconnect_atexit(void)
 {
 	int			i;
 
-	for (i = 0; i < nconns; i++)
+	for (i = 1; i < nconns; i++)
 		if (conns[i].conn)
 			PQfinish(conns[i].conn);
+
+	if (parseresult.destroy)
+	{
+		PGresult   *res;
+
+		res = PQexec(conns[0].conn, parseresult.destroy);
+		if (PQresultStatus(res) == PGRES_TUPLES_OK)
+		{
+			printResultSet(res);
+		}
+		else if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		{
+			fprintf(stderr, "destroy failed: %s",
+					PQerrorMessage(conns[0].conn));
+			/* don't exit on teardown failure */
+		}
+		PQclear(res);
+	}
+
+	if (conns[0].conn)
+		PQfinish(conns[0].conn);
 }
 
 int
@@ -214,6 +235,24 @@ main(int argc, char **argv)
 	PQclear(res);
 	termPQExpBuffer(&wait_query);
 
+	if (parseresult.initialize)
+	{
+		PGresult   *res;
+
+		res = PQexec(conns[0].conn, parseresult.initialize);
+		if (PQresultStatus(res) == PGRES_TUPLES_OK)
+		{
+			printResultSet(res);
+		}
+		else if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		{
+			fprintf(stderr, "initialize failed: %s",
+					PQerrorMessage(conns[0].conn));
+			/* don't exit on teardown failure */
+		}
+		PQclear(res);
+	}
+
 	/*
 	 * Run the permutations specified in the spec, or all if none were
 	 * explicitly specified.
diff --git a/src/test/isolation/isolationtester.h b/src/test/isolation/isolationtester.h
index 5f300219c20..b5a29893da9 100644
--- a/src/test/isolation/isolationtester.h
+++ b/src/test/isolation/isolationtester.h
@@ -81,6 +81,8 @@ typedef struct
 	int			nsessions;
 	Permutation **permutations;
 	int			npermutations;
+	char	   *initialize;
+	char	   *destroy;
 } TestSpec;
 
 extern TestSpec parseresult;
diff --git a/src/test/isolation/specparse.y b/src/test/isolation/specparse.y
index c25aa1a73fa..2784f758ed9 100644
--- a/src/test/isolation/specparse.y
+++ b/src/test/isolation/specparse.y
@@ -39,7 +39,7 @@ TestSpec		parseresult;			/* result of parsing is left here */
 }
 
 %type <ptr_list> setup_list
-%type <str>  opt_setup opt_teardown
+%type <str>  opt_setup opt_teardown opt_initialize opt_destroy
 %type <str> setup
 %type <ptr_list> step_list session_list permutation_list opt_permutation_list
 %type <ptr_list> permutation_step_list blocker_list
@@ -51,23 +51,27 @@ TestSpec		parseresult;			/* result of parsing is left here */
 
 %token <str> sqlblock identifier
 %token <integer> INTEGER
-%token NOTICES PERMUTATION SESSION SETUP STEP TEARDOWN TEST
+%token NOTICES PERMUTATION SESSION SETUP STEP TEARDOWN TEST INITIALIZE DESTROY
 
 %%
 
 TestSpec:
+			opt_initialize
 			setup_list
 			opt_teardown
+			opt_destroy
 			session_list
 			opt_permutation_list
 			{
-				parseresult.setupsqls = (char **) $1.elements;
-				parseresult.nsetupsqls = $1.nelements;
-				parseresult.teardownsql = $2;
-				parseresult.sessions = (Session **) $3.elements;
-				parseresult.nsessions = $3.nelements;
-				parseresult.permutations = (Permutation **) $4.elements;
-				parseresult.npermutations = $4.nelements;
+				parseresult.setupsqls = (char **) $2.elements;
+				parseresult.nsetupsqls = $2.nelements;
+				parseresult.teardownsql = $3;
+				parseresult.sessions = (Session **) $5.elements;
+				parseresult.nsessions = $5.nelements;
+				parseresult.permutations = (Permutation **) $6.elements;
+				parseresult.npermutations = $6.nelements;
+				parseresult.initialize = $1;
+				parseresult.destroy = $4;
 			}
 		;
 
@@ -100,6 +104,16 @@ opt_teardown:
 			| TEARDOWN sqlblock	{ $$ = $2; }
 		;
 
+opt_initialize:
+			/* EMPTY */			{ $$ = NULL; }
+			| INITIALIZE sqlblock	{ $$ = $2; }
+		;
+
+opt_destroy:
+			/* EMPTY */			{ $$ = NULL; }
+			| DESTROY sqlblock	{ $$ = $2; }
+		;
+
 session_list:
 			session_list session
 			{
diff --git a/src/test/isolation/specs/gtt-sequence.spec b/src/test/isolation/specs/gtt-sequence.spec
new file mode 100644
index 00000000000..88eece45e29
--- /dev/null
+++ b/src/test/isolation/specs/gtt-sequence.spec
@@ -0,0 +1,39 @@
+# Tests for global temporary relations
+
+initialize
+{
+  CREATE GLOBAL TEMPORARY TABLE if not exists gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+}
+
+destroy
+{
+  /* wait other backend exit */
+  select pg_sleep(1);
+
+  DROP TABLE gtt_with_seq;
+}
+
+# Session 1
+session "s1"
+step "s1_seq_next" { select nextval('gtt_with_seq_c2_seq'); }
+step "s1_seq_restart" { alter sequence gtt_with_seq_c2_seq RESTART; }
+step "s1_insert" { insert into gtt_with_seq values(1); }
+step "s1_select" { select * from gtt_with_seq order by c1,c2; }
+teardown
+{
+  TRUNCATE gtt_with_seq;
+}
+
+# Session 2
+session "s2"
+step "s2_seq_next" { select nextval('gtt_with_seq_c2_seq'); }
+step "s2_insert" { insert into gtt_with_seq values(10); }
+step "s2_select" { select * from gtt_with_seq order by c1,c2; }
+teardown
+{
+  TRUNCATE gtt_with_seq RESTART IDENTITY;
+}
+
+permutation "s1_seq_next" "s2_seq_next" "s1_seq_next"
+permutation "s1_select" "s2_select" "s1_insert" "s2_insert" "s1_select" "s2_select"
+
diff --git a/src/test/isolation/specs/gtt-table.spec b/src/test/isolation/specs/gtt-table.spec
new file mode 100644
index 00000000000..e0396b21ef0
--- /dev/null
+++ b/src/test/isolation/specs/gtt-table.spec
@@ -0,0 +1,135 @@
+# Tests for global temporary relations
+
+initialize
+{
+  create global temp table gtt_on_commit_delete_row(a bigserial primary key, b text) on commit delete rows;
+  create global temp table gtt_on_commit_preserve_row(a bigserial primary key, b text) on commit preserve rows;
+  create global temp table gtt_test_createindex(a int, b char(1000)) on commit preserve rows;
+}
+
+destroy
+{
+  /* wait other backend exit */
+  select pg_sleep(1);
+
+  DROP TABLE gtt_on_commit_delete_row;
+  DROP TABLE gtt_on_commit_preserve_row;
+  DROP TABLE gtt_test_createindex;
+}
+
+# Session 1
+session "s1"
+step "s1_begin" {begin}
+step "s1_commit" {commit}
+step "s1_rollback" {rollback}
+step "s1_insert_d" {insert into gtt_on_commit_delete_row (b) values('test1')}
+step "s1_select_d" {select a,b from gtt_on_commit_delete_row order by a,b}
+step "s1_insert_p" {insert into gtt_on_commit_preserve_row (b) values('test20')}
+step "s1_select_p" {select a,b from gtt_on_commit_preserve_row order by a,b}
+step "s1_truncate_d" {truncate gtt_on_commit_delete_row}
+step "s1_truncate_p" {truncate gtt_on_commit_preserve_row}
+step "s1_lock_p" {LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE}
+step "s1_update_d" {update gtt_on_commit_delete_row set b = 'update'}
+step "s1_save_1" {SAVEPOINT save1}
+step "s1_save_2" {SAVEPOINT save2}
+step "s1_save_3" {SAVEPOINT save3}
+step "s1_rollback_to_save_2" {rollback to savepoint save2}
+step "s1_reindex_p" {reindex table gtt_on_commit_preserve_row}
+step "s1_reindex_i_p" {reindex index gtt_on_commit_preserve_row_pkey}
+step "s1_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s1_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s1_analyze_c" {analyze gtt_test_createindex}
+teardown
+{
+  TRUNCATE gtt_on_commit_delete_row RESTART IDENTITY;
+  TRUNCATE gtt_on_commit_preserve_row RESTART IDENTITY;
+}
+
+# Session 2
+session "s2"
+step "s2_begin" {begin}
+step "s2_commit" {commit}
+step "s2_rollback" {rollback}
+step "s2_insert_p" {insert into gtt_on_commit_preserve_row (b) values('test10')}
+step "s2_select_p" {select a,b from gtt_on_commit_preserve_row order by a,b}
+step "s2_insert_d" {insert into gtt_on_commit_delete_row (b) values('test1')}
+step "s2_truncate_p" {truncate gtt_on_commit_preserve_row}
+step "s2_update_p" {update gtt_on_commit_preserve_row set b = 'update'}
+step "s2_lock_p" {LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE}
+step "s2_save_1" {SAVEPOINT save1}
+step "s2_save_2" {SAVEPOINT save2}
+step "s2_save_3" {SAVEPOINT save3}
+step "s2_rollback_to_save_2" {rollback to savepoint save2}
+step "s2_reindex_p" {reindex table gtt_on_commit_preserve_row}
+step "s2_reindex_i_p" {reindex index gtt_on_commit_preserve_row_pkey}
+step "s2_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s2_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s2_analyze_c" {analyze gtt_test_createindex}
+teardown
+{
+  TRUNCATE gtt_on_commit_delete_row RESTART IDENTITY;
+  TRUNCATE gtt_on_commit_preserve_row RESTART IDENTITY;
+}
+
+session "s3"
+step "s3_create_c" {create unique index idx_temp_table_a on gtt_test_createindex(a)}
+step "s3_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s3_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s3_analyze_c" {analyze gtt_test_createindex}
+
+
+#
+# test on commit delete temp table
+#
+
+# test update empty temp table
+permutation "s1_update_d"
+# test insert into temp table
+permutation "s1_select_d" "s1_insert_d" "s1_select_d"
+# test temp table in transaction(commit)
+permutation "s1_select_d" "s1_begin"    "s1_insert_d" "s1_select_d"   "s1_commit"   "s1_select_d" 
+# test temp table in transaction(rollback)
+permutation "s1_select_d" "s1_begin"    "s1_insert_d" "s1_select_d"   "s1_rollback" "s1_select_d" 
+# test truncate
+permutation "s1_select_d" "s1_insert_d" "s1_select_d" "s1_truncate_d" "s1_select_d"
+# test truncate in transaction block
+permutation "s1_select_d" "s1_insert_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_truncate_d" "s1_select_d"   "s1_insert_d" "s1_select_d" "s1_commit"   "s1_select_d" 
+permutation "s1_select_d" "s1_insert_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_truncate_d" "s1_select_d"   "s1_insert_d" "s1_select_d" "s1_rollback" "s1_select_d"
+# test temp table with subtransaction or savepoint
+permutation "s1_insert_d" "s1_select_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_save_1"     "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_2"   "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_3" "s1_rollback_to_save_2" "s1_select_d" "s1_insert_d" "s1_select_d" "s1_commit" "s1_select_d"
+permutation "s1_insert_d" "s1_select_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_save_1"     "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_2"   "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_3" "s1_rollback_to_save_2" "s1_select_d" "s1_insert_d" "s1_select_d" "s1_rollback" "s1_select_d"
+
+#
+# test on commit preserve table
+#
+
+# same as test on commit delete temp table
+permutation "s2_update_p"
+permutation "s2_select_p" "s2_insert_p" "s2_select_p"
+permutation "s2_select_p" "s2_begin"    "s2_insert_p" "s2_select_p"   "s2_commit"   "s2_select_p"
+permutation "s2_select_p" "s2_begin"    "s2_insert_p" "s2_select_p"   "s2_rollback" "s2_select_p"
+permutation "s2_select_p" "s2_insert_p" "s2_select_p" "s2_truncate_p" "s2_select_p"
+permutation "s2_select_p" "s2_insert_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_truncate_p" "s2_select_p"   "s2_insert_p" "s2_select_p" "s2_commit"   "s2_select_p" 
+permutation "s2_select_p" "s2_insert_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_truncate_p" "s2_select_p"   "s2_insert_p" "s2_select_p" "s2_rollback" "s2_select_p" 
+permutation "s2_insert_p" "s2_select_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_save_1"     "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_2"   "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_3" "s2_rollback_to_save_2" "s2_select_p" "s2_insert_p" "s2_select_p" "s2_commit" "s2_select_p"
+permutation "s2_insert_p" "s2_select_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_save_1"     "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_2"   "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_3" "s2_rollback_to_save_2" "s2_select_p" "s2_insert_p" "s2_select_p" "s2_rollback" "s2_select_p"
+
+#
+# test concurrent operation on temp table
+#
+
+#  test concurrent read
+permutation "s1_insert_p" "s2_insert_p" "s1_select_p" "s2_select_p" 
+#  test concurrent truncate
+permutation "s1_begin" "s2_begin"    "s1_insert_p" "s2_insert_p"   "s1_truncate_p" "s2_truncate_p"  "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+permutation "s1_begin" "s1_insert_d" "s2_insert_d" "s1_truncate_d" "s2_insert_d"   "s1_commit" 
+#  test concurrent reindex table
+permutation "s1_begin" "s2_begin" "s1_insert_p" "s2_insert_p" "s1_reindex_p"   "s2_reindex_p"   "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+#  test concurrent reindex index
+permutation "s1_begin" "s2_begin" "s1_insert_p" "s2_insert_p" "s1_reindex_i_p" "s2_reindex_i_p" "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+
+# test create index
+permutation "s2_insert_c" "s3_create_c" "s3_insert_c" "s1_insert_c" "s1_analyze_c" "s2_analyze_c" "s3_analyze_c" "s1_select_c" "s2_select_c" "s3_select_c"
+
+# test lock gtt
+permutation "s1_begin" "s2_begin" "s1_lock_p" "s2_lock_p" "s1_truncate_p" "s2_truncate_p" "s1_insert_p" "s2_insert_p" "s1_commit" "s2_commit" "s1_select_p" "s2_select_p"
diff --git a/src/test/isolation/specscanner.l b/src/test/isolation/specscanner.l
index d9fa6a5b54a..697db975479 100644
--- a/src/test/isolation/specscanner.l
+++ b/src/test/isolation/specscanner.l
@@ -67,6 +67,8 @@ session			{ return SESSION; }
 setup			{ return SETUP; }
 step			{ return STEP; }
 teardown		{ return TEARDOWN; }
+initialize		 { return INITIALIZE; }
+destroy			 { return DESTROY; }
 
  /* Whitespace and comments */
 [\n]			{ yyline++; }
diff --git a/src/test/regress/expected/global_temporary_table.out b/src/test/regress/expected/global_temporary_table.out
new file mode 100644
index 00000000000..def015dcbd6
--- /dev/null
+++ b/src/test/regress/expected/global_temporary_table.out
@@ -0,0 +1,506 @@
+--
+-- GLobal emparary table test case 
+--
+CREATE SCHEMA IF NOT EXISTS global_temporary_table;
+set search_path=global_temporary_table,sys;
+--
+--  test create global temp table basic syntax
+--
+create global temp table gtt_on_commit_default(a int primary key, b text);
+create global temp table gtt_on_commit_delete(a int primary key, b text) on commit delete rows;
+create global temp table gtt_on_commit_delete2(n int) with (on_commit_delete_rows='true');
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit PRESERVE rows;
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt_test_createindex(c0 tsvector,c1 varchar(100), c2 int);
+--
+-- test DML of temp table
+--
+-- update empty temp table
+update gtt_on_commit_delete set b ='test';
+begin;
+insert into gtt_on_commit_delete values (1);
+update gtt_on_commit_delete set b ='test';
+-- should 1 row
+select * from gtt_on_commit_delete;
+ a |  b   
+---+------
+ 1 | test
+(1 row)
+
+commit;
+-- data delete after transaction commit
+-- should 0 row
+select * from gtt_on_commit_delete;
+ a | b 
+---+---
+(0 rows)
+
+-- update empty temp table
+update gtt_on_commit_preserve set b ='test';
+insert into gtt_on_commit_preserve values (2);
+begin;
+insert into gtt_on_commit_preserve values (3);
+update gtt_on_commit_preserve set b ='test';
+-- should 2 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 2 | test
+ 3 | test
+(2 rows)
+
+delete from gtt_on_commit_preserve where a=2;
+commit;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 3 | test
+(1 row)
+
+begin;
+insert into gtt_on_commit_preserve values (4);
+-- temp table support truncate;
+truncate gtt_on_commit_preserve;
+select * from gtt_on_commit_preserve order by a;
+ a | b 
+---+---
+(0 rows)
+
+rollback;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 3 | test
+(1 row)
+
+--
+-- test unsupported global temp partition table
+--
+-- should fail
+CREATE global temp TABLE global_temp_partition_01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+on commit delete rows;
+ERROR:  Only support global temporary regular table.
+CREATE TABLE regular_partition_01_2019 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+);
+CREATE TABLE regular_partition01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time);
+-- should fail
+CREATE global temp TABLE temp_partition01_2018
+PARTITION OF regular_partition01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+ERROR:  Not support global temporary partition table or inherit table.
+CREATE global temp TABLE global_temp_partition_01_2021 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+)on commit delete rows;
+-- should fail
+ALTER TABLE regular_partition01 ATTACH PARTITION global_temp_partition_01_2021 FOR VALUES FROM ('2021-01-01 00:00:00') TO ('2022-01-01 00:00:00');
+ERROR:  cannot attach a global temporary relation as partition of permanent relation "regular_partition01"
+--
+-- test unsupported inherit table
+--
+create table 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 inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent);
+ERROR:  Not support global temporary partition table or inherit table.
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent_global_temp) on commit delete rows;
+ERROR:  Not support global temporary partition table or inherit table.
+--
+-- test DDL on global temp table
+--
+create index gtt_idx_1 on gtt_test_createindex using gin (c0);
+create index gtt_idx_2 on gtt_test_createindex using gist (c0);
+create index gtt_idx_3 on gtt_test_createindex using hash (c2);
+alter table gtt_test_rename rename to gtt_test_new;
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+create index CONCURRENTLY idx_b on gtt_on_commit_default (b);
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+insert into gtt_on_commit_default values(1,'test');
+create global temp table gtt_test_alter1 (a int primary key,b text);
+alter table gtt_test_alter1 alter a type varchar;
+-- should fail
+alter table gtt_on_commit_default alter a type varchar;
+ERROR:  Only support alter global temporary table in an empty context.
+HINT:  Please create a new connection and execute ALTER TABLE on the new connection.
+-- should fail
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+ERROR:  not support cluster global temporary table yet
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- should fail
+alter table gtt_on_commit_default SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temporary table
+-- should fail
+alter table gtt_on_commit_default set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table
+-- should fail
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- should fail
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- should fail
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit drop;
+ERROR:  global temporary table not support on commit drop clause
+-- should fail
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  could not create global temporary table with on commit and with clause at same time
+-- should fail
+CREATE MATERIALIZED VIEW mv_gtt_on_commit_default as select * from gtt_on_commit_default;
+ERROR:  materialized views must not use global temporary tables or views
+--
+-- test foreign key dependencies for global temp table
+--
+CREATE global temp TABLE temp_products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+CREATE global temp TABLE temp_orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+)on commit delete rows;
+-- should fail
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+CREATE global temp TABLE temp_orders_2 (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products(product_no),
+    quantity integer
+);
+--should fail
+insert into temp_orders values(1,1,1);
+ERROR:  insert or update on table "temp_orders" violates foreign key constraint "temp_orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "temp_products".
+insert into temp_products values(1,'test',1.0);
+begin;
+insert into temp_orders values(1,1,1);
+commit;
+-- should 1 row
+select count(*) from temp_products;
+ count 
+-------
+     1
+(1 row)
+
+-- should 0 row
+select count(*) from temp_orders;
+ count 
+-------
+     0
+(1 row)
+
+--
+-- test sequence on global temp table
+--
+create global temp table global_temp_with_serial (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+-- support insert data to temp table in read only transaction
+insert into global_temp_with_serial (b) values(1);
+select * from global_temp_with_serial;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_delete(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_preserve(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table temp_table_with_sequence_oncommit_delete add c2 int default nextval('seq_1');
+alter table temp_table_with_sequence_oncommit_preserve add c2 int default nextval('seq_1');
+begin;
+insert into temp_table_with_sequence_oncommit_delete (c1)values(1);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(2);
+insert into temp_table_with_sequence_oncommit_delete (c1)values(3);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(4);
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  3 |  3
+(2 rows)
+
+commit;
+-- should 0 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_preserve order by c1;
+ c1 | c2 
+----+----
+  2 |  2
+  4 |  4
+(2 rows)
+
+--
+-- test statistics on temp table
+--
+create global temp table temp_table_test_statistics(a int);
+insert into temp_table_test_statistics values(generate_series(1,100000));
+create index idx_test_1 on temp_table_test_statistics (a);
+create index idx_test_2 on temp_table_test_statistics((a*10));
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+                   QUERY PLAN                   
+------------------------------------------------
+ Bitmap Heap Scan on temp_table_test_statistics
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_test_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+                   QUERY PLAN                   
+------------------------------------------------
+ Bitmap Heap Scan on temp_table_test_statistics
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_test_2
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze temp_table_test_statistics;
+-- indexscan by idx_test_1
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Index Only Scan using idx_test_1 on temp_table_test_statistics
+   Index Cond: (a = 200000)
+(2 rows)
+
+-- indexscan by idx_test_2
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+                        QUERY PLAN                         
+-----------------------------------------------------------
+ Index Scan using idx_test_2 on temp_table_test_statistics
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+--
+-- test temp table with toast table
+--
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |                0 |        483328 |           98304 |                 581632
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            32768 |                  |         32768 |               0 |                  32768
+(3 rows)
+
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |             8192 |        499712 |          106496 |                 606208
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            40960 |                  |         40960 |               0 |                  40960
+(3 rows)
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,10),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',10);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |             8192 |                0 |         16384 |           32768 |                  49152
+ idx_gtt_t_kenyon_1 |            16384 |                  |         16384 |               0 |                  16384
+ idx_gtt_t_kenyon_2 |            16384 |                  |         16384 |               0 |                  16384
+(3 rows)
+
+rollback;
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |             8192 |        499712 |          106496 |                 606208
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            40960 |                  |         40960 |               0 |                  40960
+(3 rows)
+
+--
+-- test global temp table system view
+--
+create global temp table temp_table_test_systemview(a int primary key, b text) on commit PRESERVE rows;
+-- should empty, storage not initialized
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+ tablename 
+-----------
+(0 rows)
+
+-- should empty, storage not initialized
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into temp_table_test_systemview values(generate_series(1,10000),'test');
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+         tablename          
+----------------------------
+ temp_table_test_systemview
+(1 row)
+
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+       schemaname       |            tablename            | relpages | reltuples | relallvisible 
+------------------------+---------------------------------+----------+-----------+---------------
+ global_temporary_table | temp_table_test_systemview      |        0 |         0 |             0
+ global_temporary_table | temp_table_test_systemview_pkey |        1 |         0 |             0
+(2 rows)
+
+-- should empty
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+analyze temp_table_test_systemview;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+       schemaname       |            tablename            | relpages | reltuples | relallvisible 
+------------------------+---------------------------------+----------+-----------+---------------
+ global_temporary_table | temp_table_test_systemview      |       55 |     10000 |             0
+ global_temporary_table | temp_table_test_systemview_pkey |       30 |     10000 |             0
+(2 rows)
+
+-- get data after analyze;
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+       schemaname       |         tablename          | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------------------+----------------------------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ global_temporary_table | temp_table_test_systemview | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ global_temporary_table | temp_table_test_systemview | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+-- get all object info in current schema
+select relname ,relkind, relpersistence, reloptions from pg_class c, pg_namespace n where c.relnamespace = n.oid and n.nspname = 'global_temporary_table' order by relkind,relpersistence,relname;
+                     relname                     | relkind | relpersistence |          reloptions           
+-------------------------------------------------+---------+----------------+-------------------------------
+ global_temp_partition_01_2021_id_seq            | S       | g              | {on_commit_delete_rows=false}
+ global_temp_with_serial_a_seq                   | S       | g              | {on_commit_delete_rows=false}
+ regular_partition01_id_seq                      | S       | p              | 
+ regular_partition_01_2019_id_seq                | S       | p              | 
+ seq_1                                           | S       | p              | 
+ gtt_idx_1                                       | i       | g              | 
+ gtt_idx_2                                       | i       | g              | 
+ gtt_idx_3                                       | i       | g              | 
+ gtt_on_commit_default_pkey                      | i       | g              | 
+ gtt_on_commit_delete_pkey                       | i       | g              | 
+ gtt_on_commit_preserve_pkey                     | i       | g              | 
+ gtt_test_alter1_pkey                            | i       | g              | 
+ gtt_test_rename_pkey                            | i       | g              | 
+ idx_b                                           | i       | g              | 
+ idx_gtt_t_kenyon_1                              | i       | g              | 
+ idx_gtt_t_kenyon_2                              | i       | g              | 
+ idx_test_1                                      | i       | g              | 
+ idx_test_2                                      | i       | g              | 
+ products_pkey                                   | i       | g              | 
+ temp_orders_2_pkey                              | i       | g              | 
+ temp_orders_pkey                                | i       | g              | 
+ temp_products_pkey                              | i       | g              | 
+ temp_table_test_systemview_pkey                 | i       | g              | 
+ temp_table_with_sequence_oncommit_delete_pkey   | i       | g              | 
+ temp_table_with_sequence_oncommit_preserve_pkey | i       | g              | 
+ regular_partition01                             | p       | p              | 
+ global_temp_partition_01_2021                   | r       | g              | {on_commit_delete_rows=true}
+ global_temp_with_serial                         | r       | g              | {on_commit_delete_rows=false}
+ gtt_on_commit_default                           | r       | g              | {on_commit_delete_rows=false}
+ gtt_on_commit_delete                            | r       | g              | {on_commit_delete_rows=true}
+ gtt_on_commit_delete2                           | r       | g              | {on_commit_delete_rows=true}
+ gtt_on_commit_preserve                          | r       | g              | {on_commit_delete_rows=false}
+ gtt_t_kenyon                                    | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_alter1                                 | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_createindex                            | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_new                                    | r       | g              | {on_commit_delete_rows=false}
+ inherits_parent_global_temp                     | r       | g              | {on_commit_delete_rows=true}
+ products                                        | r       | g              | {on_commit_delete_rows=false}
+ temp_orders                                     | r       | g              | {on_commit_delete_rows=true}
+ temp_orders_2                                   | r       | g              | {on_commit_delete_rows=false}
+ temp_products                                   | r       | g              | {on_commit_delete_rows=false}
+ temp_table_test_statistics                      | r       | g              | {on_commit_delete_rows=false}
+ temp_table_test_systemview                      | r       | g              | {on_commit_delete_rows=false}
+ temp_table_with_sequence_oncommit_delete        | r       | g              | {on_commit_delete_rows=true}
+ temp_table_with_sequence_oncommit_preserve      | r       | g              | {on_commit_delete_rows=false}
+ foo                                             | r       | p              | 
+ inherits_parent                                 | r       | p              | 
+ regular_partition_01_2019                       | r       | p              | 
+(48 rows)
+
+reset search_path;
+drop schema global_temporary_table cascade;
+NOTICE:  drop cascades to 24 other objects
+DETAIL:  drop cascades to table global_temporary_table.gtt_on_commit_default
+drop cascades to table global_temporary_table.gtt_on_commit_delete
+drop cascades to table global_temporary_table.gtt_on_commit_delete2
+drop cascades to table global_temporary_table.gtt_on_commit_preserve
+drop cascades to table global_temporary_table.gtt_test_new
+drop cascades to table global_temporary_table.gtt_test_createindex
+drop cascades to table global_temporary_table.regular_partition_01_2019
+drop cascades to table global_temporary_table.regular_partition01
+drop cascades to table global_temporary_table.global_temp_partition_01_2021
+drop cascades to table global_temporary_table.inherits_parent
+drop cascades to table global_temporary_table.inherits_parent_global_temp
+drop cascades to table global_temporary_table.gtt_test_alter1
+drop cascades to table global_temporary_table.foo
+drop cascades to table global_temporary_table.temp_products
+drop cascades to table global_temporary_table.products
+drop cascades to table global_temporary_table.temp_orders
+drop cascades to table global_temporary_table.temp_orders_2
+drop cascades to table global_temporary_table.global_temp_with_serial
+drop cascades to sequence global_temporary_table.seq_1
+drop cascades to table global_temporary_table.temp_table_with_sequence_oncommit_delete
+drop cascades to table global_temporary_table.temp_table_with_sequence_oncommit_preserve
+drop cascades to table global_temporary_table.temp_table_test_statistics
+drop cascades to table global_temporary_table.gtt_t_kenyon
+drop cascades to table global_temporary_table.temp_table_test_systemview
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2fa00a3c29a..e0001bc3448 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1359,6 +1359,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 7be89178f0f..db8095d30bf 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -130,3 +130,6 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: global_temporary_table
diff --git a/src/test/regress/sql/global_temporary_table.sql b/src/test/regress/sql/global_temporary_table.sql
new file mode 100644
index 00000000000..cb49dc1af57
--- /dev/null
+++ b/src/test/regress/sql/global_temporary_table.sql
@@ -0,0 +1,284 @@
+--
+-- GLobal emparary table test case 
+--
+
+CREATE SCHEMA IF NOT EXISTS global_temporary_table;
+set search_path=global_temporary_table,sys;
+
+--
+--  test create global temp table basic syntax
+--
+create global temp table gtt_on_commit_default(a int primary key, b text);
+create global temp table gtt_on_commit_delete(a int primary key, b text) on commit delete rows;
+create global temp table gtt_on_commit_delete2(n int) with (on_commit_delete_rows='true');
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit PRESERVE rows;
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt_test_createindex(c0 tsvector,c1 varchar(100), c2 int);
+
+--
+-- test DML of temp table
+--
+
+-- update empty temp table
+update gtt_on_commit_delete set b ='test';
+begin;
+insert into gtt_on_commit_delete values (1);
+update gtt_on_commit_delete set b ='test';
+-- should 1 row
+select * from gtt_on_commit_delete;
+commit;
+-- data delete after transaction commit
+-- should 0 row
+select * from gtt_on_commit_delete;
+
+-- update empty temp table
+update gtt_on_commit_preserve set b ='test';
+insert into gtt_on_commit_preserve values (2);
+begin;
+insert into gtt_on_commit_preserve values (3);
+update gtt_on_commit_preserve set b ='test';
+-- should 2 row
+select * from gtt_on_commit_preserve order by a;
+delete from gtt_on_commit_preserve where a=2;
+commit;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+
+begin;
+insert into gtt_on_commit_preserve values (4);
+-- temp table support truncate;
+truncate gtt_on_commit_preserve;
+select * from gtt_on_commit_preserve order by a;
+rollback;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+
+--
+-- test unsupported global temp partition table
+--
+
+-- should fail
+CREATE global temp TABLE global_temp_partition_01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+on commit delete rows;
+
+CREATE TABLE regular_partition_01_2019 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+);
+
+CREATE TABLE regular_partition01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time);
+
+-- should fail
+CREATE global temp TABLE temp_partition01_2018
+PARTITION OF regular_partition01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE global_temp_partition_01_2021 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+)on commit delete rows;
+
+-- should fail
+ALTER TABLE regular_partition01 ATTACH PARTITION global_temp_partition_01_2021 FOR VALUES FROM ('2021-01-01 00:00:00') TO ('2022-01-01 00:00:00');
+
+--
+-- test unsupported inherit table
+--
+create table 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 inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent);
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent_global_temp) on commit delete rows;
+
+--
+-- test DDL on global temp table
+--
+create index gtt_idx_1 on gtt_test_createindex using gin (c0);
+create index gtt_idx_2 on gtt_test_createindex using gist (c0);
+create index gtt_idx_3 on gtt_test_createindex using hash (c2);
+alter table gtt_test_rename rename to gtt_test_new;
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+create index CONCURRENTLY idx_b on gtt_on_commit_default (b);
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+insert into gtt_on_commit_default values(1,'test');
+create global temp table gtt_test_alter1 (a int primary key,b text);
+alter table gtt_test_alter1 alter a type varchar;
+-- should fail
+alter table gtt_on_commit_default alter a type varchar;
+-- should fail
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) on commit delete rows;
+-- should fail
+alter table gtt_on_commit_default SET TABLESPACE pg_default;
+-- should fail
+alter table gtt_on_commit_default set ( on_commit_delete_rows='true');
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) with(on_commit_delete_rows=true);
+-- should fail
+create or replace global temp view gtt_v as select 5;
+create table foo();
+-- should fail
+alter table foo set (on_commit_delete_rows='true');
+-- should fail
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit drop;
+-- should fail
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+-- should fail
+CREATE MATERIALIZED VIEW mv_gtt_on_commit_default as select * from gtt_on_commit_default;
+
+--
+-- test foreign key dependencies for global temp table
+--
+CREATE global temp TABLE temp_products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+CREATE global temp TABLE temp_orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+)on commit delete rows;
+
+-- should fail
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+);
+
+CREATE global temp TABLE temp_orders_2 (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products(product_no),
+    quantity integer
+);
+
+--should fail
+insert into temp_orders values(1,1,1);
+
+insert into temp_products values(1,'test',1.0);
+begin;
+insert into temp_orders values(1,1,1);
+commit;
+-- should 1 row
+select count(*) from temp_products;
+-- should 0 row
+select count(*) from temp_orders;
+
+--
+-- test sequence on global temp table
+--
+create global temp table global_temp_with_serial (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+-- support insert data to temp table in read only transaction
+insert into global_temp_with_serial (b) values(1);
+select * from global_temp_with_serial;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_delete(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_preserve(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table temp_table_with_sequence_oncommit_delete add c2 int default nextval('seq_1');
+alter table temp_table_with_sequence_oncommit_preserve add c2 int default nextval('seq_1');
+begin;
+insert into temp_table_with_sequence_oncommit_delete (c1)values(1);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(2);
+insert into temp_table_with_sequence_oncommit_delete (c1)values(3);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(4);
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+commit;
+-- should 0 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_preserve order by c1;
+
+--
+-- test statistics on temp table
+--
+create global temp table temp_table_test_statistics(a int);
+insert into temp_table_test_statistics values(generate_series(1,100000));
+create index idx_test_1 on temp_table_test_statistics (a);
+create index idx_test_2 on temp_table_test_statistics((a*10));
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+analyze temp_table_test_statistics;
+-- indexscan by idx_test_1
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+-- indexscan by idx_test_2
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+
+--
+-- test temp table with toast table
+--
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,10),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',10);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+rollback;
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+
+--
+-- test global temp table system view
+--
+create global temp table temp_table_test_systemview(a int primary key, b text) on commit PRESERVE rows;
+-- should empty, storage not initialized
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+-- should empty, storage not initialized
+select count(*) from pg_list_gtt_relfrozenxids();
+insert into temp_table_test_systemview values(generate_series(1,10000),'test');
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+select count(*) from pg_list_gtt_relfrozenxids();
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+-- should empty
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+analyze temp_table_test_systemview;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+-- get data after analyze;
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+
+-- get all object info in current schema
+select relname ,relkind, relpersistence, reloptions from pg_class c, pg_namespace n where c.relnamespace = n.oid and n.nspname = 'global_temporary_table' order by relkind,relpersistence,relname;
+
+reset search_path;
+drop schema global_temporary_table cascade;
+
-- 
2.30.1 (Apple Git-130)

0003-gtt-v53-implementation.patchapplication/octet-stream; name=0003-gtt-v53-implementation.patchDownload
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index b5602f53233..21b2d2a9527 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -159,6 +159,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * In order to avoid consistency problems, the global temporary table
+	 * uses ShareUpdateExclusiveLock.
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temporary table on commit options",
+			RELOPT_KIND_HEAP,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},
 	/* list terminator */
 	{{NULL}}
 };
@@ -1834,6 +1847,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 43ba03b6eb9..49f1052fdb1 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1023,7 +1023,7 @@ gistproperty(Oid index_oid, int attno,
 XLogRecPtr
 gistGetFakeLSN(Relation rel)
 {
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	if (RELATION_IS_TEMP(rel))
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index eb3810494f2..cbd22909582 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -151,7 +151,7 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	if (!RELATION_IS_TEMP(index))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 9befe012a9e..26fce0c4b83 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -593,7 +593,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
@@ -645,7 +645,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(RelationGetSmgr(rel), dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 9eaf07649e8..41db273abed 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -63,6 +63,7 @@
 #include "access/xlog.h"
 #include "catalog/index.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -508,6 +509,14 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 	TransactionId FreezeLimit;
 	MultiXactId MultiXactCutoff;
 
+	/*
+	 * not every AM requires these to be valid, but regular heap does.
+	 * Transaction information for the global temp table will be stored
+	 * in the local hash table, not the catalog.
+	 */
+	Assert(RELATION_IS_GLOBAL_TEMP(rel) ^ TransactionIdIsNormal(rel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(rel) ^ MultiXactIdIsValid(rel->rd_rel->relminmxid));
+
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
 	{
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index ebec8fa5b89..84766b3a338 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -677,6 +678,14 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		/*
+		 * If a global temporary table storage file is not initialized in the
+		 * current backend, its index does not have a root page, just returns 0.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 5fcd004e1b1..58b994cef57 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -212,7 +212,8 @@ Boot_CreateStmt:
 												   mapped_relation,
 												   true,
 												   &relfrozenxid,
-												   &relminmxid);
+												   &relminmxid,
+												   false);
 						elog(DEBUG4, "bootstrap relation created");
 					}
 					else
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e773612..8c21979625f 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -44,6 +44,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index aa7d4d5456b..595cb03eb4a 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -504,6 +504,7 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 83746d3fd91..656cc675255 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -62,6 +62,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,
@@ -306,7 +308,8 @@ heap_create(const char *relname,
 			bool mapped_relation,
 			bool allow_system_table_mods,
 			TransactionId *relfrozenxid,
-			MultiXactId *relminmxid)
+			MultiXactId *relminmxid,
+			bool skip_create_storage)
 {
 	bool		create_storage;
 	Relation	rel;
@@ -366,13 +369,26 @@ heap_create(const char *relname,
 			break;
 	}
 
+	/* For global temporary table, even if the storage is not initialized,
+	 * the relfilenode needs to be generated and put into the catalog.
+	 */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		if (skip_create_storage)
+			create_storage = false;
+
+		if (!OidIsValid(relfilenode))
+			relfilenode = relid;
+	}
 	/*
 	 * Decide whether to create storage. If caller passed a valid relfilenode,
 	 * storage is already created, so don't do it here.  Also don't create it
 	 * for relkinds without physical storage.
 	 */
-	if (!RELKIND_HAS_STORAGE(relkind) || OidIsValid(relfilenode))
+	else if (!RELKIND_HAS_STORAGE(relkind) || OidIsValid(relfilenode))
+	{
 		create_storage = false;
+	}
 	else
 	{
 		create_storage = true;
@@ -427,7 +443,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:
@@ -445,7 +461,8 @@ heap_create(const char *relname,
 	 * protected by the existence of a physical file; but for relations with
 	 * no files, add a pg_shdepend entry to account for that.
 	 */
-	if (!create_storage && reltablespace != InvalidOid)
+	if (!create_storage && reltablespace != InvalidOid &&
+		relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
 		recordDependencyOnTablespace(RelationRelationId, relid,
 									 reltablespace);
 
@@ -998,6 +1015,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -1036,8 +1054,21 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/*
+	 * The transaction information of the global temporary table is stored
+	 * in the local hash table, not in catalog.
+	 */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1303,7 +1334,8 @@ heap_create_with_catalog(const char *relname,
 							   mapped_relation,
 							   allow_system_table_mods,
 							   &relfrozenxid,
-							   &relminmxid);
+							   &relminmxid,
+							   false);
 
 	Assert(relid == RelationGetRelid(new_rel_desc));
 
@@ -1410,6 +1442,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1495,8 +1528,9 @@ heap_create_with_catalog(const char *relname,
 	/*
 	 * If there's a special on-commit action, remember it
 	 */
-	if (oncommit != ONCOMMIT_NOOP)
-		register_on_commit_action(relid, oncommit);
+	if (oncommit != ONCOMMIT_NOOP &&
+		relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+		register_on_commit_action(relid, oncommit, false);
 
 	/*
 	 * ok, the relation has been cataloged, so close our relations and return
@@ -1993,6 +2027,19 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/*
+	 * Only when other sessions are not using this Global temporary table,
+	 * is it allowed to DROP it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot drop global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3277,7 +3324,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3289,7 +3336,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3325,7 +3372,7 @@ RelationTruncateIndexes(Relation heapRelation)
  * ON COMMIT truncation of temporary tables, where it doesn't matter.
  */
 void
-heap_truncate(List *relids)
+heap_truncate(List *relids, bool is_global_temp)
 {
 	List	   *relations = NIL;
 	ListCell   *cell;
@@ -3335,8 +3382,23 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode;
+
+		/*
+		 * Truncate global temporary table only clears local data,
+		 * so only low-level locks need to be held.
+		 */
+		if (is_global_temp)
+		{
+			if (!gtt_storage_attached(rid))
+				continue;
+
+			lockmode = RowExclusiveLock;
+		}
+		else
+			lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3369,6 +3431,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3377,23 +3440,39 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	/*
+	 * Truncate GTT only clears local data, so only low-level locks
+	 * need to be held.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		lockmode = AccessShareLock;
+	else
+		lockmode = AccessExclusiveLock;
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	/*
+	 * After the data is cleaned up on the GTT, the transaction information
+	 * for the data(stored in local hash table) is also need reset.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(RelationGetRelid(rel), 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 26bfa74ce75..a957b43dd65 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -54,6 +54,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"
@@ -732,6 +733,29 @@ index_create(Relation heapRelation,
 	char		relkind;
 	TransactionId relfrozenxid;
 	MultiXactId relminmxid;
+	bool		skip_create_storage = false;
+
+	/* For global temporary table only */
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temporary table with concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot reindex global temporary tables concurrently")));
+
+		/*
+		 * For the case that some backend is applied relcache message to create
+		 * an index on a global temporary table, if this table in the current
+		 * backend are not initialized, the creation of index storage on the
+		 * table are also skipped.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+		{
+			skip_create_storage = true;
+			flags |= INDEX_CREATE_SKIP_BUILD;
+		}
+	}
 
 	/* constraint flags can only be set when a constraint is requested */
 	Assert((constr_flags == 0) ||
@@ -939,7 +963,8 @@ index_create(Relation heapRelation,
 								mapped_relation,
 								allow_system_table_mods,
 								&relfrozenxid,
-								&relminmxid);
+								&relminmxid,
+								skip_create_storage);
 
 	Assert(relfrozenxid == InvalidTransactionId);
 	Assert(relminmxid == InvalidMultiXactId);
@@ -2107,7 +2132,7 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 * lock (see comments in RemoveRelations), and a non-concurrent DROP is
 	 * more efficient.
 	 */
-	Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+	Assert(!RelpersistenceTsTemp(get_rel_persistence(indexId)) ||
 		   (!concurrent && !concurrent_lock_mode));
 
 	/*
@@ -2139,6 +2164,21 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/*
+	 * Allow to drop index on global temporary table when only current
+	 * backend use it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation) &&
+		is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+	{
+		ereport(ERROR,
+			(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+			 errmsg("cannot drop index %s on global temporary table %s",
+					RelationGetRelationName(userIndexRelation), RelationGetRelationName(userHeapRelation)),
+					errdetail("Because the index is created on the global temporary table and other backend attached it."),
+					errhint("Please try detach all sessions using this temporary table and try again.")));
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2747,6 +2787,7 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = RELATION_IS_GLOBAL_TEMP(rel);
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2841,20 +2882,37 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
+		/* For global temporary table */
+		if (is_gtt)
 		{
-			rd_rel->relpages = (int32) relpages;
-			dirty = true;
-		}
-		if (rd_rel->reltuples != (float4) reltuples)
-		{
-			rd_rel->reltuples = (float4) reltuples;
-			dirty = true;
+			/* Update GTT'statistics into local relcache */
+			rel->rd_rel->relpages = (int32) relpages;
+			rel->rd_rel->reltuples = (float4) reltuples;
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+
+			/* Update GTT'statistics into local hashtable */
+			up_gtt_relstats(RelationGetRelid(rel), relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+		else
 		{
-			rd_rel->relallvisible = (int32) relallvisible;
-			dirty = true;
+			if (rd_rel->relpages != (int32) relpages)
+			{
+				rd_rel->relpages = (int32) relpages;
+				dirty = true;
+			}
+
+			if (rd_rel->reltuples != (float4) reltuples)
+			{
+				rd_rel->reltuples = (float4) reltuples;
+				dirty = true;
+			}
+
+			if (rd_rel->relallvisible != (int32) relallvisible)
+			{
+				rd_rel->relallvisible = (int32) relallvisible;
+				dirty = true;
+			}
 		}
 	}
 
@@ -2967,6 +3025,26 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, progress_index, progress_vals);
 	}
 
+	/* For build index on global temporary table */
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		/*
+		 * If the storage for the index in this session is not initialized,
+		 * it needs to be created.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			/* Before create init storage, fix the local Relcache first */
+			force_enable_gtt_index(indexRelation);
+
+			Assert(gtt_storage_attached(RelationGetRelid(heapRelation)));
+
+			/* Init storage for index */
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3508,6 +3586,8 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	PGRUsage	ru0;
 	bool		progress = ((params->options & REINDEXOPT_REPORT_PROGRESS) != 0);
 	bool		set_tablespace = false;
+	LOCKMODE	lockmode_on_heap = ShareLock;
+	LOCKMODE	lockmode_on_index = AccessExclusiveLock;
 
 	pg_rusage_init(&ru0);
 
@@ -3521,10 +3601,29 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	if (!OidIsValid(heapId))
 		return;
 
+	/*
+	 * For reindex on global temporary table, If the storage for the index
+	 * in current backend is not initialized, nothing is done.
+	 */
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		if (!gtt_storage_attached(indexId))
+		{
+			/* Suppress use of the target index while rebuilding it */
+			SetReindexProcessing(heapId, indexId);
+			/* Re-allow use of target index */
+			ResetReindexProcessing();
+			return;
+		}
+
+		lockmode_on_heap = AccessShareLock;
+		lockmode_on_index = AccessShareLock;
+	}
+
 	if ((params->options & REINDEXOPT_MISSING_OK) != 0)
-		heapRelation = try_table_open(heapId, ShareLock);
+		heapRelation = try_table_open(heapId, lockmode_on_heap);
 	else
-		heapRelation = table_open(heapId, ShareLock);
+		heapRelation = table_open(heapId, lockmode_on_heap);
 
 	/* if relation is gone, leave */
 	if (!heapRelation)
@@ -3550,7 +3649,7 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	 * Open the target index relation and get an exclusive lock on it, to
 	 * ensure that no one else is touching this particular index.
 	 */
-	iRel = index_open(indexId, AccessExclusiveLock);
+	iRel = index_open(indexId, lockmode_on_index);
 
 	if (progress)
 		pgstat_progress_update_param(PROGRESS_CREATEIDX_ACCESS_METHOD_OID,
@@ -3801,6 +3900,12 @@ reindex_relation(Oid relid, int flags, ReindexParams *params)
 	bool		result;
 	ListCell   *indexId;
 	int			i;
+	LOCKMODE	lockmode;
+
+	if (flags & REINDEX_REL_PROCESS_GLOBAL_TEMP)
+		lockmode = AccessShareLock;
+	else
+		lockmode = ShareLock;
 
 	/*
 	 * Open and lock the relation.  ShareLock is sufficient since we only need
@@ -3808,9 +3913,9 @@ reindex_relation(Oid relid, int flags, ReindexParams *params)
 	 * should match ReindexTable().
 	 */
 	if ((params->options & REINDEXOPT_MISSING_OK) != 0)
-		rel = try_table_open(relid, ShareLock);
+		rel = try_table_open(relid, lockmode);
 	else
-		rel = table_open(relid, ShareLock);
+		rel = table_open(relid, lockmode);
 
 	/* if relation is gone, leave */
 	if (!rel)
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 4de8400fd0f..fe3fcc712cb 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -656,6 +656,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/* Do not allow create global temporary table in temporary schemas */
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp table in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index c5ad28d71fe..707068a6fd8 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,6 +27,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -61,6 +62,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			temprelOid;			/* InvalidOid if not a global temporary rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -115,7 +117,7 @@ AddPendingSync(const RelFileNode *rnode)
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -126,7 +128,12 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/*
+		 * Global temporary table and local temporary table use same
+		 * design on storage module.
+		 */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -154,6 +161,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
@@ -165,6 +173,21 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		AddPendingSync(&rnode);
 	}
 
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		Assert(rel && RELATION_IS_GLOBAL_TEMP(rel));
+
+		/*
+		 * Remember the reloid of global temporary table, which is used for
+		 * transaction commit or rollback.
+		 * see smgrDoPendingDeletes.
+		 */
+		pending->temprelOid = RelationGetRelid(rel);
+
+		/* Remember global temporary table storage info to localhash */
+		remember_gtt_storage_info(rnode, rel);
+	}
+
 	return srel;
 }
 
@@ -201,11 +224,20 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/*
+	 * Remember the reloid of global temporary table, which is used for
+	 * transaction commit or rollback.
+	 * see smgrDoPendingDeletes.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->temprelOid = RelationGetRelid(rel);
+
 	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
@@ -618,6 +650,7 @@ smgrDoPendingDeletes(bool isCommit)
 	int			nrels = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -647,14 +680,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->temprelOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -664,12 +701,21 @@ smgrDoPendingDeletes(bool isCommit)
 
 	if (nrels > 0)
 	{
+		int	i;
+
 		smgrdounlinkall(srels, nrels, false);
 
-		for (int i = 0; i < nrels; i++)
+		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* Delete global temporary table info in localhash */
+			if (gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 00000000000..384b4b07af1
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1573 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  The body implementation of Global Temparary table.
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *	  See src/backend/catalog/GTT_README for Global temparary table's
+ *	  requirements and design.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_info_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+int		vacuum_gtt_defer_check_age = 0;
+
+/*
+ * The Global temporary table's shared hash table data structure
+ */
+typedef struct gtt_ctl_data
+{
+	LWLock		lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+/* record this global temporary table in which backends are being used */
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+/*
+ * The Global temporary table's local hash table data structure
+ */
+/* Record the storage information and statistical information of the global temporary table */
+typedef struct
+{
+	Oid			relfilenode;
+	Oid			spcnode;
+
+	/* pg_class relstat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+
+	/* pg_statistic column stat */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_free_statistics(gtt_relfilenode *rnode);
+static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok);
+static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok);
+static Bitmapset *copy_active_gtt_bitmap(Oid relid);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+/*
+ * Calculate shared hash table entry size for GTT.
+ */
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	/* hash entry header size */
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	/*
+	 * hash entry data size
+	 * this is a bitmap in shared memory, each backend have a bit.
+	 */
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+/*
+ * Calculate shared hash table max size for GTT.
+ */
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	/* shared hash header size */
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	/* hash entry size */
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	/* max size */
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+/*
+ * Initialization shared hash table for GTT.
+ */
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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();
+	}
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(gtt_fnode);
+	info.entrysize = action_gtt_shared_hash_entry_size();
+	active_gtt_shared_hash =
+		ShmemInitHash("active gtt shared hash",
+						gtt_shared_ctl->max_entry,
+						gtt_shared_ctl->max_entry,
+						&info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+/*
+ * Record GTT relid to shared hash table, which means that current backend is using this GTT.
+ */
+static void
+gtt_storage_checkin(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			found;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *)&(fnode), 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_global_temporary_table.")));
+	}
+
+	if (!found)
+	{
+		int			wordnum;
+
+		/* init bitmap */
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	/* record itself in bitmap */
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+/*
+ * Remove the GTT relid record from the shared hash table which means that current backend is
+ * not use this GTT.
+ */
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	if (!skiplock)
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		if (!skiplock)
+			LWLockRelease(&gtt_shared_ctl->lock);
+
+		if (isCommit)
+			elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+	/* remove itself from bitmap */
+	bms_del_member(entry->map, MyBackendId);
+
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+
+	if (!skiplock)
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+	return;
+}
+
+/*
+ * Gets usage information for a GTT from shared hash table.
+ * The information is in the form of bitmap.
+ * Quickly copy the entire bitmap from shared memory and return it.
+ * that to avoid holding locks for a long time.
+ */
+static Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	Bitmapset		*map_copy = NULL;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		return NULL;
+	}
+
+	Assert(entry->map);
+
+	/* copy the entire bitmap */
+	if (!bms_is_empty(entry->map))
+		map_copy = bms_copy(entry->map);
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return map_copy;
+}
+
+/*
+ * Check if there are other backends using this GTT besides the current backend.
+ */
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			in_use = false;
+	int			num_use = 0;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		return false;
+	}
+
+	Assert(entry->map);
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+	/* how many backend are using this GTT */
+	num_use = bms_num_members(entry->map);
+	if (num_use == 0)
+		in_use = false;
+	else if (num_use == 1)
+	{
+		/* check if this is itself */
+		if(bms_is_member(MyBackendId, entry->map))
+			in_use = false;
+		else
+			in_use = true;
+	}
+	else
+		in_use = true;
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return in_use;
+}
+
+/*
+ * Record GTT information to local hash.
+ * They include GTT storage info, transaction info and statistical info.
+ */
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+	gtt_local_hash_entry		*entry;
+	MemoryContext			oldcontext;
+	gtt_relfilenode			*new_node = NULL;
+	Oid				relid = RelationGetRelid(rel);
+	int				natts = 0;
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	/* First time through: initialize the hash table */
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		HASHCTL		ctl;
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_info_context =
+			AllocSetContextCreate(CacheMemoryContext,
+								"gtt info context",
+								ALLOCSET_DEFAULT_SIZES);
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		ctl.hcxt = gtt_info_context;
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+	}
+
+	Assert(CacheMemoryContext);
+	Assert(gtt_info_context);
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	entry = gtt_search_by_relid(relid, true);
+	if (!entry)
+	{
+		bool		found = false;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash,
+						(void *) &relid, HASH_ENTER, &found);
+
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			/* record the on commit clause */
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS, true);
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			gtt_storage_checkin(relid);
+		}
+	}
+
+	/* record storage info relstat columnstats and transaction info to relfilenode list */
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	new_node->natts = 0;
+	new_node->attnum = NULL;
+	new_node->att_stat_tups = NULL;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* init column stats structure */
+	natts = RelationGetNumberOfAttributes(rel);
+	new_node->attnum = palloc0(sizeof(int) * natts);
+	new_node->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+	new_node->natts = natts;
+
+	/* only heap have transaction info */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+
+		/**/
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+
+	/* Registration callbacks are used to trigger cleanup during process exit */
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+/*
+ * Remove GTT information from local hash when transaction commit/rollback.
+ */
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	gtt_relfilenode		*d_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+		return;
+	}
+
+	d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+		else
+		{
+			/* rollback transaction */
+			if (entry->relfilenode_list == NIL)
+			{
+				if (entry->relkind == RELKIND_RELATION ||
+					entry->relkind == RELKIND_SEQUENCE)
+					gtt_storage_checkout(relid, false, isCommit);
+
+				hash_search(gtt_storage_local_hash,
+						(void *) &(relid), HASH_REMOVE, NULL);
+			}
+
+			return;
+		}
+	}
+
+	/* Clean up transaction info from Local order list and MyProc */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+
+		/* this is valid relfrozenxid */
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	/* delete relfilenode from rel entry */
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	gtt_free_statistics(d_rnode);
+
+	if (entry->relfilenode_list == NIL)
+	{
+		/* this means we truncate this GTT at current backend */
+
+		/* tell shared hash that current backend will no longer use this GTT */
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(relid, false, isCommit);
+
+		hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_REMOVE, NULL);
+	}
+
+	return;
+}
+
+/*
+ * Check if current backend is using this GTT.
+ */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool			found = false;
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (!OidIsValid(relid))
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry)
+		found = true;
+
+	return found;
+}
+
+/*
+ * When backend exit, bulk cleaning all GTT storage and local buffer of this backend.
+ */
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS		status;
+	gtt_local_hash_entry	*entry;
+	SMgrRelation		*srels = NULL;
+	Oid			*relids = NULL;
+	char			*relkinds = NULL;
+	int			nrels = 0,
+				nfiles = 0,
+				maxrels = 0,
+				maxfiles = 0,
+				i = 0;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	/* Search all relfilenode for GTT in current backend */
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel;
+			RelFileNode		rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel = smgropen(rnode, MyBackendId);
+
+			if (maxfiles == 0)
+			{
+				maxfiles = 32;
+				srels = palloc(sizeof(SMgrRelation) * maxfiles);
+			}
+			else if (maxfiles <= nfiles)
+			{
+				maxfiles *= 2;
+				srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+			}
+
+			srels[nfiles++] = srel;
+		}
+
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			relids  = palloc(sizeof(Oid) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			relids  = repalloc(relids , sizeof(Oid) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		relkinds[nrels] = entry->relkind;
+		relids[nrels] = entry->relid;
+		nrels++;
+	}
+
+	/* drop local buffer and storage */
+	if (nfiles > 0)
+	{
+		/* Need to ensure we have a usable transaction. */
+                AbortOutOfAnyTransaction();
+
+		smgrdounlinkall(srels, nfiles, false);
+		for (i = 0; i < nfiles; i++)
+			smgrclose(srels[i]);
+
+		pfree(srels);
+	}
+
+	if (nrels)
+	{
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+		for (i = 0; i < nrels; i++)
+		{
+			/* tell shared hash */
+			if (relkinds[i] == RELKIND_RELATION ||
+				relkinds[i] == RELKIND_SEQUENCE)
+				gtt_storage_checkout(relids[i], true, false);
+		}
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+		pfree(relids);
+		pfree(relkinds);
+	}
+
+	/* set to global area */
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;
+
+	return;
+}
+
+/*
+ * Update GTT relstats(relpage/reltuple/relallvisible)
+ * to local hash.
+ */
+void
+up_gtt_relstats(Oid relid,
+					BlockNumber num_pages,
+					double num_tuples,
+					BlockNumber num_all_visible_pages,
+					TransactionId relfrozenxid,
+					TransactionId relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (!OidIsValid(relid))
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (num_pages > 0 &&
+		gtt_rnode->relpages != (int32)num_pages)
+		gtt_rnode->relpages = (int32)num_pages;
+
+	if (num_tuples > 0 &&
+		gtt_rnode->reltuples != (float4)num_tuples)
+		gtt_rnode->reltuples = (float4)num_tuples;
+
+	/* only heap contain transaction information and relallvisible */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (num_all_visible_pages > 0 &&
+			gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+		{
+			gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+		}
+
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNextTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			/* set to local order list */
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			/* set to global area */
+			set_gtt_session_relfrozenxid();
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search GTT relstats(relpage/reltuple/relallvisible)
+ * from local has.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update GTT info(definition is same as pg_statistic)
+ * to local hash.
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	MemoryContext		oldcontext;
+	int			i = 0;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return;
+
+	Assert(entry->relid == reloid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (gtt_rnode->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	/* switch context to gtt_info_context for store tuple at heap_form_tuple */
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == 0)
+		{
+			gtt_rnode->attnum[i] = attnum;
+			break;
+		}
+		else if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			heap_freetuple(gtt_rnode->att_stat_tups[i]);
+			gtt_rnode->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < gtt_rnode->natts);
+	Assert(gtt_rnode->att_stat_tups[i] == NULL);
+	gtt_rnode->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search GTT statistic info(definition is same as pg_statistic)
+ * from local hash.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	int			i = 0;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return NULL;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return NULL;
+
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			return gtt_rnode->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Insert a RelfrozenXID into the list and keep the list in order.
+ */
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int		i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Remove a RelfrozenXID from order list gtt_session_relfrozenxid_list.
+ */
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+/*
+ * Update of backend Level oldest relfrozenxid to MyProc.
+ * This makes each backend's oldest RelFrozenxID globally visible.
+ */
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->backend_gtt_frozenxid != gtt_frozenxid)
+		MyProc->backend_gtt_frozenxid = gtt_frozenxid;
+}
+
+/*
+ * Get GTT column level data statistics.
+ */
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate		*tupstore;
+	HeapTuple		tuple;
+	Relation		rel = NULL;
+	Oid			reloid = PG_GETARG_OID(0);
+	int			attnum = PG_GETARG_INT32(1);
+	char			rel_persistence;
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	/* get data from local hash */
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum		values[31];
+		bool		isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, NoLock);
+	relation_close(pg_tatistic, AccessShareLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get GTT table level data statistics.
+ */
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate	*tupstore;
+	TupleDesc	tupdesc;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid			reloid = PG_GETARG_OID(0);
+	Oid			relnode = 0;
+	char			rel_persistence;
+	BlockNumber		relpages = 0;
+	BlockNumber		relallvisible = 0;
+	uint32			relfrozenxid = 0;
+	uint32			relminmxid = 0;
+	double			reltuples = 0;
+	Relation		rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[6];
+		bool	isnull[6];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get a list of backend pids that are currently using this GTT.
+ */
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	PGPROC			*proc = NULL;
+	Bitmapset		*map = NULL;
+	Tuplestorestate		*tupstore;
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	HeapTuple		tuple;
+	Oid			reloid = PG_GETARG_OID(0);
+	char			rel_persistence;
+	Relation		rel = NULL;
+	pid_t			pid = 0;
+	int				backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	/* get data from share hash */
+	map = copy_active_gtt_bitmap(reloid);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			/* backendid map to process pid */
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get backend level oldest relfrozenxid of each backend using GTT in current database.
+ */
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate		*tupstore;
+	int			*pids = NULL;
+	uint32			*xids = NULL;
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	HeapTuple		tuple;
+	int			num_xid = MaxBackends + 1;
+	int			i = 0;
+	int			j = 0;
+	uint32			oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+
+	/* Get backend level oldest relfrozenxid in all backend that in MyDatabaseId use GTT */
+	oldest = list_all_backend_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+/*
+ * In order to build the GTT index, force enable GTT'index.
+ */
+void
+force_enable_gtt_index(Relation index)
+{
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(RelationGetRelid(index)));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+/*
+ * Fix the local state of the GTT's index.
+ */
+void
+gtt_fix_index_backend_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid heapOid = index->rd_index->indrelid;
+
+	/* Must be GTT */
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	/*
+	 * If this GTT is not initialized in the current backend,
+	 * its index status is temporarily set to invalid(local relcache).
+	 */
+	if (gtt_storage_attached(heapOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+/*
+ * During the SQL initialization of the executor (InitPlan)
+ * Initialize storage of GTT GTT'indexes and build empty index.
+ */
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int		i;
+	Oid		toastrelid;
+
+	if (!(operation == CMD_UTILITY || operation == CMD_INSERT))
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	/* Each GTT is initialized once in each backend */
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	/* init heap storage */
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+
+		index_build(relation, index, info, true, false);
+	}
+
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+
+		/* init index storage */
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid			indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+			/* build empty index */
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+/*
+ * Release the data structure memory used to store GTT storage info.
+ */
+static void
+gtt_free_statistics(gtt_relfilenode *rnode)
+{
+	int i;
+
+	Assert(rnode);
+
+	for (i = 0; i < rnode->natts; i++)
+	{
+		if (rnode->att_stat_tups[i])
+		{
+			heap_freetuple(rnode->att_stat_tups[i]);
+			rnode->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (rnode->attnum)
+		pfree(rnode->attnum);
+
+	if (rnode->att_stat_tups)
+		pfree(rnode->att_stat_tups);
+
+	pfree(rnode);
+
+	return;
+}
+
+/*
+ * Get the current relfilenode of this GTT.
+ */
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
+/*
+ * Get a relfilenode used by this GTT during the transaction life cycle.
+ */
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok)
+{
+	gtt_relfilenode		*rnode = NULL;
+	ListCell		*lc;
+
+	Assert(entry);
+
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == relfilenode)
+		{
+			rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (!missing_ok && rnode == NULL)
+		elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+	return rnode;
+}
+
+/*
+ * Get one GTT info from local hash.
+ */
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry == NULL && !missing_ok)
+		elog(ERROR, "relid %u not found in local hash", relid);
+
+	return entry;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 55f6e3711d8..fc75533263b 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 8bfb2ad9584..500305e8ae8 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -104,7 +105,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -185,6 +186,17 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -601,14 +613,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]));
 		}
 
 		/*
@@ -1633,7 +1646,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;
@@ -1735,31 +1748,48 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/*
+		 * For global temporary table,
+		 * Update column statistic to localhash, not catalog.
+		 */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 9d22f648a84..a44eefa1f6b 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/toasting.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
@@ -390,6 +391,22 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+	{
+		if (gtt_storage_attached(RelationGetRelid(OldHeap)))
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("not support cluster global temporary table yet")));
+
+		relation_close(OldHeap, AccessExclusiveLock);
+		pgstat_progress_end_command();
+		return;
+	}
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
@@ -585,6 +602,8 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
 	TransactionId frozenXid;
 	MultiXactId cutoffMulti;
 
+	Assert(!RELATION_IS_GLOBAL_TEMP(OldHeap));
+
 	/* Mark the correct index as clustered */
 	if (OidIsValid(indexOid))
 		mark_index_clustered(OldHeap, indexOid, true);
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 53f48531419..c03191cce94 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -289,7 +289,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP_ON_CURRENT_SESSION(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, whereClause,
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 40a54ad0bd7..7895e7d99b9 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -30,6 +30,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/copyfrom_internal.h"
 #include "commands/progress.h"
@@ -659,6 +660,9 @@ CopyFrom(CopyFromState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	/* Check and init global temporary table storage in current backend */
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	/*
 	 * Set up a ModifyTableState so we can let FDW(s) init themselves for
 	 * foreign-table result relation(s).
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index c14ca27c5ed..d65ae895356 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -111,6 +111,7 @@ struct ReindexIndexCallbackState
 {
 	ReindexParams params;		/* options from statement */
 	Oid			locked_table_oid;	/* tracks previously locked table */
+	LOCKMODE	lockmode;
 };
 
 /*
@@ -570,7 +571,7 @@ DefineIndex(Oid relationId,
 	 * is more efficient.  Do this before any use of the concurrent option is
 	 * done.
 	 */
-	if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+	if (stmt->concurrent && !RelpersistenceTsTemp(get_rel_persistence(relationId)))
 		concurrent = true;
 	else
 		concurrent = false;
@@ -2581,9 +2582,9 @@ ReindexIndex(RangeVar *indexRelation, ReindexParams *params, bool isTopLevel)
 	 */
 	state.params = *params;
 	state.locked_table_oid = InvalidOid;
+	state.lockmode = AccessShareLock;
 	indOid = RangeVarGetRelidExtended(indexRelation,
-									  (params->options & REINDEXOPT_CONCURRENTLY) != 0 ?
-									  ShareUpdateExclusiveLock : AccessExclusiveLock,
+									  AccessShareLock,
 									  0,
 									  RangeVarCallbackForReindexIndex,
 									  &state);
@@ -2594,11 +2595,25 @@ ReindexIndex(RangeVar *indexRelation, ReindexParams *params, bool isTopLevel)
 	 */
 	persistence = get_rel_persistence(indOid);
 	relkind = get_rel_relkind(indOid);
+	if (persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		LOCKMODE	lockmode;
+
+		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0)
+			lockmode = ShareUpdateExclusiveLock;
+		else
+			lockmode = AccessExclusiveLock;
+
+		/* lock heap first */
+		Assert(OidIsValid(state.locked_table_oid));
+		LockRelationOid(state.locked_table_oid, lockmode);
+		LockRelationOid(indOid, lockmode);
+	}
 
 	if (relkind == RELKIND_PARTITIONED_INDEX)
 		ReindexPartitions(indOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 persistence != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(persistence))
 		ReindexRelationConcurrently(indOid, params);
 	else
 	{
@@ -2620,15 +2635,7 @@ RangeVarCallbackForReindexIndex(const RangeVar *relation,
 {
 	char		relkind;
 	struct ReindexIndexCallbackState *state = arg;
-	LOCKMODE	table_lockmode;
-
-	/*
-	 * Lock level here should match table lock in reindex_index() for
-	 * non-concurrent case and table locks used by index_concurrently_*() for
-	 * concurrent case.
-	 */
-	table_lockmode = (state->params.options & REINDEXOPT_CONCURRENTLY) != 0 ?
-		ShareUpdateExclusiveLock : ShareLock;
+	LOCKMODE	table_lockmode = state->lockmode;
 
 	/*
 	 * If we previously locked some other index's heap, and the name we're
@@ -2689,6 +2696,8 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 {
 	Oid			heapOid;
 	bool		result;
+	char		relpersistence;
+	int 		reindex_flags = 0;
 
 	/*
 	 * The lock level used here should match reindex_relation().
@@ -2699,15 +2708,27 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 	 * locks on our temporary table.
 	 */
 	heapOid = RangeVarGetRelidExtended(relation,
-									   (params->options & REINDEXOPT_CONCURRENTLY) != 0 ?
-									   ShareUpdateExclusiveLock : ShareLock,
+									   AccessShareLock,
 									   0,
 									   RangeVarCallbackOwnsTable, NULL);
 
+	relpersistence = get_rel_persistence(heapOid);
+	if (relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		LOCKMODE	lockmode;
+
+		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0)
+			lockmode = ShareUpdateExclusiveLock;
+		else
+			lockmode = ShareLock;
+
+		LockRelationOid(heapOid, lockmode);
+	}
+
 	if (get_rel_relkind(heapOid) == RELKIND_PARTITIONED_TABLE)
 		ReindexPartitions(heapOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(relpersistence))
 	{
 		result = ReindexRelationConcurrently(heapOid, params);
 
@@ -2721,9 +2742,14 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 		ReindexParams newparams = *params;
 
 		newparams.options |= REINDEXOPT_REPORT_PROGRESS;
+
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			reindex_flags |= REINDEX_REL_PROCESS_GLOBAL_TEMP;
+
+		reindex_flags |= REINDEX_REL_PROCESS_TOAST;
+		reindex_flags |= REINDEX_REL_CHECK_CONSTRAINTS;
 		result = reindex_relation(heapOid,
-								  REINDEX_REL_PROCESS_TOAST |
-								  REINDEX_REL_CHECK_CONSTRAINTS,
+								  reindex_flags,
 								  &newparams);
 		if (!result)
 			ereport(NOTICE,
@@ -3122,7 +3148,7 @@ ReindexMultipleInternal(List *relids, ReindexParams *params)
 			   relkind != RELKIND_PARTITIONED_TABLE);
 
 		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			relpersistence != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(relpersistence))
 		{
 			ReindexParams newparams = *params;
 
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 62465bacd81..ef37f79ba68 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -51,12 +51,32 @@ LockTableCommand(LockStmt *lockstmt)
 		RangeVar   *rv = (RangeVar *) lfirst(p);
 		bool		recurse = rv->inh;
 		Oid			reloid;
+		LOCKMODE	lockmode = lockstmt->mode;
+		char		relpersistence;
 
-		reloid = RangeVarGetRelidExtended(rv, lockstmt->mode,
-										  lockstmt->nowait ? RVR_NOWAIT : 0,
+		reloid = RangeVarGetRelidExtended(rv, NoLock, 0,
 										  RangeVarCallbackForLockTable,
 										  (void *) &lockstmt->mode);
 
+		relpersistence = get_rel_persistence(reloid);
+		if (relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			if (!lockstmt->nowait)
+				LockRelationOid(reloid, lockmode);
+			else if (!ConditionalLockRelationOid(reloid, lockmode))
+			{
+				/* try to throw error by name; relation could be deleted... */
+				char	   *relname = get_rel_name(reloid);
+
+				if (!relname)
+					return;		/* child concurrently dropped, just skip it */
+				ereport(ERROR,
+						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						 errmsg("could not obtain lock on relation \"%s\"",
+								relname)));
+			}
+		}
+
 		if (get_rel_relkind(reloid) == RELKIND_VIEW)
 			LockViewRecurse(reloid, lockstmt->mode, lockstmt->nowait, NIL);
 		else if (recurse)
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 72bfdc07a49..9849f6bce64 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -220,9 +223,12 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	rel = table_open(seqoid, AccessExclusiveLock);
 	tupDesc = RelationGetDescr(rel);
 
-	/* now initialize the sequence's data */
-	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	if (!RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		/* now initialize the sequence's data */
+		tuple = heap_form_tuple(tupDesc, value, null);
+		fill_seq_with_data(rel, tuple);
+	}
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -275,8 +281,6 @@ ResetSequence(Oid seq_relid)
 	Buffer		buf;
 	HeapTupleData seqdatatuple;
 	HeapTuple	tuple;
-	HeapTuple	pgstuple;
-	Form_pg_sequence pgsform;
 	int64		startv;
 
 	/*
@@ -287,12 +291,7 @@ ResetSequence(Oid seq_relid)
 	init_sequence(seq_relid, &elm, &seq_rel);
 	(void) read_seq_tuple(seq_rel, &buf, &seqdatatuple);
 
-	pgstuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seq_relid));
-	if (!HeapTupleIsValid(pgstuple))
-		elog(ERROR, "cache lookup failed for sequence %u", seq_relid);
-	pgsform = (Form_pg_sequence) GETSTRUCT(pgstuple);
-	startv = pgsform->seqstart;
-	ReleaseSysCache(pgstuple);
+	startv = get_seqence_start_value(seq_relid);
 
 	/*
 	 * Copy the existing sequence tuple.
@@ -451,6 +450,15 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 
 	init_sequence(relid, &elm, &seqrel);
 
+	if (RELATION_IS_GLOBAL_TEMP(seqrel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(seqrel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary sequence %s when other backend attached it.",
+						RelationGetRelationName(seqrel))));
+	}
+
 	rel = table_open(SequenceRelationId, RowExclusiveLock);
 	seqtuple = SearchSysCacheCopy1(SEQRELID,
 								   ObjectIdGetDatum(relid));
@@ -611,7 +619,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +944,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1153,6 +1161,14 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	/* Initializes the storage for sequence which the global temporary table belongs. */
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1927,3 +1943,51 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+/*
+ * Get the startValue of the sequence from syscache.
+ */
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+/*
+ * Initialize sequence which global temporary table belongs.
+ */
+void
+gtt_init_seq(Relation rel)
+{
+	Datum		value[SEQ_COL_LASTCOL] = {0};
+	bool		null[SEQ_COL_LASTCOL] = {false};
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index dbee6ae199f..a5629506e20 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -118,6 +119,7 @@ typedef struct OnCommitItem
 	 */
 	SubTransactionId creating_subid;
 	SubTransactionId deleting_subid;
+	bool			 is_global_temp;
 } OnCommitItem;
 
 static List *on_commits = NIL;
@@ -602,7 +604,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
 static char GetAttributeCompression(Oid atttypid, char *compression);
-
+static OnCommitAction gtt_oncommit_option(List *options);
 
 /* ----------------------------------------------------------------
  *		DefineRelation
@@ -647,6 +649,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -658,7 +661,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * Check consistency of arguments
 	 */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !RelpersistenceTsTemp(stmt->relation->relpersistence))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -688,7 +691,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	if (RelpersistenceTsTemp(stmt->relation->relpersistence)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -789,6 +792,50 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* For global temporary table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		if (!(relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE))
+			elog(ERROR, "Only support global temporary regular table.");
+
+		/* Check parent table */
+		if (inheritOids)
+			elog(ERROR, "Not support global temporary partition table or inherit table.");
+
+		/* Check oncommit clause and save to reloptions */
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temporary table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1415,7 +1462,7 @@ RemoveRelations(DropStmt *drop)
 		 * relation persistence cannot be known without its OID.
 		 */
 		if (drop->concurrent &&
-			get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(get_rel_persistence(relOid)))
 		{
 			Assert(list_length(drop->objects) == 1 &&
 				   drop->removeType == OBJECT_INDEX);
@@ -1624,9 +1671,9 @@ ExecuteTruncate(TruncateStmt *stmt)
 		Relation	rel;
 		bool		recurse = rv->inh;
 		Oid			myrelid;
-		LOCKMODE	lockmode = AccessExclusiveLock;
+		LOCKMODE	lockmode;
 
-		myrelid = RangeVarGetRelidExtended(rv, lockmode,
+		myrelid = RangeVarGetRelidExtended(rv, AccessShareLock,
 										   0, RangeVarCallbackForTruncate,
 										   NULL);
 
@@ -1634,9 +1681,21 @@ ExecuteTruncate(TruncateStmt *stmt)
 		if (list_member_oid(relids, myrelid))
 			continue;
 
-		/* open the relation, we already hold a lock on it */
+		/* open the relation, we need hold a low-level lock first */
 		rel = table_open(myrelid, NoLock);
 
+		/*
+		 * Truncate global temp table only cleans up the data in current backend,
+		 * only low-level locks are required.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel))
+			lockmode = AccessShareLock;
+		else
+		{
+			lockmode = AccessExclusiveLock;
+			LockRelationOid(myrelid, lockmode);
+		}
+
 		/*
 		 * RangeVarGetRelidExtended() has done most checks with its callback,
 		 * but other checks with the now-opened Relation remain.
@@ -1886,6 +1945,7 @@ ExecuteTruncateGuts(List *explicit_rels,
 	foreach(cell, rels)
 	{
 		Relation	rel = (Relation) lfirst(cell);
+		LOCKMODE	lockmode;
 
 		/* Skip partitioned tables as there is nothing to do */
 		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
@@ -1936,6 +1996,19 @@ ExecuteTruncateGuts(List *explicit_rels,
 			continue;
 		}
 
+		/*
+		 * Skip the global temporary table that is not initialized for storage
+		 * in current backend.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel))
+		{
+			lockmode = AccessShareLock;
+			if (!gtt_storage_attached(RelationGetRelid(rel)))
+				continue;
+		}
+		else
+			lockmode = AccessExclusiveLock;
+
 		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
@@ -1954,6 +2027,7 @@ ExecuteTruncateGuts(List *explicit_rels,
 			Oid			heap_relid;
 			Oid			toast_relid;
 			ReindexParams reindex_params = {0};
+			int			reindex_flags = 0;
 
 			/*
 			 * This effectively deletes all rows in the table, and may be done
@@ -1981,17 +2055,21 @@ ExecuteTruncateGuts(List *explicit_rels,
 			if (OidIsValid(toast_relid))
 			{
 				Relation	toastrel = relation_open(toast_relid,
-													 AccessExclusiveLock);
+													 lockmode);
 
 				RelationSetNewRelfilenode(toastrel,
 										  toastrel->rd_rel->relpersistence);
 				table_close(toastrel, NoLock);
 			}
 
+			reindex_flags = REINDEX_REL_PROCESS_TOAST;
+			if (RELATION_IS_GLOBAL_TEMP(rel))
+				reindex_flags |= REINDEX_REL_PROCESS_GLOBAL_TEMP;
+
 			/*
 			 * Reconstruct the indexes to match, and we're done.
 			 */
-			reindex_relation(heap_relid, REINDEX_REL_PROCESS_TOAST,
+			reindex_relation(heap_relid, reindex_flags,
 							 &reindex_params);
 		}
 
@@ -3998,6 +4076,16 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temporary table only current backend use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -5329,6 +5417,24 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 
 			rel = table_open(tab->relid, NoLock);
 			find_composite_type_dependencies(rel->rd_rel->reltype, rel, NULL);
+
+			if (RELATION_IS_GLOBAL_TEMP(rel) && tab->rewrite > 0)
+			{
+				if (tab->chgPersistence)
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot change global temporary table persistence setting")));
+
+				if(gtt_storage_attached(tab->relid))
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("Only support alter global temporary table in an empty context."),
+						 errhint("Please create a new connection and execute ALTER TABLE on the new connection.")));
+
+				/* There is no need to override the whole temp table */
+				tab->rewrite = 0;
+			}
+
 			table_close(rel, NoLock);
 		}
 
@@ -5380,6 +5486,8 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot rewrite temporary tables of other sessions")));
 
+			Assert(!RELATION_IS_GLOBAL_TEMP(OldHeap));
+
 			/*
 			 * Select destination tablespace (same as original unless user
 			 * requested a change)
@@ -9011,6 +9119,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -13709,6 +13823,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -13908,6 +14025,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temporary table");
+
 	/* Check first if relation can be moved to new tablespace */
 	if (!CheckRelationTableSpaceMove(rel, newTableSpace))
 	{
@@ -14211,7 +14331,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(RelationGetSmgr(rel), dstrel, MAIN_FORKNUM,
@@ -15809,6 +15929,7 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
 	switch (rel->rd_rel->relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -16223,7 +16344,7 @@ AlterSeqNamespaces(Relation classRel, Relation rel,
  * Register a newly-created relation's ON COMMIT action.
  */
 void
-register_on_commit_action(Oid relid, OnCommitAction action)
+register_on_commit_action(Oid relid, OnCommitAction action, bool is_gloal_temp)
 {
 	OnCommitItem *oc;
 	MemoryContext oldcxt;
@@ -16242,6 +16363,7 @@ register_on_commit_action(Oid relid, OnCommitAction action)
 	oc->oncommit = action;
 	oc->creating_subid = GetCurrentSubTransactionId();
 	oc->deleting_subid = InvalidSubTransactionId;
+	oc->is_global_temp = is_gloal_temp;
 
 	/*
 	 * We use lcons() here so that ON COMMIT actions are processed in reverse
@@ -16287,6 +16409,7 @@ PreCommit_on_commit_actions(void)
 	ListCell   *l;
 	List	   *oids_to_truncate = NIL;
 	List	   *oids_to_drop = NIL;
+	List	   *oids_to_truncate_gtt = NIL;
 
 	foreach(l, on_commits)
 	{
@@ -16310,7 +16433,12 @@ PreCommit_on_commit_actions(void)
 				 * tables, as they must still be empty.
 				 */
 				if ((MyXactFlags & XACT_FLAGS_ACCESSEDTEMPNAMESPACE))
-					oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid);
+				{
+					if (oc->is_global_temp)
+						oids_to_truncate_gtt = lappend_oid(oids_to_truncate_gtt, oc->relid);
+					else
+						oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid);
+				}
 				break;
 			case ONCOMMIT_DROP:
 				oids_to_drop = lappend_oid(oids_to_drop, oc->relid);
@@ -16327,7 +16455,10 @@ PreCommit_on_commit_actions(void)
 	 * exists at truncation time.
 	 */
 	if (oids_to_truncate != NIL)
-		heap_truncate(oids_to_truncate);
+		heap_truncate(oids_to_truncate, false);
+
+	if (oids_to_truncate_gtt != NIL)
+		heap_truncate(oids_to_truncate_gtt, true);
 
 	if (oids_to_drop != NIL)
 	{
@@ -17326,6 +17457,13 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot attach temporary relation of another session as partition")));
 
+	/* If the parent is permanent, so must be all of its partitions. */
+	if (attachrel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach a global temporary relation as partition of permanent relation \"%s\"",
+						RelationGetRelationName(rel))));
+
 	/* Check if there are any columns in attachrel that aren't in the parent */
 	tupleDesc = RelationGetDescr(attachrel);
 	natts = tupleDesc->natts;
@@ -18761,3 +18899,40 @@ GetAttributeCompression(Oid atttypid, char *compression)
 
 	return cmethod;
 }
+
+/*
+ * Parse the on commit clause for the temporary table
+ */
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			bool	res = false;
+			char	*sval = defGetString(def);
+
+			/* It has to be a Boolean value */
+			if (!parse_bool(sval, &res))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
+
+			if (res)
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 5c4bc15b441..77c0f384ed1 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"
@@ -1315,6 +1316,22 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = RELATION_IS_GLOBAL_TEMP(relation);
+
+	 /* For global temporary table */
+	if (is_gtt)
+	{
+		/* Store relation statistics and transaction information to the localhash */
+		up_gtt_relstats(RelationGetRelid(relation),
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+
+		/* Update relation statistics to local relcache */
+		relation->rd_rel->relpages = (int32) num_pages;
+		relation->rd_rel->reltuples = (float4) num_tuples;
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1328,17 +1345,23 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (!is_gtt &&
+		pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (!is_gtt &&
+		pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (!is_gtt &&
+		pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1351,7 +1374,15 @@ vac_update_relstats(Relation relation,
 		/*
 		 * If we didn't find any indexes, reset relhasindex.
 		 */
-		if (pgcform->relhasindex && !hasindex)
+		if (is_gtt &&
+			RelationGetIndexList(relation) != NIL)
+		{
+			/*
+			 * Global temporary tables may contain indexes that are not valid locally.
+			 * The catalog should not be updated based on local invalid index.
+			 */
+		}
+		else if (pgcform->relhasindex && !hasindex)
 		{
 			pgcform->relhasindex = false;
 			dirty = true;
@@ -1383,7 +1414,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNextTransactionId(),
@@ -1394,7 +1426,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1502,6 +1535,13 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/*
+		 * The relfrozenxid for a global temporary talble is stored in localhash,
+		 * not pg_class, See list_all_session_gtt_frozenxids()
+		 */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1559,6 +1599,43 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/* If enable global temporary table */
+	if (max_active_gtt > 0)
+	{
+		TransactionId	safe_age;
+		/*  */
+		TransactionId	oldest_gtt_frozenxid =
+			list_all_backend_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid))
+		{
+			safe_age = oldest_gtt_frozenxid + vacuum_gtt_defer_check_age;
+			if (safe_age < FirstNormalTransactionId)
+				safe_age += FirstNormalTransactionId;
+
+			/*
+			 * We tolerate that the minimum age of gtt is less than
+			 * the minimum age of conventional tables, otherwise it will
+			 * throw warning message.
+			 */
+			if (TransactionIdIsNormal(safe_age) &&
+				TransactionIdPrecedes(safe_age, newFrozenXid))
+			{
+				ereport(WARNING,
+					(errmsg("global temp table oldest relfrozenxid %u is the oldest in the entire db",
+							oldest_gtt_frozenxid),
+					 errdetail("The oldest relfrozenxid in pg_class is %u", newFrozenXid),
+					 errhint("If they differ greatly, please consider cleaning up the data in global temp table.")));
+			}
+
+			/*
+			 * We need to ensure that the clog required by gtt is not cleand.
+			 */
+			if (TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+				newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1910,6 +1987,19 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	/*
+	 * Skip those global temporary table that are not initialized in
+	 * current backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel) &&
+		!gtt_storage_attached(RelationGetRelid(rel)))
+	{
+		relation_close(rel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 4df05a0b33d..4c181e2e14e 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -527,6 +527,12 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	/* Global temporary table are not sensible. */
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b3ce4bae530..611e3f18a70 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -784,6 +784,10 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		/* This is one kind of temp table */
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 5c723bc54e1..a7edceb1a5c 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
@@ -533,6 +534,9 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	/* Init storage for partitioned global temporary table in current backend */
+	init_gtt_storage(mtstate->operation, leaf_part_rri);
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d328856ae5b..4e2bbb224cc 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -38,6 +38,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -633,6 +634,9 @@ ExecInsert(ModifyTableState *mtstate,
 		resultRelInfo->ri_IndexRelationDescs == NULL)
 		ExecOpenIndices(resultRelInfo, onconflict != ONCONFLICT_NONE);
 
+	/* Init storage for global temporary table in current backend */
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	/*
 	 * BEFORE ROW INSERT Triggers.
 	 *
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 296dd75c1b6..d971aea2546 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -48,7 +48,7 @@
 #include "partitioning/partprune.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/lsyscache.h"
-
+#include "utils/rel.h"
 
 /* results of subquery_is_pushdown_safe */
 typedef struct pushdown_safety_info
@@ -619,7 +619,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			if (RelpersistenceTsTemp(get_rel_persistence(rte->relid)))
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 1e42d75465e..15a76b2326b 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6070,7 +6070,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
-	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+	if (RELATION_IS_TEMP(heap) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index c5194fdbbf2..38d7c658541 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -30,6 +30,7 @@
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temporary table in current backend */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 146ee8dd1ea..2d4e9393f00 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2907,6 +2907,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 e3068a374ee..159465e0185 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3410,17 +3410,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; }
@@ -11683,19 +11677,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 c5c3f26ecf1..2a2b2789077 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -82,6 +82,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);
 
 
 /*
@@ -3665,3 +3666,53 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * Like function isQueryUsingTempRelation_walker
+ * return true if any relation underlying
+ * the query is a global temporary table.
+ */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* Check if the query uses global temporary table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 1d3ee53244d..5e4feacd847 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -447,6 +447,13 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	/*
+	 * If a sequence is bound to a global temporary table, then the sequence
+	 * must been "global temporary"
+	 */
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
@@ -3326,6 +3333,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		cxt.isforeign = false;
 	}
 	cxt.relation = stmt->relation;
+	/* Sets the table persistence to the context */
+	cxt.relation->relpersistence = RelationGetRelPersistence(rel);
 	cxt.rel = rel;
 	cxt.inhRelations = NIL;
 	cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 3b3df8fa8cc..ca6c68879d2 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2111,6 +2111,14 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/*
+			 * Aotuvacuum cannot vacuum the private data stored in each backend
+			 * that belongs to global temporary table, so skip them.
+			 */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2177,7 +2185,7 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		if (RelpersistenceTsTemp(classForm->relpersistence))
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index e88e4e918b0..10eaac892a8 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlogutils.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -2934,6 +2935,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * Returns 0 if this global temporary table is not initialized in current
+	 * backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 9fa3e0631e6..cc3eb928bc6 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -23,6 +23,7 @@
 #include "access/syncscan.h"
 #include "access/twophase.h"
 #include "commands/async.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
@@ -143,6 +144,7 @@ CalculateShmemSize(int *num_semaphores)
 	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
@@ -246,6 +248,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* For global temporary table shared hashtable */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index bd3c7a47fe2..22481374778 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -65,6 +65,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -5131,3 +5132,78 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temporary table.
+ */
+int
+list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct		*arrayP = procArray;
+	TransactionId		result = InvalidTransactionId;
+	int			index;
+	uint8			flags = 0;
+	int			i = 0;
+
+	/* return 0 if feature is disabled */
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+		*n = 0;
+	}
+
+	/* Disable in standby node */
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		uint8           statusFlags = ProcGlobal->statusFlags[index];
+
+		if (statusFlags & flags)
+			continue;
+
+		/* Fetch all backend that is belonging to MyDatabaseId */
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->backend_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->backend_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->backend_gtt_frozenxid, result))
+				result = proc->backend_gtt_frozenxid;
+
+			/* save backend pid and backend level oldest relfrozenxid */
+			if (max_size > 0)
+			{
+				pids[i] = proc->pid;
+				xids[i] = proc->backend_gtt_frozenxid;
+				i++;
+			}
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (max_size > 0)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 862097352bb..4edd3b31f7a 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -176,7 +176,9 @@ static const char *const BuiltinTrancheNames[] = {
 	/* LWTRANCHE_PARALLEL_APPEND: */
 	"ParallelAppend",
 	/* LWTRANCHE_PER_XACT_PREDICATE_LIST: */
-	"PerXactPredicateList"
+	"PerXactPredicateList",
+	/* LWTRANCHE_GTT_CTL */
+	"GlobalTempTableControl"
 };
 
 StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index b7d9da0aa9f..8051f2053f9 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -393,6 +393,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;	/* init backend level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
@@ -578,6 +579,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;	/* init backend level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index d5a7fb13f3c..8225cf6219f 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -982,6 +982,13 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/*
+			 * For global temporary table ,each backend has its own storage,
+			 * also only sees its own storage. Use Backendid to identify them.
+			 */
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 0c8c05f6c2e..699507a24c7 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -108,6 +108,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -5115,12 +5116,26 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								/* For global temporary table, get statistic data from localhash */
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -5368,15 +5383,28 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6800,6 +6828,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;
@@ -6817,6 +6846,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6828,6 +6865,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;
@@ -6843,6 +6882,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7761,6 +7808,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))
@@ -7773,6 +7822,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7785,6 +7843,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.
@@ -7804,6 +7864,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 4ebaa552a27..78c33d2ac87 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -3113,6 +3114,19 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		/* For global temporary table, get statistic data from localhash */
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3e..94fdb998aae 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -65,6 +65,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1116,6 +1117,28 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+
+				/* For global temporary table, get relstat data from localhash */
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+
+				/* And put them to local relcache */
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1173,6 +1196,8 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			/* The state of the global temporary table's index may need to be set */
+			gtt_fix_index_backend_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -1300,7 +1325,22 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+			/*
+			 * For global temporary table, get the latest relfilenode
+			 * from localhash and put it in relcache.
+			 */
+			if (OidIsValid(newrelnode) &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2251,6 +2291,9 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		/* The state of the global temporary table's index may need to be set */
+		gtt_fix_index_backend_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3489,6 +3532,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3598,28 +3645,39 @@ void
 RelationSetNewRelfilenode(Relation relation, char persistence)
 {
 	Oid			newrelfilenode;
-	Relation	pg_class;
-	HeapTuple	tuple;
+	Relation	pg_class = NULL;
+	HeapTuple	tuple = NULL;
 	Form_pg_class classform;
 	MultiXactId minmulti = InvalidMultiXactId;
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
 
+	/*
+	 * For global temporary table, storage information for the table is
+	 * maintained locally, not in catalog.
+	 */
+	bool		update_catalog = !RELATION_IS_GLOBAL_TEMP(relation);
+
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
 
-	/*
-	 * Get a writable copy of the pg_class tuple for the given relation.
-	 */
-	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+	memset(&classform, 0, sizeof(classform));
 
-	tuple = SearchSysCacheCopy1(RELOID,
-								ObjectIdGetDatum(RelationGetRelid(relation)));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "could not find tuple for relation %u",
-			 RelationGetRelid(relation));
-	classform = (Form_pg_class) GETSTRUCT(tuple);
+	if (update_catalog)
+	{
+		/*
+		 * Get a writable copy of the pg_class tuple for the given relation.
+		 */
+		pg_class = table_open(RelationRelationId, RowExclusiveLock);
+
+		tuple = SearchSysCacheCopy1(RELOID,
+									ObjectIdGetDatum(RelationGetRelid(relation)));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u",
+				 RelationGetRelid(relation));
+		classform = (Form_pg_class) GETSTRUCT(tuple);
+	}
 
 	/*
 	 * Schedule unlinking of the old storage at transaction commit.
@@ -3645,7 +3703,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;
@@ -3665,6 +3723,18 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 			break;
 	}
 
+	/* For global temporary table */
+	if (!update_catalog)
+	{
+		Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+		Assert(RELATION_IS_GLOBAL_TEMP(relation));
+		Assert(!RelationIsMapped(relation));
+
+		/* Make cache invalid and set new relnode to local cache. */
+		CacheInvalidateRelcache(relation);
+		relation->rd_node.relNode = relnode;
+	}
 	/*
 	 * If we're dealing with a mapped index, pg_class.relfilenode doesn't
 	 * change; instead we have to send the update to the relation mapper.
@@ -3674,7 +3744,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	 * possibly-inaccurate values of relpages etc, but those will be fixed up
 	 * later.
 	 */
-	if (RelationIsMapped(relation))
+	else if (RelationIsMapped(relation))
 	{
 		/* This case is only supported for indexes */
 		Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3720,9 +3790,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 		CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 	}
 
-	heap_freetuple(tuple);
+	if (update_catalog)
+	{
+		heap_freetuple(tuple);
 
-	table_close(pg_class, RowExclusiveLock);
+		table_close(pg_class, RowExclusiveLock);
+	}
 
 	/*
 	 * Make the pg_class row change or relation map change visible.  This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index d2ce4a84506..7717ee2ca13 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -44,6 +44,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "commands/prepare.h"
 #include "commands/trigger.h"
@@ -152,6 +153,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temporary table feature.
+ * table schema are still saved in catalog.
+ *
+ * num > 0 means allows the database to manage multiple active tables at the same time.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2127,6 +2140,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 "
@@ -2697,6 +2719,16 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"vacuum_gtt_defer_check_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("The defer check age of GTT, used to check expired data after vacuum."),
+			NULL
+		},
+		&vacuum_gtt_defer_check_age,
+		10000, 0, 1000000,
+		NULL, NULL, NULL
+	},
+
 	/*
 	 * See also CheckRequiredParameterValues() if this parameter changes
 	 */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a485fb2d070..80658c4f3ba 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2503,6 +2503,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
 		dopt->no_unlogged_table_data)
 		return;
 
+	/* Don't dump data in global temporary table/sequence */
+	if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		return;
+
 	/* Check that the data is not explicitly excluded */
 	if (simple_oid_list_member(&tabledata_exclude_oids,
 							   tbinfo->dobj.catId.oid))
@@ -15961,6 +15965,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
 		char	   *foreign = "";
+		char	   *table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -16014,9 +16019,15 @@ dumpTableSchema(Archive *fout, const 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);
 
@@ -16380,6 +16391,15 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 			}
 		}
 
+		/*
+		 * Transaction information for the global temporary table is not stored
+		 * in the pg_class.
+		 */
+		if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			Assert(tbinfo->frozenxid == 0);
+			Assert(tbinfo->minmxid == 0);
+		}
 		/*
 		 * In binary_upgrade mode, arrange to restore the old relfrozenxid and
 		 * relminmxid of all vacuumable relations.  (While vacuum.c processes
@@ -16387,7 +16407,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		 * of other relations; so this "if" lacks RELKIND_TOASTVALUE, and the
 		 * child toast table is handled below.)
 		 */
-		if (dopt->binary_upgrade &&
+		else if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
 			 tbinfo->relkind == RELKIND_MATVIEW))
 		{
@@ -17390,6 +17410,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	PQExpBuffer query = createPQExpBuffer();
 	PQExpBuffer delqry = createPQExpBuffer();
 	char	   *qseqname;
+	bool	   global_temp_seq = false;
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
@@ -17399,9 +17420,12 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
-						  "FROM pg_catalog.pg_sequence "
-						  "WHERE seqrelid = '%u'::oid",
+						  "seqcache, seqcycle, "
+						  "c.relpersistence "
+						  "FROM pg_catalog.pg_sequence s, "
+						  "pg_catalog.pg_class c "
+						  "WHERE seqrelid = '%u'::oid "
+						  "and s.seqrelid = c.oid",
 						  tbinfo->dobj.catId.oid);
 	}
 	else if (fout->remoteVersion >= 80400)
@@ -17446,6 +17470,9 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
 
+	if (fout->remoteVersion >= 140000)
+		global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
 	if (strcmp(seqtype, "smallint") == 0)
@@ -17523,9 +17550,13 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	}
 	else
 	{
-		appendPQExpBuffer(query,
-						  "CREATE SEQUENCE %s\n",
-						  fmtQualifiedDumpable(tbinfo));
+		appendPQExpBuffer(query, "CREATE ");
+
+		if (global_temp_seq)
+			appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+		appendPQExpBuffer(query, "SEQUENCE %s\n",
+								  fmtQualifiedDumpable(tbinfo));
 
 		if (strcmp(seqtype, "bigint") != 0)
 			appendPQExpBuffer(query, "    AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index ad5f3919956..f3819860096 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -88,7 +88,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -178,7 +178,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 5d9a26cf822..2de11d5d707 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -447,8 +449,17 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
 			 "         ON c.relnamespace = n.oid "
 			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
+			 CppAsString2(RELKIND_MATVIEW) ") AND ");
+
+	if (skip_gtt)
+	{
+		/* exclude global temp tables */
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+			"    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND ");
+	}
+
 	/* exclude possible orphaned temp tables */
+	snprintf(query + strlen(query), sizeof(query) - strlen(query),
 			 "    ((n.nspname !~ '^pg_temp_' AND "
 			 "      n.nspname !~ '^pg_toast_temp_' AND "
 			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 3628bd74a7b..bbb9b5ea13d 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -407,7 +407,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -645,7 +645,10 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+			/* exclude global temp tables */
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -656,7 +659,10 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+		/* exclude global temp tables */
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index ca0795f68ff..018a2effd4b 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -389,7 +389,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 90ff649be72..7a50197875b 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -4067,7 +4067,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 5cd58386689..e528739a0ca 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1059,6 +1059,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2542,6 +2544,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2755,6 +2760,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE", "SEQUENCE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 6ce480b49c1..415ec38a801 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -59,7 +59,8 @@ extern Relation heap_create(const char *relname,
 							bool mapped_relation,
 							bool allow_system_table_mods,
 							TransactionId *relfrozenxid,
-							MultiXactId *relminmxid);
+							MultiXactId *relminmxid,
+							bool skip_create_storage);
 
 extern Oid	heap_create_with_catalog(const char *relname,
 									 Oid relnamespace,
@@ -85,7 +86,7 @@ extern Oid	heap_create_with_catalog(const char *relname,
 
 extern void heap_drop_with_catalog(Oid relid);
 
-extern void heap_truncate(List *relids);
+extern void heap_truncate(List *relids, bool is_global_temp);
 
 extern void heap_truncate_one_rel(Relation rel);
 
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 008f723e104..875b1003899 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -157,6 +157,7 @@ extern void reindex_index(Oid indexId, bool skip_constraint_checks,
 #define REINDEX_REL_CHECK_CONSTRAINTS		0x04
 #define REINDEX_REL_FORCE_INDEXES_UNLOGGED	0x08
 #define REINDEX_REL_FORCE_INDEXES_PERMANENT 0x10
+#define REINDEX_REL_PROCESS_GLOBAL_TEMP		0x20
 
 extern bool reindex_relation(Oid relid, int flags, ReindexParams *params);
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index fef9945ed8f..9176b7dcc07 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -172,6 +172,7 @@ DECLARE_INDEX(pg_class_tblspc_relfilenode_index, 3455, ClassTblspcRelfilenodeInd
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index d068d6532ec..fd5089388f2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5735,6 +5735,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '9874',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '9875',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '9876',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '9877',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 0ab32b44e91..92e9f8ba485 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,7 +22,7 @@
 /* GUC variables */
 extern int	wal_skip_threshold;
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationPreTruncate(Relation rel);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 00000000000..8a3d9558712
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,45 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern int		vacuum_gtt_defer_check_age;
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Oid relid,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void force_enable_gtt_index(Relation index);
+extern void gtt_fix_index_backend_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index 40544dd4c70..7b66d808fc5 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 336549cc5f0..3e8167134b7 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -86,7 +86,7 @@ extern void find_composite_type_dependencies(Oid typeOid,
 
 extern void check_of_type(HeapTuple typetuple);
 
-extern void register_on_commit_action(Oid relid, OnCommitAction action);
+extern void register_on_commit_action(Oid relid, OnCommitAction action, bool is_gloal_temp);
 extern void remove_on_commit_action(Oid relid);
 
 extern void PreCommit_on_commit_actions(void);
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 8336c2c5a29..bddcfe7256d 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index c86ccdaf608..6b395551c1d 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -399,6 +399,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index a8f052e4845..4b4ed1a13aa 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -189,6 +189,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_SHARED_TIDBITMAP,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_PER_XACT_PREDICATE_LIST,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index be67d8a8616..e2f8bb5162d 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -157,6 +157,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId backend_gtt_frozenxid;	/* backend level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index b01fa52139a..8efffa55ac5 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -94,4 +94,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index aa18d304ac0..524c9d7de3f 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -288,6 +288,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 b4faa1c1238..a74558a8383 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	SMgrRelation rd_smgr;		/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -326,6 +326,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -608,11 +609,13 @@ RelationGetSmgr(Relation rel)
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -620,6 +623,7 @@ RelationGetSmgr(Relation rel)
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -632,6 +636,30 @@ RelationGetSmgr(Relation rel)
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP_ON_CURRENT_SESSION
+ *		Test a rel is either local temp relation of this session
+ *		or global temp relation.
+ */
+#define RELATION_IS_TEMP_ON_CURRENT_SESSION(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is local temp relation or global temporary relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RelpersistenceTsTemp
+ *		Test a relpersistence is local temp relation or global temporary relation.
+ */
+#define RelpersistenceTsTemp(relpersistence) \
+	(relpersistence == RELPERSISTENCE_TEMP || \
+	 relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -677,6 +705,19 @@ RelationGetSmgr(Relation rel)
 	 (relation)->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&	\
 	 !IsCatalogRelation(relation))
 
+/* For global temporary table */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/* Get on commit clause value only for global temporary table */
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+/* Get relpersistence for relation */
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
-- 
2.30.1 (Apple Git-130)

0001-gtt-v53-reademe.patchapplication/octet-stream; name=0001-gtt-v53-reademe.patchDownload
diff --git a/README.gtt.txt b/README.gtt.txt
new file mode 100644
index 00000000000..d181df9acd7
--- /dev/null
+++ b/README.gtt.txt
@@ -0,0 +1,172 @@
+Global Temporary Table(GTT)
+=========================================
+
+Feature description
+-----------------------------------------
+
+Previously, temporary tables are defined once and automatically
+exist (starting with empty contents) in every session before using them.
+
+The temporary table implementation in PostgreSQL, known as Local temp tables(LTT),
+did not fully comply with the SQL standard. This version added the support of
+Global Temporary Table .
+
+The metadata of Global Temporary Table is persistent and shared among sessions.
+The data stored in the Global temporary table is independent of sessions. This
+means, when a session creates a Global Temporary Table and writes some data.
+Other sessions cannot see those data, but they have an empty Global Temporary
+Table with same schema.
+
+Like local temporary table, Global Temporary Table supports ON COMMIT PRESERVE ROWS
+or ON COMMIT DELETE ROWS clause, so that data in the temporary table can be
+cleaned up or preserved automatically when a session exits or a transaction COMMITs.
+
+Unlike Local Temporary Table, Global Temporary Table does not support
+ON COMMIT DROP clauses.
+
+In following paragraphs, we use GTT for Global Temporary Table and LTT for
+local temporary table.
+
+Main design ideas
+-----------------------------------------
+In general, GTT and LTT use the same storage and buffer design and
+implementation. The storage files for both types of temporary tables are named
+as t_backendid_relfilenode, and the local buffer is used to cache the data.
+
+The schema of GTTs is shared among sessions while their data are not. We build
+a new mechanisms to manage those non-shared data and their statistics.
+Here is the summary of changes:
+
+1) CATALOG
+GTTs store session-specific data. The storage information of GTTs'data, their
+transaction information, and their statistics are not stored in the catalog.
+
+2) STORAGE INFO & STATISTICS INFO & TRANSACTION INFO
+In order to maintain durability and availability of GTTs'session-specific data,
+their storage information, statistics, and transaction information is managed
+in a local hash table tt_storage_local_hash.
+
+3) DDL
+Currently, GTT supports almost all table'DDL except CLUSTER/VACUUM FULL.
+Part of the DDL behavior is limited by shared definitions and multiple copies of
+local data, and we added some structures to handle this.
+
+A shared hash table active_gtt_shared_hash is added to track the state of the
+GTT in a different session. This information is recorded in the hash table
+during the DDL execution of the GTT.
+
+The data stored in a GTT can only be modified or accessed by owning session.
+The statements that only modify data in a GTT do not need a high level of
+table locking. The operations making those changes include truncate GTT,
+reindex GTT, and lock GTT.
+
+4) MVCC commit log(clog) cleanup
+Each GTT in a session has its own piece of data, and they have their own
+transaction information. We set up data structures to track and maintain
+this information. The cleaning of CLOGs also needs to consider the transaction
+information of GTT.
+
+Detailed design
+-----------------------------------------
+
+1. CATALOG
+1.1 relpersistence
+define RELPERSISTENCEGLOBALTEMP 'g'
+Mark Global Temporary Table in pg_class relpersistence to 'g'. The relpersistence
+of indexes created on the GTT, sequences on GTT and toast tables on GTT are
+also set to 'g'
+
+1.2 on commit clause
+LTT's status associated with on commit DELETE ROWS and on commit PRESERVE ROWS
+is not stored in catalog. Instead, GTTs need a bool value on_commit_delete_rows
+in reloptions which is shared among sessions.
+
+1.3 gram.y
+GTT is already supported in syntax tree. We remove the warning message
+"GLOBAL is deprecated in temporary table creation" and mark
+relpersistence = RELPERSISTENCEGLOBALTEMP.
+
+2. STORAGE INFO & STATISTICS INFO & TRANSACTION INFO
+2.1. gtt_storage_local_hash
+Each backend creates a local hashtable gtt_storage_local_hash to track a GTT's
+storage file information, statistics, and transaction information.
+
+2.2 GTT storage file info track
+1) When one session inserts data into a GTT for the first time, record the
+storage info to gtt_storage_local_hash.
+2) Use beforeshmemexit to ensure that all files of session GTT are deleted when
+the session exits.
+
+2.3 statistics info
+1) relpages reltuples relallvisible relfilenode
+2) The statistics of each column from pg_statistic
+All the above information is stored in gtt_storage_local_hash.
+When doing vacuum or analyze, GTT's statistic is updated, which is used by
+the SQL planner.
+The statistics summarizes only data in the current session.
+
+2.3 transaction info track
+frozenxid minmulti from pg_class is stored to gtt_storage_local_hash.
+
+3 DDL
+3.1. active_gtt_shared_hash
+This is the hash table created in shared memory to trace the GTT files initialized
+in each session. Each hash entry contains a bitmap that records the backendid of
+the initialized GTT file. With this hash table, we know which backend/session
+is using this GTT. Such information is used during GTT's DDL operations.
+
+3.2 DROP GTT
+One GTT is allowed to be deleted when there is only one session using the table
+and the session is the current session. After holding the lock on GTT,
+active_gtt_shared_hash is checked to ensure that.
+
+3.3 ALTER GTT/DROP INDEX ON GTT
+Same as drop GTT.
+
+3.4 CREATE INDEX ON GTT
+1) create index on GTT statements build index based on local data in a session.
+2) After the index is created, record the index metadata to the catalog.
+3) Other sessions can enable or disable the local GTT index.
+
+3.5 TRUNCATE/REINDEX GTT
+The SQL truncate/reindex command open the GTT using AccessShareLock lock,
+not AccessExclusiveLock, because this command only cleans up local data and
+local buffers in current session. This allows these operations to be executed
+concurrently between sessions, unlike normal tables.
+
+3.6 LOCK GTT
+A lock GTT statement does not hold any relation lock.
+
+3.7 CLUSTER GTT/VACUUM FULL GTT
+The current version does not support.
+
+4 MVCC commit log(clog) cleanup
+
+The GTT storage file contains transaction information. Queries for GTT data rely
+on transaction information such as clog. The transaction information required by
+each session may be completely different. We need to ensure that the transaction
+information of the GTT data is not cleaned up during its lifetime and that
+transaction resources are recycled at the instance level.
+
+4.1 The session level GTT oldest frozenxid
+1) To manage all GTT transaction information, add session level oldest frozenxid
+in each session. When one GTT is created or removed, record the session level
+oldest frozenxid and store it in MyProc.
+2) When vacuum advances the database's frozenxid, session level oldest frozenxid
+should be considered. This is acquired by searching all of MyProc. This way,
+we can avoid the clog required by GTTs to be cleaned.
+
+4.2 vacuum GTT
+Users can perform vacuum over a GTT to clean up local data in the GTT.
+
+4.3 autovacuum GTT
+Autovacuum skips all GTTs, because the data in GTTs is only visible in current session.
+
+5 OTHERS
+5.1 Parallel query
+Planner does not produce parallel query plans for SQL related to GTT. Because
+GTT private data cannot be accessed across processes.
+
+5.2 WAL and Logical replication
+Like LTT, the DML on GTT does not record WAL and is not parsed or replay by
+the logical replication.
\ No newline at end of file
-- 
2.30.1 (Apple Git-130)

#326Pavel Stehule
pavel.stehule@gmail.com
In reply to: wenjing (#325)
Re: [Proposal] Global temporary tables

hi

ne 26. 9. 2021 v 6:05 odesílatel wenjing <wjzeng2012@gmail.com> napsal:

2021年9月23日 21:55,Tony Zhu <tony.zhu@ww-it.cn> 写道:

Hi Wenjing

we have reviewed the code, and done the regression tests, all tests is
pass, we believe the feature code quality is ready for production ; and I
will change the status to "Ready for commit”

Thank you very much for your attention and testing.
As we communicated, I fixed several issues and attached the latest patch.

looks so windows build is broken

https://ci.appveyor.com/project/postgresql-cfbot/postgresql/build/1.0.148542

Regards

Pavel

Show quoted text

Wenjing

#327wenjing
wjzeng2012@gmail.com
In reply to: Pavel Stehule (#326)
4 attachment(s)
Re: [Proposal] Global temporary tables

Pavel Stehule <pavel.stehule@gmail.com> 于2021年9月29日周三 下午1:53写道:

hi

ne 26. 9. 2021 v 6:05 odesílatel wenjing <wjzeng2012@gmail.com> napsal:

2021年9月23日 21:55,Tony Zhu <tony.zhu@ww-it.cn> 写道:

Hi Wenjing

we have reviewed the code, and done the regression tests, all tests is
pass, we believe the feature code quality is ready for production ; and I
will change the status to "Ready for commit”

Thank you very much for your attention and testing.
As we communicated, I fixed several issues and attached the latest patch.

looks so windows build is broken

https://ci.appveyor.com/project/postgresql-cfbot/postgresql/build/1.0.148542

This is indeed a problem and it has been fixed in the new version(v54).
Thank you for pointing it out, please review the code again.

Wenjing

Show quoted text

Regards

Pavel

Wenjing

Attachments:

0001-gtt-v54-reademe.patchapplication/octet-stream; name=0001-gtt-v54-reademe.patchDownload
diff --git a/README.gtt.txt b/README.gtt.txt
new file mode 100644
index 00000000000..d181df9acd7
--- /dev/null
+++ b/README.gtt.txt
@@ -0,0 +1,172 @@
+Global Temporary Table(GTT)
+=========================================
+
+Feature description
+-----------------------------------------
+
+Previously, temporary tables are defined once and automatically
+exist (starting with empty contents) in every session before using them.
+
+The temporary table implementation in PostgreSQL, known as Local temp tables(LTT),
+did not fully comply with the SQL standard. This version added the support of
+Global Temporary Table .
+
+The metadata of Global Temporary Table is persistent and shared among sessions.
+The data stored in the Global temporary table is independent of sessions. This
+means, when a session creates a Global Temporary Table and writes some data.
+Other sessions cannot see those data, but they have an empty Global Temporary
+Table with same schema.
+
+Like local temporary table, Global Temporary Table supports ON COMMIT PRESERVE ROWS
+or ON COMMIT DELETE ROWS clause, so that data in the temporary table can be
+cleaned up or preserved automatically when a session exits or a transaction COMMITs.
+
+Unlike Local Temporary Table, Global Temporary Table does not support
+ON COMMIT DROP clauses.
+
+In following paragraphs, we use GTT for Global Temporary Table and LTT for
+local temporary table.
+
+Main design ideas
+-----------------------------------------
+In general, GTT and LTT use the same storage and buffer design and
+implementation. The storage files for both types of temporary tables are named
+as t_backendid_relfilenode, and the local buffer is used to cache the data.
+
+The schema of GTTs is shared among sessions while their data are not. We build
+a new mechanisms to manage those non-shared data and their statistics.
+Here is the summary of changes:
+
+1) CATALOG
+GTTs store session-specific data. The storage information of GTTs'data, their
+transaction information, and their statistics are not stored in the catalog.
+
+2) STORAGE INFO & STATISTICS INFO & TRANSACTION INFO
+In order to maintain durability and availability of GTTs'session-specific data,
+their storage information, statistics, and transaction information is managed
+in a local hash table tt_storage_local_hash.
+
+3) DDL
+Currently, GTT supports almost all table'DDL except CLUSTER/VACUUM FULL.
+Part of the DDL behavior is limited by shared definitions and multiple copies of
+local data, and we added some structures to handle this.
+
+A shared hash table active_gtt_shared_hash is added to track the state of the
+GTT in a different session. This information is recorded in the hash table
+during the DDL execution of the GTT.
+
+The data stored in a GTT can only be modified or accessed by owning session.
+The statements that only modify data in a GTT do not need a high level of
+table locking. The operations making those changes include truncate GTT,
+reindex GTT, and lock GTT.
+
+4) MVCC commit log(clog) cleanup
+Each GTT in a session has its own piece of data, and they have their own
+transaction information. We set up data structures to track and maintain
+this information. The cleaning of CLOGs also needs to consider the transaction
+information of GTT.
+
+Detailed design
+-----------------------------------------
+
+1. CATALOG
+1.1 relpersistence
+define RELPERSISTENCEGLOBALTEMP 'g'
+Mark Global Temporary Table in pg_class relpersistence to 'g'. The relpersistence
+of indexes created on the GTT, sequences on GTT and toast tables on GTT are
+also set to 'g'
+
+1.2 on commit clause
+LTT's status associated with on commit DELETE ROWS and on commit PRESERVE ROWS
+is not stored in catalog. Instead, GTTs need a bool value on_commit_delete_rows
+in reloptions which is shared among sessions.
+
+1.3 gram.y
+GTT is already supported in syntax tree. We remove the warning message
+"GLOBAL is deprecated in temporary table creation" and mark
+relpersistence = RELPERSISTENCEGLOBALTEMP.
+
+2. STORAGE INFO & STATISTICS INFO & TRANSACTION INFO
+2.1. gtt_storage_local_hash
+Each backend creates a local hashtable gtt_storage_local_hash to track a GTT's
+storage file information, statistics, and transaction information.
+
+2.2 GTT storage file info track
+1) When one session inserts data into a GTT for the first time, record the
+storage info to gtt_storage_local_hash.
+2) Use beforeshmemexit to ensure that all files of session GTT are deleted when
+the session exits.
+
+2.3 statistics info
+1) relpages reltuples relallvisible relfilenode
+2) The statistics of each column from pg_statistic
+All the above information is stored in gtt_storage_local_hash.
+When doing vacuum or analyze, GTT's statistic is updated, which is used by
+the SQL planner.
+The statistics summarizes only data in the current session.
+
+2.3 transaction info track
+frozenxid minmulti from pg_class is stored to gtt_storage_local_hash.
+
+3 DDL
+3.1. active_gtt_shared_hash
+This is the hash table created in shared memory to trace the GTT files initialized
+in each session. Each hash entry contains a bitmap that records the backendid of
+the initialized GTT file. With this hash table, we know which backend/session
+is using this GTT. Such information is used during GTT's DDL operations.
+
+3.2 DROP GTT
+One GTT is allowed to be deleted when there is only one session using the table
+and the session is the current session. After holding the lock on GTT,
+active_gtt_shared_hash is checked to ensure that.
+
+3.3 ALTER GTT/DROP INDEX ON GTT
+Same as drop GTT.
+
+3.4 CREATE INDEX ON GTT
+1) create index on GTT statements build index based on local data in a session.
+2) After the index is created, record the index metadata to the catalog.
+3) Other sessions can enable or disable the local GTT index.
+
+3.5 TRUNCATE/REINDEX GTT
+The SQL truncate/reindex command open the GTT using AccessShareLock lock,
+not AccessExclusiveLock, because this command only cleans up local data and
+local buffers in current session. This allows these operations to be executed
+concurrently between sessions, unlike normal tables.
+
+3.6 LOCK GTT
+A lock GTT statement does not hold any relation lock.
+
+3.7 CLUSTER GTT/VACUUM FULL GTT
+The current version does not support.
+
+4 MVCC commit log(clog) cleanup
+
+The GTT storage file contains transaction information. Queries for GTT data rely
+on transaction information such as clog. The transaction information required by
+each session may be completely different. We need to ensure that the transaction
+information of the GTT data is not cleaned up during its lifetime and that
+transaction resources are recycled at the instance level.
+
+4.1 The session level GTT oldest frozenxid
+1) To manage all GTT transaction information, add session level oldest frozenxid
+in each session. When one GTT is created or removed, record the session level
+oldest frozenxid and store it in MyProc.
+2) When vacuum advances the database's frozenxid, session level oldest frozenxid
+should be considered. This is acquired by searching all of MyProc. This way,
+we can avoid the clog required by GTTs to be cleaned.
+
+4.2 vacuum GTT
+Users can perform vacuum over a GTT to clean up local data in the GTT.
+
+4.3 autovacuum GTT
+Autovacuum skips all GTTs, because the data in GTTs is only visible in current session.
+
+5 OTHERS
+5.1 Parallel query
+Planner does not produce parallel query plans for SQL related to GTT. Because
+GTT private data cannot be accessed across processes.
+
+5.2 WAL and Logical replication
+Like LTT, the DML on GTT does not record WAL and is not parsed or replay by
+the logical replication.
\ No newline at end of file
-- 
2.30.1 (Apple Git-130)

0002-gtt-v54-doc.patchapplication/octet-stream; name=0002-gtt-v54-doc.patchDownload
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 473a0a4aeb..e510bde8ac 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -169,32 +169,67 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     <listitem>
      <para>
       If specified, the table is created as a temporary table.
-      Temporary tables are automatically dropped at the end of a
-      session, or optionally at the end of the current transaction
-      (see <literal>ON COMMIT</literal> below).  The default
-      search_path includes the temporary schema first and so identically
-      named existing permanent tables are not chosen for new plans
+      Optionally, <literal>GLOBAL</literal> or <literal>LOCAL</literal>
+      can be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
+      They represent two types of temporary tables supported by <productname>PostgreSQL</productname>:
+      global temporary table and local temporary table. Without specified
+      GLOBAL or LOCAL, a local temporary table is created by default.
+     </para>
+
+    <para>
+     Both types of temporary tables’ data are truncated at the
+     end of a session or optionally at the end of the current transaction.
+     (see <literal>ON COMMIT</literal> below). For global temporary table,
+     its schema is reserved and reused by future sessions or transactions.
+     For local temporary table, both its data and its schema are dropped.
+    </para>
+
+    <variablelist>
+     <varlistentry>
+      <term><literal>Global Temporary Table</literal></term>
+      <listitem>
+       <para>
+        Global temporary table are defined just once and automatically exist
+        (starting with empty contents) in every session that needs them.
+        The schema definition of temporary tables is persistent and shared among sessions.
+        However, the data in temporary tables are kept private to sessions themselves,
+        even though they use same name and same schema.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>Local Temporary Table</literal></term>
+     <listitem>
+     <para>
+      Local temporary table are automatically dropped at the end of a
+      session (include schema and data). Future sessions need to create
+      their own temporary tables when they are used.
+     </para>
+     <para>
+      The default search_path includes the temporary schema first and so
+      identically named existing permanent tables are not chosen for new plans
       while the temporary table exists, unless they are referenced
       with schema-qualified names. Any indexes created on a temporary
       table are automatically temporary as well.
      </para>
+     </listitem>
+     </varlistentry>
+    </variablelist>
 
-     <para>
-      The <link linkend="autovacuum">autovacuum daemon</link> cannot
-      access and therefore cannot vacuum or analyze temporary tables.
-      For this reason, appropriate vacuum and analyze operations should be
-      performed via session SQL commands.  For example, if a temporary
-      table is going to be used in complex queries, it is wise to run
-      <command>ANALYZE</command> on the temporary table after it is populated.
-     </para>
+    <para>
+     The <link linkend="autovacuum">autovacuum daemon</link> cannot
+     access and therefore cannot vacuum or analyze temporary tables.
+     For this reason, appropriate vacuum and analyze operations should be
+     performed via session SQL commands.  For example, if a temporary
+     table is going to be used in complex queries, it is wise to run
+     <command>ANALYZE</command> on the temporary table after it is populated.
+    </para>
+    <para>
+     The Temporary table resembles the SQL standard, but has some differences.
+     see <xref linkend="sql-createtable-compatibility"/> below.
+    </para>
 
-     <para>
-      Optionally, <literal>GLOBAL</literal> or <literal>LOCAL</literal>
-      can be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
-      This presently makes no difference in <productname>PostgreSQL</productname>
-      and is deprecated; see
-      <xref linkend="sql-createtable-compatibility"/> below.
-     </para>
     </listitem>
    </varlistentry>
 
@@ -2133,13 +2168,17 @@ CREATE TABLE cities_partdef
    <title>Temporary Tables</title>
 
    <para>
-    Although the syntax of <literal>CREATE TEMPORARY TABLE</literal>
-    resembles that of the SQL standard, the effect is not the same.  In the
-    standard,
-    temporary tables are defined just once and automatically exist (starting
-    with empty contents) in every session that needs them.
-    <productname>PostgreSQL</productname> instead
-    requires each session to issue its own <literal>CREATE TEMPORARY
+    Although the syntax of <literal>CREATE GLOBAL/LOCAL TEMPORARY TABLE</literal>
+    resembles that of the SQL standard, the effect is not the same.
+    The global temporary table follows the SQL standards while local temporary
+    table does not.
+   </para>
+
+   <para>
+    First, in the standard, both global and local temporary tables are defined just
+    once and automatically exist (starting with empty contents) in every session
+    that needs them. For local temporary tables, <productname>PostgreSQL</productname>
+    instead requires each session to issue its own <literal>CREATE LOCAL TEMPORARY
     TABLE</literal> command for each temporary table to be used.  This allows
     different sessions to use the same temporary table name for different
     purposes, whereas the standard's approach constrains all instances of a
@@ -2147,29 +2186,14 @@ CREATE TABLE cities_partdef
    </para>
 
    <para>
-    The standard's definition of the behavior of temporary tables is
-    widely ignored.  <productname>PostgreSQL</productname>'s behavior
-    on this point is similar to that of several other SQL databases.
-   </para>
-
-   <para>
-    The SQL standard also distinguishes between global and local temporary
+    Second, the SQL standard distinguishes between global and local temporary
     tables, where a local temporary table has a separate set of contents for
     each SQL module within each session, though its definition is still shared
-    across sessions.  Since <productname>PostgreSQL</productname> does not
+    across sessions. Since <productname>PostgreSQL</productname> does not
     support SQL modules, this distinction is not relevant in
     <productname>PostgreSQL</productname>.
    </para>
 
-   <para>
-    For compatibility's sake, <productname>PostgreSQL</productname> will
-    accept the <literal>GLOBAL</literal> and <literal>LOCAL</literal> keywords
-    in a temporary table declaration, but they currently have no effect.
-    Use of these keywords is discouraged, since future versions of
-    <productname>PostgreSQL</productname> might adopt a more
-    standard-compliant interpretation of their meaning.
-   </para>
-
    <para>
     The <literal>ON COMMIT</literal> clause for temporary tables
     also resembles the SQL standard, but has some differences.
@@ -2177,7 +2201,8 @@ CREATE TABLE cities_partdef
     default behavior is <literal>ON COMMIT DELETE ROWS</literal>.  However, the
     default behavior in <productname>PostgreSQL</productname> is
     <literal>ON COMMIT PRESERVE ROWS</literal>.  The <literal>ON COMMIT
-    DROP</literal> option does not exist in SQL.
+    DROP</literal> option does not exist in SQL and is not supported by
+    global temporary table.
    </para>
   </refsect2>
 
-- 
2.30.1 (Apple Git-130)

0003-gtt-v54-implementation.patchapplication/octet-stream; name=0003-gtt-v54-implementation.patchDownload
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index b5602f53233..21b2d2a9527 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -159,6 +159,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * In order to avoid consistency problems, the global temporary table
+	 * uses ShareUpdateExclusiveLock.
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temporary table on commit options",
+			RELOPT_KIND_HEAP,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},
 	/* list terminator */
 	{{NULL}}
 };
@@ -1834,6 +1847,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 43ba03b6eb9..49f1052fdb1 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1023,7 +1023,7 @@ gistproperty(Oid index_oid, int attno,
 XLogRecPtr
 gistGetFakeLSN(Relation rel)
 {
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	if (RELATION_IS_TEMP(rel))
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index eb3810494f2..cbd22909582 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -151,7 +151,7 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	if (!RELATION_IS_TEMP(index))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 9befe012a9e..26fce0c4b83 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -593,7 +593,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
@@ -645,7 +645,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(RelationGetSmgr(rel), dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 9eaf07649e8..41db273abed 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -63,6 +63,7 @@
 #include "access/xlog.h"
 #include "catalog/index.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -508,6 +509,14 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 	TransactionId FreezeLimit;
 	MultiXactId MultiXactCutoff;
 
+	/*
+	 * not every AM requires these to be valid, but regular heap does.
+	 * Transaction information for the global temp table will be stored
+	 * in the local hash table, not the catalog.
+	 */
+	Assert(RELATION_IS_GLOBAL_TEMP(rel) ^ TransactionIdIsNormal(rel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(rel) ^ MultiXactIdIsValid(rel->rd_rel->relminmxid));
+
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
 	{
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index ebec8fa5b89..84766b3a338 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -677,6 +678,14 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		/*
+		 * If a global temporary table storage file is not initialized in the
+		 * current backend, its index does not have a root page, just returns 0.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e773612..8c21979625f 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -44,6 +44,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index aa7d4d5456b..595cb03eb4a 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -504,6 +504,7 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 83746d3fd91..99b2ad33915 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -62,6 +62,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,
@@ -366,13 +368,24 @@ heap_create(const char *relname,
 			break;
 	}
 
+	/* For global temporary table, even if the storage is not initialized,
+	 * the relfilenode needs to be generated and put into the catalog.
+	 */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		create_storage = false;
+		if (!OidIsValid(relfilenode))
+			relfilenode = relid;
+	}
 	/*
 	 * Decide whether to create storage. If caller passed a valid relfilenode,
 	 * storage is already created, so don't do it here.  Also don't create it
 	 * for relkinds without physical storage.
 	 */
-	if (!RELKIND_HAS_STORAGE(relkind) || OidIsValid(relfilenode))
+	else if (!RELKIND_HAS_STORAGE(relkind) || OidIsValid(relfilenode))
+	{
 		create_storage = false;
+	}
 	else
 	{
 		create_storage = true;
@@ -427,7 +440,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:
@@ -445,7 +458,8 @@ heap_create(const char *relname,
 	 * protected by the existence of a physical file; but for relations with
 	 * no files, add a pg_shdepend entry to account for that.
 	 */
-	if (!create_storage && reltablespace != InvalidOid)
+	if (!create_storage && reltablespace != InvalidOid &&
+		relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
 		recordDependencyOnTablespace(RelationRelationId, relid,
 									 reltablespace);
 
@@ -998,6 +1012,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -1036,8 +1051,21 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/*
+	 * The transaction information of the global temporary table is stored
+	 * in the local hash table, not in catalog.
+	 */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1410,6 +1438,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1495,8 +1524,9 @@ heap_create_with_catalog(const char *relname,
 	/*
 	 * If there's a special on-commit action, remember it
 	 */
-	if (oncommit != ONCOMMIT_NOOP)
-		register_on_commit_action(relid, oncommit);
+	if (oncommit != ONCOMMIT_NOOP &&
+		relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+		register_on_commit_action(relid, oncommit, false);
 
 	/*
 	 * ok, the relation has been cataloged, so close our relations and return
@@ -1993,6 +2023,19 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/*
+	 * Only when other sessions are not using this Global temporary table,
+	 * is it allowed to DROP it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot drop global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3277,7 +3320,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3289,7 +3332,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3325,7 +3368,7 @@ RelationTruncateIndexes(Relation heapRelation)
  * ON COMMIT truncation of temporary tables, where it doesn't matter.
  */
 void
-heap_truncate(List *relids)
+heap_truncate(List *relids, bool is_global_temp)
 {
 	List	   *relations = NIL;
 	ListCell   *cell;
@@ -3335,8 +3378,23 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode;
+
+		/*
+		 * Truncate global temporary table only clears local data,
+		 * so only low-level locks need to be held.
+		 */
+		if (is_global_temp)
+		{
+			if (!gtt_storage_attached(rid))
+				continue;
+
+			lockmode = RowExclusiveLock;
+		}
+		else
+			lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3369,6 +3427,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3377,23 +3436,39 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	/*
+	 * Truncate GTT only clears local data, so only low-level locks
+	 * need to be held.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		lockmode = AccessShareLock;
+	else
+		lockmode = AccessExclusiveLock;
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	/*
+	 * After the data is cleaned up on the GTT, the transaction information
+	 * for the data(stored in local hash table) is also need reset.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(RelationGetRelid(rel), 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 26bfa74ce75..c32b45f9673 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -54,6 +54,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"
@@ -733,6 +734,25 @@ index_create(Relation heapRelation,
 	TransactionId relfrozenxid;
 	MultiXactId relminmxid;
 
+	/* For global temporary table only */
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temporary table with concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot reindex global temporary tables concurrently")));
+
+		/*
+		 * For the case that some backend is applied relcache message to create
+		 * an index on a global temporary table, if this table in the current
+		 * backend are not initialized, the creation of index storage on the
+		 * table are also skipped.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/* constraint flags can only be set when a constraint is requested */
 	Assert((constr_flags == 0) ||
 		   ((flags & INDEX_CREATE_ADD_CONSTRAINT) != 0));
@@ -2107,7 +2127,7 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 * lock (see comments in RemoveRelations), and a non-concurrent DROP is
 	 * more efficient.
 	 */
-	Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+	Assert(!RelpersistenceTsTemp(get_rel_persistence(indexId)) ||
 		   (!concurrent && !concurrent_lock_mode));
 
 	/*
@@ -2139,6 +2159,21 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/*
+	 * Allow to drop index on global temporary table when only current
+	 * backend use it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation) &&
+		is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+	{
+		ereport(ERROR,
+			(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+			 errmsg("cannot drop index %s on global temporary table %s",
+					RelationGetRelationName(userIndexRelation), RelationGetRelationName(userHeapRelation)),
+					errdetail("Because the index is created on the global temporary table and other backend attached it."),
+					errhint("Please try detach all sessions using this temporary table and try again.")));
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2747,6 +2782,7 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = RELATION_IS_GLOBAL_TEMP(rel);
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2841,20 +2877,37 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
-		{
-			rd_rel->relpages = (int32) relpages;
-			dirty = true;
-		}
-		if (rd_rel->reltuples != (float4) reltuples)
+		/* For global temporary table */
+		if (is_gtt)
 		{
-			rd_rel->reltuples = (float4) reltuples;
-			dirty = true;
+			/* Update GTT'statistics into local relcache */
+			rel->rd_rel->relpages = (int32) relpages;
+			rel->rd_rel->reltuples = (float4) reltuples;
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+
+			/* Update GTT'statistics into local hashtable */
+			up_gtt_relstats(RelationGetRelid(rel), relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+		else
 		{
-			rd_rel->relallvisible = (int32) relallvisible;
-			dirty = true;
+			if (rd_rel->relpages != (int32) relpages)
+			{
+				rd_rel->relpages = (int32) relpages;
+				dirty = true;
+			}
+
+			if (rd_rel->reltuples != (float4) reltuples)
+			{
+				rd_rel->reltuples = (float4) reltuples;
+				dirty = true;
+			}
+
+			if (rd_rel->relallvisible != (int32) relallvisible)
+			{
+				rd_rel->relallvisible = (int32) relallvisible;
+				dirty = true;
+			}
 		}
 	}
 
@@ -2967,6 +3020,26 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, progress_index, progress_vals);
 	}
 
+	/* For build index on global temporary table */
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		/*
+		 * If the storage for the index in this session is not initialized,
+		 * it needs to be created.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			/* Before create init storage, fix the local Relcache first */
+			force_enable_gtt_index(indexRelation);
+
+			Assert(gtt_storage_attached(RelationGetRelid(heapRelation)));
+
+			/* Init storage for index */
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3508,6 +3581,8 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	PGRUsage	ru0;
 	bool		progress = ((params->options & REINDEXOPT_REPORT_PROGRESS) != 0);
 	bool		set_tablespace = false;
+	LOCKMODE	lockmode_on_heap = ShareLock;
+	LOCKMODE	lockmode_on_index = AccessExclusiveLock;
 
 	pg_rusage_init(&ru0);
 
@@ -3521,10 +3596,29 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	if (!OidIsValid(heapId))
 		return;
 
+	/*
+	 * For reindex on global temporary table, If the storage for the index
+	 * in current backend is not initialized, nothing is done.
+	 */
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		if (!gtt_storage_attached(indexId))
+		{
+			/* Suppress use of the target index while rebuilding it */
+			SetReindexProcessing(heapId, indexId);
+			/* Re-allow use of target index */
+			ResetReindexProcessing();
+			return;
+		}
+
+		lockmode_on_heap = AccessShareLock;
+		lockmode_on_index = AccessShareLock;
+	}
+
 	if ((params->options & REINDEXOPT_MISSING_OK) != 0)
-		heapRelation = try_table_open(heapId, ShareLock);
+		heapRelation = try_table_open(heapId, lockmode_on_heap);
 	else
-		heapRelation = table_open(heapId, ShareLock);
+		heapRelation = table_open(heapId, lockmode_on_heap);
 
 	/* if relation is gone, leave */
 	if (!heapRelation)
@@ -3550,7 +3644,7 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	 * Open the target index relation and get an exclusive lock on it, to
 	 * ensure that no one else is touching this particular index.
 	 */
-	iRel = index_open(indexId, AccessExclusiveLock);
+	iRel = index_open(indexId, lockmode_on_index);
 
 	if (progress)
 		pgstat_progress_update_param(PROGRESS_CREATEIDX_ACCESS_METHOD_OID,
@@ -3801,6 +3895,12 @@ reindex_relation(Oid relid, int flags, ReindexParams *params)
 	bool		result;
 	ListCell   *indexId;
 	int			i;
+	LOCKMODE	lockmode;
+
+	if (flags & REINDEX_REL_PROCESS_GLOBAL_TEMP)
+		lockmode = AccessShareLock;
+	else
+		lockmode = ShareLock;
 
 	/*
 	 * Open and lock the relation.  ShareLock is sufficient since we only need
@@ -3808,9 +3908,9 @@ reindex_relation(Oid relid, int flags, ReindexParams *params)
 	 * should match ReindexTable().
 	 */
 	if ((params->options & REINDEXOPT_MISSING_OK) != 0)
-		rel = try_table_open(relid, ShareLock);
+		rel = try_table_open(relid, lockmode);
 	else
-		rel = table_open(relid, ShareLock);
+		rel = table_open(relid, lockmode);
 
 	/* if relation is gone, leave */
 	if (!rel)
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 4de8400fd0f..fe3fcc712cb 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -656,6 +656,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/* Do not allow create global temporary table in temporary schemas */
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp table in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index c5ad28d71fe..707068a6fd8 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,6 +27,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -61,6 +62,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			temprelOid;			/* InvalidOid if not a global temporary rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -115,7 +117,7 @@ AddPendingSync(const RelFileNode *rnode)
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -126,7 +128,12 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/*
+		 * Global temporary table and local temporary table use same
+		 * design on storage module.
+		 */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -154,6 +161,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
@@ -165,6 +173,21 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		AddPendingSync(&rnode);
 	}
 
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		Assert(rel && RELATION_IS_GLOBAL_TEMP(rel));
+
+		/*
+		 * Remember the reloid of global temporary table, which is used for
+		 * transaction commit or rollback.
+		 * see smgrDoPendingDeletes.
+		 */
+		pending->temprelOid = RelationGetRelid(rel);
+
+		/* Remember global temporary table storage info to localhash */
+		remember_gtt_storage_info(rnode, rel);
+	}
+
 	return srel;
 }
 
@@ -201,11 +224,20 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/*
+	 * Remember the reloid of global temporary table, which is used for
+	 * transaction commit or rollback.
+	 * see smgrDoPendingDeletes.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->temprelOid = RelationGetRelid(rel);
+
 	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
@@ -618,6 +650,7 @@ smgrDoPendingDeletes(bool isCommit)
 	int			nrels = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -647,14 +680,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->temprelOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -664,12 +701,21 @@ smgrDoPendingDeletes(bool isCommit)
 
 	if (nrels > 0)
 	{
+		int	i;
+
 		smgrdounlinkall(srels, nrels, false);
 
-		for (int i = 0; i < nrels; i++)
+		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* Delete global temporary table info in localhash */
+			if (gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 00000000000..384b4b07af1
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1573 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  The body implementation of Global Temparary table.
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *	  See src/backend/catalog/GTT_README for Global temparary table's
+ *	  requirements and design.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_info_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+int		vacuum_gtt_defer_check_age = 0;
+
+/*
+ * The Global temporary table's shared hash table data structure
+ */
+typedef struct gtt_ctl_data
+{
+	LWLock		lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+/* record this global temporary table in which backends are being used */
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+/*
+ * The Global temporary table's local hash table data structure
+ */
+/* Record the storage information and statistical information of the global temporary table */
+typedef struct
+{
+	Oid			relfilenode;
+	Oid			spcnode;
+
+	/* pg_class relstat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+
+	/* pg_statistic column stat */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_free_statistics(gtt_relfilenode *rnode);
+static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok);
+static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok);
+static Bitmapset *copy_active_gtt_bitmap(Oid relid);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+/*
+ * Calculate shared hash table entry size for GTT.
+ */
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	/* hash entry header size */
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	/*
+	 * hash entry data size
+	 * this is a bitmap in shared memory, each backend have a bit.
+	 */
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+/*
+ * Calculate shared hash table max size for GTT.
+ */
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	/* shared hash header size */
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	/* hash entry size */
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	/* max size */
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+/*
+ * Initialization shared hash table for GTT.
+ */
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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();
+	}
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(gtt_fnode);
+	info.entrysize = action_gtt_shared_hash_entry_size();
+	active_gtt_shared_hash =
+		ShmemInitHash("active gtt shared hash",
+						gtt_shared_ctl->max_entry,
+						gtt_shared_ctl->max_entry,
+						&info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+/*
+ * Record GTT relid to shared hash table, which means that current backend is using this GTT.
+ */
+static void
+gtt_storage_checkin(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			found;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *)&(fnode), 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_global_temporary_table.")));
+	}
+
+	if (!found)
+	{
+		int			wordnum;
+
+		/* init bitmap */
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	/* record itself in bitmap */
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+/*
+ * Remove the GTT relid record from the shared hash table which means that current backend is
+ * not use this GTT.
+ */
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	if (!skiplock)
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		if (!skiplock)
+			LWLockRelease(&gtt_shared_ctl->lock);
+
+		if (isCommit)
+			elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+	/* remove itself from bitmap */
+	bms_del_member(entry->map, MyBackendId);
+
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+
+	if (!skiplock)
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+	return;
+}
+
+/*
+ * Gets usage information for a GTT from shared hash table.
+ * The information is in the form of bitmap.
+ * Quickly copy the entire bitmap from shared memory and return it.
+ * that to avoid holding locks for a long time.
+ */
+static Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	Bitmapset		*map_copy = NULL;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		return NULL;
+	}
+
+	Assert(entry->map);
+
+	/* copy the entire bitmap */
+	if (!bms_is_empty(entry->map))
+		map_copy = bms_copy(entry->map);
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return map_copy;
+}
+
+/*
+ * Check if there are other backends using this GTT besides the current backend.
+ */
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			in_use = false;
+	int			num_use = 0;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		return false;
+	}
+
+	Assert(entry->map);
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+	/* how many backend are using this GTT */
+	num_use = bms_num_members(entry->map);
+	if (num_use == 0)
+		in_use = false;
+	else if (num_use == 1)
+	{
+		/* check if this is itself */
+		if(bms_is_member(MyBackendId, entry->map))
+			in_use = false;
+		else
+			in_use = true;
+	}
+	else
+		in_use = true;
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return in_use;
+}
+
+/*
+ * Record GTT information to local hash.
+ * They include GTT storage info, transaction info and statistical info.
+ */
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+	gtt_local_hash_entry		*entry;
+	MemoryContext			oldcontext;
+	gtt_relfilenode			*new_node = NULL;
+	Oid				relid = RelationGetRelid(rel);
+	int				natts = 0;
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	/* First time through: initialize the hash table */
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		HASHCTL		ctl;
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_info_context =
+			AllocSetContextCreate(CacheMemoryContext,
+								"gtt info context",
+								ALLOCSET_DEFAULT_SIZES);
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		ctl.hcxt = gtt_info_context;
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+	}
+
+	Assert(CacheMemoryContext);
+	Assert(gtt_info_context);
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	entry = gtt_search_by_relid(relid, true);
+	if (!entry)
+	{
+		bool		found = false;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash,
+						(void *) &relid, HASH_ENTER, &found);
+
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			/* record the on commit clause */
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS, true);
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			gtt_storage_checkin(relid);
+		}
+	}
+
+	/* record storage info relstat columnstats and transaction info to relfilenode list */
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	new_node->natts = 0;
+	new_node->attnum = NULL;
+	new_node->att_stat_tups = NULL;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* init column stats structure */
+	natts = RelationGetNumberOfAttributes(rel);
+	new_node->attnum = palloc0(sizeof(int) * natts);
+	new_node->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+	new_node->natts = natts;
+
+	/* only heap have transaction info */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+
+		/**/
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+
+	/* Registration callbacks are used to trigger cleanup during process exit */
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+/*
+ * Remove GTT information from local hash when transaction commit/rollback.
+ */
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	gtt_relfilenode		*d_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+		return;
+	}
+
+	d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+		else
+		{
+			/* rollback transaction */
+			if (entry->relfilenode_list == NIL)
+			{
+				if (entry->relkind == RELKIND_RELATION ||
+					entry->relkind == RELKIND_SEQUENCE)
+					gtt_storage_checkout(relid, false, isCommit);
+
+				hash_search(gtt_storage_local_hash,
+						(void *) &(relid), HASH_REMOVE, NULL);
+			}
+
+			return;
+		}
+	}
+
+	/* Clean up transaction info from Local order list and MyProc */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+
+		/* this is valid relfrozenxid */
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	/* delete relfilenode from rel entry */
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	gtt_free_statistics(d_rnode);
+
+	if (entry->relfilenode_list == NIL)
+	{
+		/* this means we truncate this GTT at current backend */
+
+		/* tell shared hash that current backend will no longer use this GTT */
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(relid, false, isCommit);
+
+		hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_REMOVE, NULL);
+	}
+
+	return;
+}
+
+/*
+ * Check if current backend is using this GTT.
+ */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool			found = false;
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (!OidIsValid(relid))
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry)
+		found = true;
+
+	return found;
+}
+
+/*
+ * When backend exit, bulk cleaning all GTT storage and local buffer of this backend.
+ */
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS		status;
+	gtt_local_hash_entry	*entry;
+	SMgrRelation		*srels = NULL;
+	Oid			*relids = NULL;
+	char			*relkinds = NULL;
+	int			nrels = 0,
+				nfiles = 0,
+				maxrels = 0,
+				maxfiles = 0,
+				i = 0;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	/* Search all relfilenode for GTT in current backend */
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel;
+			RelFileNode		rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel = smgropen(rnode, MyBackendId);
+
+			if (maxfiles == 0)
+			{
+				maxfiles = 32;
+				srels = palloc(sizeof(SMgrRelation) * maxfiles);
+			}
+			else if (maxfiles <= nfiles)
+			{
+				maxfiles *= 2;
+				srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+			}
+
+			srels[nfiles++] = srel;
+		}
+
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			relids  = palloc(sizeof(Oid) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			relids  = repalloc(relids , sizeof(Oid) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		relkinds[nrels] = entry->relkind;
+		relids[nrels] = entry->relid;
+		nrels++;
+	}
+
+	/* drop local buffer and storage */
+	if (nfiles > 0)
+	{
+		/* Need to ensure we have a usable transaction. */
+                AbortOutOfAnyTransaction();
+
+		smgrdounlinkall(srels, nfiles, false);
+		for (i = 0; i < nfiles; i++)
+			smgrclose(srels[i]);
+
+		pfree(srels);
+	}
+
+	if (nrels)
+	{
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+		for (i = 0; i < nrels; i++)
+		{
+			/* tell shared hash */
+			if (relkinds[i] == RELKIND_RELATION ||
+				relkinds[i] == RELKIND_SEQUENCE)
+				gtt_storage_checkout(relids[i], true, false);
+		}
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+		pfree(relids);
+		pfree(relkinds);
+	}
+
+	/* set to global area */
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;
+
+	return;
+}
+
+/*
+ * Update GTT relstats(relpage/reltuple/relallvisible)
+ * to local hash.
+ */
+void
+up_gtt_relstats(Oid relid,
+					BlockNumber num_pages,
+					double num_tuples,
+					BlockNumber num_all_visible_pages,
+					TransactionId relfrozenxid,
+					TransactionId relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (!OidIsValid(relid))
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (num_pages > 0 &&
+		gtt_rnode->relpages != (int32)num_pages)
+		gtt_rnode->relpages = (int32)num_pages;
+
+	if (num_tuples > 0 &&
+		gtt_rnode->reltuples != (float4)num_tuples)
+		gtt_rnode->reltuples = (float4)num_tuples;
+
+	/* only heap contain transaction information and relallvisible */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (num_all_visible_pages > 0 &&
+			gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+		{
+			gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+		}
+
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNextTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			/* set to local order list */
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			/* set to global area */
+			set_gtt_session_relfrozenxid();
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search GTT relstats(relpage/reltuple/relallvisible)
+ * from local has.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update GTT info(definition is same as pg_statistic)
+ * to local hash.
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	MemoryContext		oldcontext;
+	int			i = 0;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return;
+
+	Assert(entry->relid == reloid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (gtt_rnode->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	/* switch context to gtt_info_context for store tuple at heap_form_tuple */
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == 0)
+		{
+			gtt_rnode->attnum[i] = attnum;
+			break;
+		}
+		else if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			heap_freetuple(gtt_rnode->att_stat_tups[i]);
+			gtt_rnode->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < gtt_rnode->natts);
+	Assert(gtt_rnode->att_stat_tups[i] == NULL);
+	gtt_rnode->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search GTT statistic info(definition is same as pg_statistic)
+ * from local hash.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	int			i = 0;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return NULL;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return NULL;
+
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			return gtt_rnode->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Insert a RelfrozenXID into the list and keep the list in order.
+ */
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int		i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Remove a RelfrozenXID from order list gtt_session_relfrozenxid_list.
+ */
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+/*
+ * Update of backend Level oldest relfrozenxid to MyProc.
+ * This makes each backend's oldest RelFrozenxID globally visible.
+ */
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->backend_gtt_frozenxid != gtt_frozenxid)
+		MyProc->backend_gtt_frozenxid = gtt_frozenxid;
+}
+
+/*
+ * Get GTT column level data statistics.
+ */
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate		*tupstore;
+	HeapTuple		tuple;
+	Relation		rel = NULL;
+	Oid			reloid = PG_GETARG_OID(0);
+	int			attnum = PG_GETARG_INT32(1);
+	char			rel_persistence;
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	/* get data from local hash */
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum		values[31];
+		bool		isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, NoLock);
+	relation_close(pg_tatistic, AccessShareLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get GTT table level data statistics.
+ */
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate	*tupstore;
+	TupleDesc	tupdesc;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid			reloid = PG_GETARG_OID(0);
+	Oid			relnode = 0;
+	char			rel_persistence;
+	BlockNumber		relpages = 0;
+	BlockNumber		relallvisible = 0;
+	uint32			relfrozenxid = 0;
+	uint32			relminmxid = 0;
+	double			reltuples = 0;
+	Relation		rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[6];
+		bool	isnull[6];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get a list of backend pids that are currently using this GTT.
+ */
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	PGPROC			*proc = NULL;
+	Bitmapset		*map = NULL;
+	Tuplestorestate		*tupstore;
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	HeapTuple		tuple;
+	Oid			reloid = PG_GETARG_OID(0);
+	char			rel_persistence;
+	Relation		rel = NULL;
+	pid_t			pid = 0;
+	int				backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	/* get data from share hash */
+	map = copy_active_gtt_bitmap(reloid);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			/* backendid map to process pid */
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get backend level oldest relfrozenxid of each backend using GTT in current database.
+ */
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate		*tupstore;
+	int			*pids = NULL;
+	uint32			*xids = NULL;
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	HeapTuple		tuple;
+	int			num_xid = MaxBackends + 1;
+	int			i = 0;
+	int			j = 0;
+	uint32			oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+
+	/* Get backend level oldest relfrozenxid in all backend that in MyDatabaseId use GTT */
+	oldest = list_all_backend_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+/*
+ * In order to build the GTT index, force enable GTT'index.
+ */
+void
+force_enable_gtt_index(Relation index)
+{
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(RelationGetRelid(index)));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+/*
+ * Fix the local state of the GTT's index.
+ */
+void
+gtt_fix_index_backend_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid heapOid = index->rd_index->indrelid;
+
+	/* Must be GTT */
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	/*
+	 * If this GTT is not initialized in the current backend,
+	 * its index status is temporarily set to invalid(local relcache).
+	 */
+	if (gtt_storage_attached(heapOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+/*
+ * During the SQL initialization of the executor (InitPlan)
+ * Initialize storage of GTT GTT'indexes and build empty index.
+ */
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int		i;
+	Oid		toastrelid;
+
+	if (!(operation == CMD_UTILITY || operation == CMD_INSERT))
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	/* Each GTT is initialized once in each backend */
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	/* init heap storage */
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+
+		index_build(relation, index, info, true, false);
+	}
+
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+
+		/* init index storage */
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid			indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+			/* build empty index */
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+/*
+ * Release the data structure memory used to store GTT storage info.
+ */
+static void
+gtt_free_statistics(gtt_relfilenode *rnode)
+{
+	int i;
+
+	Assert(rnode);
+
+	for (i = 0; i < rnode->natts; i++)
+	{
+		if (rnode->att_stat_tups[i])
+		{
+			heap_freetuple(rnode->att_stat_tups[i]);
+			rnode->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (rnode->attnum)
+		pfree(rnode->attnum);
+
+	if (rnode->att_stat_tups)
+		pfree(rnode->att_stat_tups);
+
+	pfree(rnode);
+
+	return;
+}
+
+/*
+ * Get the current relfilenode of this GTT.
+ */
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
+/*
+ * Get a relfilenode used by this GTT during the transaction life cycle.
+ */
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok)
+{
+	gtt_relfilenode		*rnode = NULL;
+	ListCell		*lc;
+
+	Assert(entry);
+
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == relfilenode)
+		{
+			rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (!missing_ok && rnode == NULL)
+		elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+	return rnode;
+}
+
+/*
+ * Get one GTT info from local hash.
+ */
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry == NULL && !missing_ok)
+		elog(ERROR, "relid %u not found in local hash", relid);
+
+	return entry;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 55f6e3711d8..fc75533263b 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 8bfb2ad9584..500305e8ae8 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -104,7 +105,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -185,6 +186,17 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -601,14 +613,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]));
 		}
 
 		/*
@@ -1633,7 +1646,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;
@@ -1735,31 +1748,48 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/*
+		 * For global temporary table,
+		 * Update column statistic to localhash, not catalog.
+		 */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 9d22f648a84..a44eefa1f6b 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/toasting.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
@@ -390,6 +391,22 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+	{
+		if (gtt_storage_attached(RelationGetRelid(OldHeap)))
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("not support cluster global temporary table yet")));
+
+		relation_close(OldHeap, AccessExclusiveLock);
+		pgstat_progress_end_command();
+		return;
+	}
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
@@ -585,6 +602,8 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
 	TransactionId frozenXid;
 	MultiXactId cutoffMulti;
 
+	Assert(!RELATION_IS_GLOBAL_TEMP(OldHeap));
+
 	/* Mark the correct index as clustered */
 	if (OidIsValid(indexOid))
 		mark_index_clustered(OldHeap, indexOid, true);
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 53f48531419..c03191cce94 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -289,7 +289,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP_ON_CURRENT_SESSION(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, whereClause,
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 40a54ad0bd7..7895e7d99b9 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -30,6 +30,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/copyfrom_internal.h"
 #include "commands/progress.h"
@@ -659,6 +660,9 @@ CopyFrom(CopyFromState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	/* Check and init global temporary table storage in current backend */
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	/*
 	 * Set up a ModifyTableState so we can let FDW(s) init themselves for
 	 * foreign-table result relation(s).
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index c14ca27c5ed..d65ae895356 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -111,6 +111,7 @@ struct ReindexIndexCallbackState
 {
 	ReindexParams params;		/* options from statement */
 	Oid			locked_table_oid;	/* tracks previously locked table */
+	LOCKMODE	lockmode;
 };
 
 /*
@@ -570,7 +571,7 @@ DefineIndex(Oid relationId,
 	 * is more efficient.  Do this before any use of the concurrent option is
 	 * done.
 	 */
-	if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+	if (stmt->concurrent && !RelpersistenceTsTemp(get_rel_persistence(relationId)))
 		concurrent = true;
 	else
 		concurrent = false;
@@ -2581,9 +2582,9 @@ ReindexIndex(RangeVar *indexRelation, ReindexParams *params, bool isTopLevel)
 	 */
 	state.params = *params;
 	state.locked_table_oid = InvalidOid;
+	state.lockmode = AccessShareLock;
 	indOid = RangeVarGetRelidExtended(indexRelation,
-									  (params->options & REINDEXOPT_CONCURRENTLY) != 0 ?
-									  ShareUpdateExclusiveLock : AccessExclusiveLock,
+									  AccessShareLock,
 									  0,
 									  RangeVarCallbackForReindexIndex,
 									  &state);
@@ -2594,11 +2595,25 @@ ReindexIndex(RangeVar *indexRelation, ReindexParams *params, bool isTopLevel)
 	 */
 	persistence = get_rel_persistence(indOid);
 	relkind = get_rel_relkind(indOid);
+	if (persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		LOCKMODE	lockmode;
+
+		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0)
+			lockmode = ShareUpdateExclusiveLock;
+		else
+			lockmode = AccessExclusiveLock;
+
+		/* lock heap first */
+		Assert(OidIsValid(state.locked_table_oid));
+		LockRelationOid(state.locked_table_oid, lockmode);
+		LockRelationOid(indOid, lockmode);
+	}
 
 	if (relkind == RELKIND_PARTITIONED_INDEX)
 		ReindexPartitions(indOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 persistence != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(persistence))
 		ReindexRelationConcurrently(indOid, params);
 	else
 	{
@@ -2620,15 +2635,7 @@ RangeVarCallbackForReindexIndex(const RangeVar *relation,
 {
 	char		relkind;
 	struct ReindexIndexCallbackState *state = arg;
-	LOCKMODE	table_lockmode;
-
-	/*
-	 * Lock level here should match table lock in reindex_index() for
-	 * non-concurrent case and table locks used by index_concurrently_*() for
-	 * concurrent case.
-	 */
-	table_lockmode = (state->params.options & REINDEXOPT_CONCURRENTLY) != 0 ?
-		ShareUpdateExclusiveLock : ShareLock;
+	LOCKMODE	table_lockmode = state->lockmode;
 
 	/*
 	 * If we previously locked some other index's heap, and the name we're
@@ -2689,6 +2696,8 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 {
 	Oid			heapOid;
 	bool		result;
+	char		relpersistence;
+	int 		reindex_flags = 0;
 
 	/*
 	 * The lock level used here should match reindex_relation().
@@ -2699,15 +2708,27 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 	 * locks on our temporary table.
 	 */
 	heapOid = RangeVarGetRelidExtended(relation,
-									   (params->options & REINDEXOPT_CONCURRENTLY) != 0 ?
-									   ShareUpdateExclusiveLock : ShareLock,
+									   AccessShareLock,
 									   0,
 									   RangeVarCallbackOwnsTable, NULL);
 
+	relpersistence = get_rel_persistence(heapOid);
+	if (relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		LOCKMODE	lockmode;
+
+		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0)
+			lockmode = ShareUpdateExclusiveLock;
+		else
+			lockmode = ShareLock;
+
+		LockRelationOid(heapOid, lockmode);
+	}
+
 	if (get_rel_relkind(heapOid) == RELKIND_PARTITIONED_TABLE)
 		ReindexPartitions(heapOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(relpersistence))
 	{
 		result = ReindexRelationConcurrently(heapOid, params);
 
@@ -2721,9 +2742,14 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 		ReindexParams newparams = *params;
 
 		newparams.options |= REINDEXOPT_REPORT_PROGRESS;
+
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			reindex_flags |= REINDEX_REL_PROCESS_GLOBAL_TEMP;
+
+		reindex_flags |= REINDEX_REL_PROCESS_TOAST;
+		reindex_flags |= REINDEX_REL_CHECK_CONSTRAINTS;
 		result = reindex_relation(heapOid,
-								  REINDEX_REL_PROCESS_TOAST |
-								  REINDEX_REL_CHECK_CONSTRAINTS,
+								  reindex_flags,
 								  &newparams);
 		if (!result)
 			ereport(NOTICE,
@@ -3122,7 +3148,7 @@ ReindexMultipleInternal(List *relids, ReindexParams *params)
 			   relkind != RELKIND_PARTITIONED_TABLE);
 
 		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			relpersistence != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(relpersistence))
 		{
 			ReindexParams newparams = *params;
 
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 62465bacd81..ef37f79ba68 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -51,12 +51,32 @@ LockTableCommand(LockStmt *lockstmt)
 		RangeVar   *rv = (RangeVar *) lfirst(p);
 		bool		recurse = rv->inh;
 		Oid			reloid;
+		LOCKMODE	lockmode = lockstmt->mode;
+		char		relpersistence;
 
-		reloid = RangeVarGetRelidExtended(rv, lockstmt->mode,
-										  lockstmt->nowait ? RVR_NOWAIT : 0,
+		reloid = RangeVarGetRelidExtended(rv, NoLock, 0,
 										  RangeVarCallbackForLockTable,
 										  (void *) &lockstmt->mode);
 
+		relpersistence = get_rel_persistence(reloid);
+		if (relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			if (!lockstmt->nowait)
+				LockRelationOid(reloid, lockmode);
+			else if (!ConditionalLockRelationOid(reloid, lockmode))
+			{
+				/* try to throw error by name; relation could be deleted... */
+				char	   *relname = get_rel_name(reloid);
+
+				if (!relname)
+					return;		/* child concurrently dropped, just skip it */
+				ereport(ERROR,
+						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						 errmsg("could not obtain lock on relation \"%s\"",
+								relname)));
+			}
+		}
+
 		if (get_rel_relkind(reloid) == RELKIND_VIEW)
 			LockViewRecurse(reloid, lockstmt->mode, lockstmt->nowait, NIL);
 		else if (recurse)
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 72bfdc07a49..e5257f610f6 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -220,9 +223,12 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	rel = table_open(seqoid, AccessExclusiveLock);
 	tupDesc = RelationGetDescr(rel);
 
-	/* now initialize the sequence's data */
-	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	if (!RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		/* now initialize the sequence's data */
+		tuple = heap_form_tuple(tupDesc, value, null);
+		fill_seq_with_data(rel, tuple);
+	}
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -275,8 +281,6 @@ ResetSequence(Oid seq_relid)
 	Buffer		buf;
 	HeapTupleData seqdatatuple;
 	HeapTuple	tuple;
-	HeapTuple	pgstuple;
-	Form_pg_sequence pgsform;
 	int64		startv;
 
 	/*
@@ -287,12 +291,7 @@ ResetSequence(Oid seq_relid)
 	init_sequence(seq_relid, &elm, &seq_rel);
 	(void) read_seq_tuple(seq_rel, &buf, &seqdatatuple);
 
-	pgstuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seq_relid));
-	if (!HeapTupleIsValid(pgstuple))
-		elog(ERROR, "cache lookup failed for sequence %u", seq_relid);
-	pgsform = (Form_pg_sequence) GETSTRUCT(pgstuple);
-	startv = pgsform->seqstart;
-	ReleaseSysCache(pgstuple);
+	startv = get_seqence_start_value(seq_relid);
 
 	/*
 	 * Copy the existing sequence tuple.
@@ -451,6 +450,15 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 
 	init_sequence(relid, &elm, &seqrel);
 
+	if (RELATION_IS_GLOBAL_TEMP(seqrel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(seqrel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary sequence %s when other backend attached it.",
+						RelationGetRelationName(seqrel))));
+	}
+
 	rel = table_open(SequenceRelationId, RowExclusiveLock);
 	seqtuple = SearchSysCacheCopy1(SEQRELID,
 								   ObjectIdGetDatum(relid));
@@ -611,7 +619,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +944,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1153,6 +1161,14 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	/* Initializes the storage for sequence which the global temporary table belongs. */
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1927,3 +1943,58 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+/*
+ * Get the startValue of the sequence from syscache.
+ */
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+/*
+ * Initialize sequence which global temporary table belongs.
+ */
+void
+gtt_init_seq(Relation rel)
+{
+	Datum		value[SEQ_COL_LASTCOL];
+	bool		null[SEQ_COL_LASTCOL];
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL - 1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+	null[SEQ_COL_LASTVAL - 1] = false;
+
+	value[SEQ_COL_LOG - 1] = Int64GetDatum((int64)0);
+	null[SEQ_COL_LOG - 1] = false;
+
+	value[SEQ_COL_CALLED - 1] = BoolGetDatum(false);
+	null[SEQ_COL_CALLED - 1] = false;
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index dbee6ae199f..a5629506e20 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -118,6 +119,7 @@ typedef struct OnCommitItem
 	 */
 	SubTransactionId creating_subid;
 	SubTransactionId deleting_subid;
+	bool			 is_global_temp;
 } OnCommitItem;
 
 static List *on_commits = NIL;
@@ -602,7 +604,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
 static char GetAttributeCompression(Oid atttypid, char *compression);
-
+static OnCommitAction gtt_oncommit_option(List *options);
 
 /* ----------------------------------------------------------------
  *		DefineRelation
@@ -647,6 +649,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -658,7 +661,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * Check consistency of arguments
 	 */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !RelpersistenceTsTemp(stmt->relation->relpersistence))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -688,7 +691,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	if (RelpersistenceTsTemp(stmt->relation->relpersistence)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -789,6 +792,50 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* For global temporary table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		if (!(relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE))
+			elog(ERROR, "Only support global temporary regular table.");
+
+		/* Check parent table */
+		if (inheritOids)
+			elog(ERROR, "Not support global temporary partition table or inherit table.");
+
+		/* Check oncommit clause and save to reloptions */
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temporary table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1415,7 +1462,7 @@ RemoveRelations(DropStmt *drop)
 		 * relation persistence cannot be known without its OID.
 		 */
 		if (drop->concurrent &&
-			get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(get_rel_persistence(relOid)))
 		{
 			Assert(list_length(drop->objects) == 1 &&
 				   drop->removeType == OBJECT_INDEX);
@@ -1624,9 +1671,9 @@ ExecuteTruncate(TruncateStmt *stmt)
 		Relation	rel;
 		bool		recurse = rv->inh;
 		Oid			myrelid;
-		LOCKMODE	lockmode = AccessExclusiveLock;
+		LOCKMODE	lockmode;
 
-		myrelid = RangeVarGetRelidExtended(rv, lockmode,
+		myrelid = RangeVarGetRelidExtended(rv, AccessShareLock,
 										   0, RangeVarCallbackForTruncate,
 										   NULL);
 
@@ -1634,9 +1681,21 @@ ExecuteTruncate(TruncateStmt *stmt)
 		if (list_member_oid(relids, myrelid))
 			continue;
 
-		/* open the relation, we already hold a lock on it */
+		/* open the relation, we need hold a low-level lock first */
 		rel = table_open(myrelid, NoLock);
 
+		/*
+		 * Truncate global temp table only cleans up the data in current backend,
+		 * only low-level locks are required.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel))
+			lockmode = AccessShareLock;
+		else
+		{
+			lockmode = AccessExclusiveLock;
+			LockRelationOid(myrelid, lockmode);
+		}
+
 		/*
 		 * RangeVarGetRelidExtended() has done most checks with its callback,
 		 * but other checks with the now-opened Relation remain.
@@ -1886,6 +1945,7 @@ ExecuteTruncateGuts(List *explicit_rels,
 	foreach(cell, rels)
 	{
 		Relation	rel = (Relation) lfirst(cell);
+		LOCKMODE	lockmode;
 
 		/* Skip partitioned tables as there is nothing to do */
 		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
@@ -1936,6 +1996,19 @@ ExecuteTruncateGuts(List *explicit_rels,
 			continue;
 		}
 
+		/*
+		 * Skip the global temporary table that is not initialized for storage
+		 * in current backend.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel))
+		{
+			lockmode = AccessShareLock;
+			if (!gtt_storage_attached(RelationGetRelid(rel)))
+				continue;
+		}
+		else
+			lockmode = AccessExclusiveLock;
+
 		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
@@ -1954,6 +2027,7 @@ ExecuteTruncateGuts(List *explicit_rels,
 			Oid			heap_relid;
 			Oid			toast_relid;
 			ReindexParams reindex_params = {0};
+			int			reindex_flags = 0;
 
 			/*
 			 * This effectively deletes all rows in the table, and may be done
@@ -1981,17 +2055,21 @@ ExecuteTruncateGuts(List *explicit_rels,
 			if (OidIsValid(toast_relid))
 			{
 				Relation	toastrel = relation_open(toast_relid,
-													 AccessExclusiveLock);
+													 lockmode);
 
 				RelationSetNewRelfilenode(toastrel,
 										  toastrel->rd_rel->relpersistence);
 				table_close(toastrel, NoLock);
 			}
 
+			reindex_flags = REINDEX_REL_PROCESS_TOAST;
+			if (RELATION_IS_GLOBAL_TEMP(rel))
+				reindex_flags |= REINDEX_REL_PROCESS_GLOBAL_TEMP;
+
 			/*
 			 * Reconstruct the indexes to match, and we're done.
 			 */
-			reindex_relation(heap_relid, REINDEX_REL_PROCESS_TOAST,
+			reindex_relation(heap_relid, reindex_flags,
 							 &reindex_params);
 		}
 
@@ -3998,6 +4076,16 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temporary table only current backend use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -5329,6 +5417,24 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 
 			rel = table_open(tab->relid, NoLock);
 			find_composite_type_dependencies(rel->rd_rel->reltype, rel, NULL);
+
+			if (RELATION_IS_GLOBAL_TEMP(rel) && tab->rewrite > 0)
+			{
+				if (tab->chgPersistence)
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot change global temporary table persistence setting")));
+
+				if(gtt_storage_attached(tab->relid))
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("Only support alter global temporary table in an empty context."),
+						 errhint("Please create a new connection and execute ALTER TABLE on the new connection.")));
+
+				/* There is no need to override the whole temp table */
+				tab->rewrite = 0;
+			}
+
 			table_close(rel, NoLock);
 		}
 
@@ -5380,6 +5486,8 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot rewrite temporary tables of other sessions")));
 
+			Assert(!RELATION_IS_GLOBAL_TEMP(OldHeap));
+
 			/*
 			 * Select destination tablespace (same as original unless user
 			 * requested a change)
@@ -9011,6 +9119,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -13709,6 +13823,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -13908,6 +14025,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temporary table");
+
 	/* Check first if relation can be moved to new tablespace */
 	if (!CheckRelationTableSpaceMove(rel, newTableSpace))
 	{
@@ -14211,7 +14331,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(RelationGetSmgr(rel), dstrel, MAIN_FORKNUM,
@@ -15809,6 +15929,7 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
 	switch (rel->rd_rel->relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -16223,7 +16344,7 @@ AlterSeqNamespaces(Relation classRel, Relation rel,
  * Register a newly-created relation's ON COMMIT action.
  */
 void
-register_on_commit_action(Oid relid, OnCommitAction action)
+register_on_commit_action(Oid relid, OnCommitAction action, bool is_gloal_temp)
 {
 	OnCommitItem *oc;
 	MemoryContext oldcxt;
@@ -16242,6 +16363,7 @@ register_on_commit_action(Oid relid, OnCommitAction action)
 	oc->oncommit = action;
 	oc->creating_subid = GetCurrentSubTransactionId();
 	oc->deleting_subid = InvalidSubTransactionId;
+	oc->is_global_temp = is_gloal_temp;
 
 	/*
 	 * We use lcons() here so that ON COMMIT actions are processed in reverse
@@ -16287,6 +16409,7 @@ PreCommit_on_commit_actions(void)
 	ListCell   *l;
 	List	   *oids_to_truncate = NIL;
 	List	   *oids_to_drop = NIL;
+	List	   *oids_to_truncate_gtt = NIL;
 
 	foreach(l, on_commits)
 	{
@@ -16310,7 +16433,12 @@ PreCommit_on_commit_actions(void)
 				 * tables, as they must still be empty.
 				 */
 				if ((MyXactFlags & XACT_FLAGS_ACCESSEDTEMPNAMESPACE))
-					oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid);
+				{
+					if (oc->is_global_temp)
+						oids_to_truncate_gtt = lappend_oid(oids_to_truncate_gtt, oc->relid);
+					else
+						oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid);
+				}
 				break;
 			case ONCOMMIT_DROP:
 				oids_to_drop = lappend_oid(oids_to_drop, oc->relid);
@@ -16327,7 +16455,10 @@ PreCommit_on_commit_actions(void)
 	 * exists at truncation time.
 	 */
 	if (oids_to_truncate != NIL)
-		heap_truncate(oids_to_truncate);
+		heap_truncate(oids_to_truncate, false);
+
+	if (oids_to_truncate_gtt != NIL)
+		heap_truncate(oids_to_truncate_gtt, true);
 
 	if (oids_to_drop != NIL)
 	{
@@ -17326,6 +17457,13 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot attach temporary relation of another session as partition")));
 
+	/* If the parent is permanent, so must be all of its partitions. */
+	if (attachrel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach a global temporary relation as partition of permanent relation \"%s\"",
+						RelationGetRelationName(rel))));
+
 	/* Check if there are any columns in attachrel that aren't in the parent */
 	tupleDesc = RelationGetDescr(attachrel);
 	natts = tupleDesc->natts;
@@ -18761,3 +18899,40 @@ GetAttributeCompression(Oid atttypid, char *compression)
 
 	return cmethod;
 }
+
+/*
+ * Parse the on commit clause for the temporary table
+ */
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			bool	res = false;
+			char	*sval = defGetString(def);
+
+			/* It has to be a Boolean value */
+			if (!parse_bool(sval, &res))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
+
+			if (res)
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 5c4bc15b441..77c0f384ed1 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"
@@ -1315,6 +1316,22 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = RELATION_IS_GLOBAL_TEMP(relation);
+
+	 /* For global temporary table */
+	if (is_gtt)
+	{
+		/* Store relation statistics and transaction information to the localhash */
+		up_gtt_relstats(RelationGetRelid(relation),
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+
+		/* Update relation statistics to local relcache */
+		relation->rd_rel->relpages = (int32) num_pages;
+		relation->rd_rel->reltuples = (float4) num_tuples;
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1328,17 +1345,23 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (!is_gtt &&
+		pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (!is_gtt &&
+		pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (!is_gtt &&
+		pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1351,7 +1374,15 @@ vac_update_relstats(Relation relation,
 		/*
 		 * If we didn't find any indexes, reset relhasindex.
 		 */
-		if (pgcform->relhasindex && !hasindex)
+		if (is_gtt &&
+			RelationGetIndexList(relation) != NIL)
+		{
+			/*
+			 * Global temporary tables may contain indexes that are not valid locally.
+			 * The catalog should not be updated based on local invalid index.
+			 */
+		}
+		else if (pgcform->relhasindex && !hasindex)
 		{
 			pgcform->relhasindex = false;
 			dirty = true;
@@ -1383,7 +1414,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNextTransactionId(),
@@ -1394,7 +1426,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1502,6 +1535,13 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/*
+		 * The relfrozenxid for a global temporary talble is stored in localhash,
+		 * not pg_class, See list_all_session_gtt_frozenxids()
+		 */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1559,6 +1599,43 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/* If enable global temporary table */
+	if (max_active_gtt > 0)
+	{
+		TransactionId	safe_age;
+		/*  */
+		TransactionId	oldest_gtt_frozenxid =
+			list_all_backend_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid))
+		{
+			safe_age = oldest_gtt_frozenxid + vacuum_gtt_defer_check_age;
+			if (safe_age < FirstNormalTransactionId)
+				safe_age += FirstNormalTransactionId;
+
+			/*
+			 * We tolerate that the minimum age of gtt is less than
+			 * the minimum age of conventional tables, otherwise it will
+			 * throw warning message.
+			 */
+			if (TransactionIdIsNormal(safe_age) &&
+				TransactionIdPrecedes(safe_age, newFrozenXid))
+			{
+				ereport(WARNING,
+					(errmsg("global temp table oldest relfrozenxid %u is the oldest in the entire db",
+							oldest_gtt_frozenxid),
+					 errdetail("The oldest relfrozenxid in pg_class is %u", newFrozenXid),
+					 errhint("If they differ greatly, please consider cleaning up the data in global temp table.")));
+			}
+
+			/*
+			 * We need to ensure that the clog required by gtt is not cleand.
+			 */
+			if (TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+				newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1910,6 +1987,19 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	/*
+	 * Skip those global temporary table that are not initialized in
+	 * current backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel) &&
+		!gtt_storage_attached(RelationGetRelid(rel)))
+	{
+		relation_close(rel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 4df05a0b33d..4c181e2e14e 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -527,6 +527,12 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	/* Global temporary table are not sensible. */
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b3ce4bae530..611e3f18a70 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -784,6 +784,10 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		/* This is one kind of temp table */
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 5c723bc54e1..a7edceb1a5c 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
@@ -533,6 +534,9 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	/* Init storage for partitioned global temporary table in current backend */
+	init_gtt_storage(mtstate->operation, leaf_part_rri);
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d328856ae5b..4e2bbb224cc 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -38,6 +38,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -633,6 +634,9 @@ ExecInsert(ModifyTableState *mtstate,
 		resultRelInfo->ri_IndexRelationDescs == NULL)
 		ExecOpenIndices(resultRelInfo, onconflict != ONCONFLICT_NONE);
 
+	/* Init storage for global temporary table in current backend */
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	/*
 	 * BEFORE ROW INSERT Triggers.
 	 *
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 296dd75c1b6..d971aea2546 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -48,7 +48,7 @@
 #include "partitioning/partprune.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/lsyscache.h"
-
+#include "utils/rel.h"
 
 /* results of subquery_is_pushdown_safe */
 typedef struct pushdown_safety_info
@@ -619,7 +619,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			if (RelpersistenceTsTemp(get_rel_persistence(rte->relid)))
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 1e42d75465e..15a76b2326b 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6070,7 +6070,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
-	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+	if (RELATION_IS_TEMP(heap) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index c5194fdbbf2..38d7c658541 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -30,6 +30,7 @@
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temporary table in current backend */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 146ee8dd1ea..2d4e9393f00 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2907,6 +2907,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 e3068a374ee..159465e0185 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3410,17 +3410,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; }
@@ -11683,19 +11677,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 c5c3f26ecf1..2a2b2789077 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -82,6 +82,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);
 
 
 /*
@@ -3665,3 +3666,53 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * Like function isQueryUsingTempRelation_walker
+ * return true if any relation underlying
+ * the query is a global temporary table.
+ */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* Check if the query uses global temporary table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 1d3ee53244d..5e4feacd847 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -447,6 +447,13 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	/*
+	 * If a sequence is bound to a global temporary table, then the sequence
+	 * must been "global temporary"
+	 */
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
@@ -3326,6 +3333,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		cxt.isforeign = false;
 	}
 	cxt.relation = stmt->relation;
+	/* Sets the table persistence to the context */
+	cxt.relation->relpersistence = RelationGetRelPersistence(rel);
 	cxt.rel = rel;
 	cxt.inhRelations = NIL;
 	cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 3b3df8fa8cc..ca6c68879d2 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2111,6 +2111,14 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/*
+			 * Aotuvacuum cannot vacuum the private data stored in each backend
+			 * that belongs to global temporary table, so skip them.
+			 */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2177,7 +2185,7 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		if (RelpersistenceTsTemp(classForm->relpersistence))
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index e88e4e918b0..10eaac892a8 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlogutils.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -2934,6 +2935,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * Returns 0 if this global temporary table is not initialized in current
+	 * backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 9fa3e0631e6..cc3eb928bc6 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -23,6 +23,7 @@
 #include "access/syncscan.h"
 #include "access/twophase.h"
 #include "commands/async.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
@@ -143,6 +144,7 @@ CalculateShmemSize(int *num_semaphores)
 	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
@@ -246,6 +248,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* For global temporary table shared hashtable */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index bd3c7a47fe2..22481374778 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -65,6 +65,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -5131,3 +5132,78 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temporary table.
+ */
+int
+list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct		*arrayP = procArray;
+	TransactionId		result = InvalidTransactionId;
+	int			index;
+	uint8			flags = 0;
+	int			i = 0;
+
+	/* return 0 if feature is disabled */
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+		*n = 0;
+	}
+
+	/* Disable in standby node */
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		uint8           statusFlags = ProcGlobal->statusFlags[index];
+
+		if (statusFlags & flags)
+			continue;
+
+		/* Fetch all backend that is belonging to MyDatabaseId */
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->backend_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->backend_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->backend_gtt_frozenxid, result))
+				result = proc->backend_gtt_frozenxid;
+
+			/* save backend pid and backend level oldest relfrozenxid */
+			if (max_size > 0)
+			{
+				pids[i] = proc->pid;
+				xids[i] = proc->backend_gtt_frozenxid;
+				i++;
+			}
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (max_size > 0)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 862097352bb..4edd3b31f7a 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -176,7 +176,9 @@ static const char *const BuiltinTrancheNames[] = {
 	/* LWTRANCHE_PARALLEL_APPEND: */
 	"ParallelAppend",
 	/* LWTRANCHE_PER_XACT_PREDICATE_LIST: */
-	"PerXactPredicateList"
+	"PerXactPredicateList",
+	/* LWTRANCHE_GTT_CTL */
+	"GlobalTempTableControl"
 };
 
 StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index b7d9da0aa9f..8051f2053f9 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -393,6 +393,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;	/* init backend level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
@@ -578,6 +579,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;	/* init backend level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index d5a7fb13f3c..8225cf6219f 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -982,6 +982,13 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/*
+			 * For global temporary table ,each backend has its own storage,
+			 * also only sees its own storage. Use Backendid to identify them.
+			 */
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 0c8c05f6c2e..699507a24c7 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -108,6 +108,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -5115,12 +5116,26 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								/* For global temporary table, get statistic data from localhash */
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -5368,15 +5383,28 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6800,6 +6828,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;
@@ -6817,6 +6846,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6828,6 +6865,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;
@@ -6843,6 +6882,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7761,6 +7808,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))
@@ -7773,6 +7822,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7785,6 +7843,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.
@@ -7804,6 +7864,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 4ebaa552a27..78c33d2ac87 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -3113,6 +3114,19 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		/* For global temporary table, get statistic data from localhash */
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3e..94fdb998aae 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -65,6 +65,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1116,6 +1117,28 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+
+				/* For global temporary table, get relstat data from localhash */
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+
+				/* And put them to local relcache */
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1173,6 +1196,8 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			/* The state of the global temporary table's index may need to be set */
+			gtt_fix_index_backend_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -1300,7 +1325,22 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+			/*
+			 * For global temporary table, get the latest relfilenode
+			 * from localhash and put it in relcache.
+			 */
+			if (OidIsValid(newrelnode) &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2251,6 +2291,9 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		/* The state of the global temporary table's index may need to be set */
+		gtt_fix_index_backend_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3489,6 +3532,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3598,28 +3645,39 @@ void
 RelationSetNewRelfilenode(Relation relation, char persistence)
 {
 	Oid			newrelfilenode;
-	Relation	pg_class;
-	HeapTuple	tuple;
+	Relation	pg_class = NULL;
+	HeapTuple	tuple = NULL;
 	Form_pg_class classform;
 	MultiXactId minmulti = InvalidMultiXactId;
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
 
+	/*
+	 * For global temporary table, storage information for the table is
+	 * maintained locally, not in catalog.
+	 */
+	bool		update_catalog = !RELATION_IS_GLOBAL_TEMP(relation);
+
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
 
-	/*
-	 * Get a writable copy of the pg_class tuple for the given relation.
-	 */
-	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+	memset(&classform, 0, sizeof(classform));
 
-	tuple = SearchSysCacheCopy1(RELOID,
-								ObjectIdGetDatum(RelationGetRelid(relation)));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "could not find tuple for relation %u",
-			 RelationGetRelid(relation));
-	classform = (Form_pg_class) GETSTRUCT(tuple);
+	if (update_catalog)
+	{
+		/*
+		 * Get a writable copy of the pg_class tuple for the given relation.
+		 */
+		pg_class = table_open(RelationRelationId, RowExclusiveLock);
+
+		tuple = SearchSysCacheCopy1(RELOID,
+									ObjectIdGetDatum(RelationGetRelid(relation)));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u",
+				 RelationGetRelid(relation));
+		classform = (Form_pg_class) GETSTRUCT(tuple);
+	}
 
 	/*
 	 * Schedule unlinking of the old storage at transaction commit.
@@ -3645,7 +3703,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;
@@ -3665,6 +3723,18 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 			break;
 	}
 
+	/* For global temporary table */
+	if (!update_catalog)
+	{
+		Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+		Assert(RELATION_IS_GLOBAL_TEMP(relation));
+		Assert(!RelationIsMapped(relation));
+
+		/* Make cache invalid and set new relnode to local cache. */
+		CacheInvalidateRelcache(relation);
+		relation->rd_node.relNode = relnode;
+	}
 	/*
 	 * If we're dealing with a mapped index, pg_class.relfilenode doesn't
 	 * change; instead we have to send the update to the relation mapper.
@@ -3674,7 +3744,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	 * possibly-inaccurate values of relpages etc, but those will be fixed up
 	 * later.
 	 */
-	if (RelationIsMapped(relation))
+	else if (RelationIsMapped(relation))
 	{
 		/* This case is only supported for indexes */
 		Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3720,9 +3790,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 		CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 	}
 
-	heap_freetuple(tuple);
+	if (update_catalog)
+	{
+		heap_freetuple(tuple);
 
-	table_close(pg_class, RowExclusiveLock);
+		table_close(pg_class, RowExclusiveLock);
+	}
 
 	/*
 	 * Make the pg_class row change or relation map change visible.  This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index d2ce4a84506..7717ee2ca13 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -44,6 +44,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "commands/prepare.h"
 #include "commands/trigger.h"
@@ -152,6 +153,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temporary table feature.
+ * table schema are still saved in catalog.
+ *
+ * num > 0 means allows the database to manage multiple active tables at the same time.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2127,6 +2140,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 "
@@ -2697,6 +2719,16 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"vacuum_gtt_defer_check_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("The defer check age of GTT, used to check expired data after vacuum."),
+			NULL
+		},
+		&vacuum_gtt_defer_check_age,
+		10000, 0, 1000000,
+		NULL, NULL, NULL
+	},
+
 	/*
 	 * See also CheckRequiredParameterValues() if this parameter changes
 	 */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a485fb2d070..80658c4f3ba 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2503,6 +2503,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
 		dopt->no_unlogged_table_data)
 		return;
 
+	/* Don't dump data in global temporary table/sequence */
+	if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		return;
+
 	/* Check that the data is not explicitly excluded */
 	if (simple_oid_list_member(&tabledata_exclude_oids,
 							   tbinfo->dobj.catId.oid))
@@ -15961,6 +15965,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
 		char	   *foreign = "";
+		char	   *table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -16014,9 +16019,15 @@ dumpTableSchema(Archive *fout, const 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);
 
@@ -16380,6 +16391,15 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 			}
 		}
 
+		/*
+		 * Transaction information for the global temporary table is not stored
+		 * in the pg_class.
+		 */
+		if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			Assert(tbinfo->frozenxid == 0);
+			Assert(tbinfo->minmxid == 0);
+		}
 		/*
 		 * In binary_upgrade mode, arrange to restore the old relfrozenxid and
 		 * relminmxid of all vacuumable relations.  (While vacuum.c processes
@@ -16387,7 +16407,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		 * of other relations; so this "if" lacks RELKIND_TOASTVALUE, and the
 		 * child toast table is handled below.)
 		 */
-		if (dopt->binary_upgrade &&
+		else if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
 			 tbinfo->relkind == RELKIND_MATVIEW))
 		{
@@ -17390,6 +17410,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	PQExpBuffer query = createPQExpBuffer();
 	PQExpBuffer delqry = createPQExpBuffer();
 	char	   *qseqname;
+	bool	   global_temp_seq = false;
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
@@ -17399,9 +17420,12 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
-						  "FROM pg_catalog.pg_sequence "
-						  "WHERE seqrelid = '%u'::oid",
+						  "seqcache, seqcycle, "
+						  "c.relpersistence "
+						  "FROM pg_catalog.pg_sequence s, "
+						  "pg_catalog.pg_class c "
+						  "WHERE seqrelid = '%u'::oid "
+						  "and s.seqrelid = c.oid",
 						  tbinfo->dobj.catId.oid);
 	}
 	else if (fout->remoteVersion >= 80400)
@@ -17446,6 +17470,9 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
 
+	if (fout->remoteVersion >= 140000)
+		global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
 	if (strcmp(seqtype, "smallint") == 0)
@@ -17523,9 +17550,13 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	}
 	else
 	{
-		appendPQExpBuffer(query,
-						  "CREATE SEQUENCE %s\n",
-						  fmtQualifiedDumpable(tbinfo));
+		appendPQExpBuffer(query, "CREATE ");
+
+		if (global_temp_seq)
+			appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+		appendPQExpBuffer(query, "SEQUENCE %s\n",
+								  fmtQualifiedDumpable(tbinfo));
 
 		if (strcmp(seqtype, "bigint") != 0)
 			appendPQExpBuffer(query, "    AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index ad5f3919956..f3819860096 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -88,7 +88,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -178,7 +178,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 5d9a26cf822..2de11d5d707 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -447,8 +449,17 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
 			 "         ON c.relnamespace = n.oid "
 			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
+			 CppAsString2(RELKIND_MATVIEW) ") AND ");
+
+	if (skip_gtt)
+	{
+		/* exclude global temp tables */
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+			"    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND ");
+	}
+
 	/* exclude possible orphaned temp tables */
+	snprintf(query + strlen(query), sizeof(query) - strlen(query),
 			 "    ((n.nspname !~ '^pg_temp_' AND "
 			 "      n.nspname !~ '^pg_toast_temp_' AND "
 			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 3628bd74a7b..bbb9b5ea13d 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -407,7 +407,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -645,7 +645,10 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+			/* exclude global temp tables */
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -656,7 +659,10 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+		/* exclude global temp tables */
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index ca0795f68ff..018a2effd4b 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -389,7 +389,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 90ff649be72..7a50197875b 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -4067,7 +4067,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 5cd58386689..e528739a0ca 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1059,6 +1059,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2542,6 +2544,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2755,6 +2760,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE", "SEQUENCE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 6ce480b49c1..dda3f3c5a60 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -85,7 +85,7 @@ extern Oid	heap_create_with_catalog(const char *relname,
 
 extern void heap_drop_with_catalog(Oid relid);
 
-extern void heap_truncate(List *relids);
+extern void heap_truncate(List *relids, bool is_global_temp);
 
 extern void heap_truncate_one_rel(Relation rel);
 
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 008f723e104..875b1003899 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -157,6 +157,7 @@ extern void reindex_index(Oid indexId, bool skip_constraint_checks,
 #define REINDEX_REL_CHECK_CONSTRAINTS		0x04
 #define REINDEX_REL_FORCE_INDEXES_UNLOGGED	0x08
 #define REINDEX_REL_FORCE_INDEXES_PERMANENT 0x10
+#define REINDEX_REL_PROCESS_GLOBAL_TEMP		0x20
 
 extern bool reindex_relation(Oid relid, int flags, ReindexParams *params);
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index fef9945ed8f..9176b7dcc07 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -172,6 +172,7 @@ DECLARE_INDEX(pg_class_tblspc_relfilenode_index, 3455, ClassTblspcRelfilenodeInd
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index d068d6532ec..fd5089388f2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5735,6 +5735,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '9874',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '9875',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '9876',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '9877',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 0ab32b44e91..92e9f8ba485 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,7 +22,7 @@
 /* GUC variables */
 extern int	wal_skip_threshold;
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationPreTruncate(Relation rel);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 00000000000..8a3d9558712
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,45 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern int		vacuum_gtt_defer_check_age;
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Oid relid,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void force_enable_gtt_index(Relation index);
+extern void gtt_fix_index_backend_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index 40544dd4c70..7b66d808fc5 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 336549cc5f0..3e8167134b7 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -86,7 +86,7 @@ extern void find_composite_type_dependencies(Oid typeOid,
 
 extern void check_of_type(HeapTuple typetuple);
 
-extern void register_on_commit_action(Oid relid, OnCommitAction action);
+extern void register_on_commit_action(Oid relid, OnCommitAction action, bool is_gloal_temp);
 extern void remove_on_commit_action(Oid relid);
 
 extern void PreCommit_on_commit_actions(void);
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 8336c2c5a29..bddcfe7256d 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index c86ccdaf608..6b395551c1d 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -399,6 +399,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index a8f052e4845..4b4ed1a13aa 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -189,6 +189,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_SHARED_TIDBITMAP,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_PER_XACT_PREDICATE_LIST,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index be67d8a8616..e2f8bb5162d 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -157,6 +157,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId backend_gtt_frozenxid;	/* backend level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index b01fa52139a..8efffa55ac5 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -94,4 +94,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index aa18d304ac0..524c9d7de3f 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -288,6 +288,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 b4faa1c1238..a74558a8383 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	SMgrRelation rd_smgr;		/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -326,6 +326,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -608,11 +609,13 @@ RelationGetSmgr(Relation rel)
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -620,6 +623,7 @@ RelationGetSmgr(Relation rel)
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -632,6 +636,30 @@ RelationGetSmgr(Relation rel)
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP_ON_CURRENT_SESSION
+ *		Test a rel is either local temp relation of this session
+ *		or global temp relation.
+ */
+#define RELATION_IS_TEMP_ON_CURRENT_SESSION(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is local temp relation or global temporary relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RelpersistenceTsTemp
+ *		Test a relpersistence is local temp relation or global temporary relation.
+ */
+#define RelpersistenceTsTemp(relpersistence) \
+	(relpersistence == RELPERSISTENCE_TEMP || \
+	 relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -677,6 +705,19 @@ RelationGetSmgr(Relation rel)
 	 (relation)->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&	\
 	 !IsCatalogRelation(relation))
 
+/* For global temporary table */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/* Get on commit clause value only for global temporary table */
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+/* Get relpersistence for relation */
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
-- 
2.30.1 (Apple Git-130)

0004-gtt-v54-regress.patchapplication/octet-stream; name=0004-gtt-v54-regress.patchDownload
diff --git a/src/test/isolation/expected/gtt-sequence.out b/src/test/isolation/expected/gtt-sequence.out
new file mode 100644
index 00000000000..31db2ebd423
--- /dev/null
+++ b/src/test/isolation/expected/gtt-sequence.out
@@ -0,0 +1,48 @@
+unused step name: s1_seq_restart
+Parsed test spec with 2 sessions
+
+starting permutation: s1_seq_next s2_seq_next s1_seq_next
+step s1_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      1
+(1 row)
+
+step s2_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      1
+(1 row)
+
+step s1_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      2
+(1 row)
+
+
+starting permutation: s1_select s2_select s1_insert s2_insert s1_select s2_select
+step s1_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+(0 rows)
+
+step s2_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+(0 rows)
+
+step s1_insert: insert into gtt_with_seq values(1);
+step s2_insert: insert into gtt_with_seq values(10);
+step s1_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+ 1| 3
+(1 row)
+
+step s2_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+10| 1
+(1 row)
+
diff --git a/src/test/isolation/expected/gtt-table.out b/src/test/isolation/expected/gtt-table.out
new file mode 100644
index 00000000000..5825773aa12
--- /dev/null
+++ b/src/test/isolation/expected/gtt-table.out
@@ -0,0 +1,675 @@
+Parsed test spec with 3 sessions
+
+starting permutation: s1_update_d
+step s1_update_d: update gtt_on_commit_delete_row set b = 'update'
+
+starting permutation: s1_select_d s1_insert_d s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_begin s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+1|test1
+(1 row)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_begin s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+1|test1
+(1 row)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_select_d s1_truncate_d s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_begin s1_insert_d s1_select_d s1_truncate_d s1_select_d s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_begin s1_insert_d s1_select_d s1_truncate_d s1_select_d s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_insert_d s1_select_d s1_begin s1_insert_d s1_select_d s1_save_1 s1_truncate_d s1_insert_d s1_select_d s1_save_2 s1_truncate_d s1_insert_d s1_select_d s1_save_3 s1_rollback_to_save_2 s1_select_d s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_save_1: SAVEPOINT save1
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_save_2: SAVEPOINT save2
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+4|test1
+(1 row)
+
+step s1_save_3: SAVEPOINT save3
+step s1_rollback_to_save_2: rollback to savepoint save2
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+5|test1
+(2 rows)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_insert_d s1_select_d s1_begin s1_insert_d s1_select_d s1_save_1 s1_truncate_d s1_insert_d s1_select_d s1_save_2 s1_truncate_d s1_insert_d s1_select_d s1_save_3 s1_rollback_to_save_2 s1_select_d s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_save_1: SAVEPOINT save1
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_save_2: SAVEPOINT save2
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+4|test1
+(1 row)
+
+step s1_save_3: SAVEPOINT save3
+step s1_rollback_to_save_2: rollback to savepoint save2
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+5|test1
+(2 rows)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_update_p
+step s2_update_p: update gtt_on_commit_preserve_row set b = 'update'
+
+starting permutation: s2_select_p s2_insert_p s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_begin s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_begin s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_select_p s2_insert_p s2_select_p s2_truncate_p s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_select_p s2_insert_p s2_begin s2_insert_p s2_select_p s2_truncate_p s2_select_p s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_insert_p s2_begin s2_insert_p s2_select_p s2_truncate_p s2_select_p s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_insert_p s2_select_p s2_begin s2_insert_p s2_select_p s2_save_1 s2_truncate_p s2_insert_p s2_select_p s2_save_2 s2_truncate_p s2_insert_p s2_select_p s2_save_3 s2_rollback_to_save_2 s2_select_p s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_save_1: SAVEPOINT save1
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_save_2: SAVEPOINT save2
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+4|test10
+(1 row)
+
+step s2_save_3: SAVEPOINT save3
+step s2_rollback_to_save_2: rollback to savepoint save2
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+
+starting permutation: s2_insert_p s2_select_p s2_begin s2_insert_p s2_select_p s2_save_1 s2_truncate_p s2_insert_p s2_select_p s2_save_2 s2_truncate_p s2_insert_p s2_select_p s2_save_3 s2_rollback_to_save_2 s2_select_p s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_save_1: SAVEPOINT save1
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_save_2: SAVEPOINT save2
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+4|test10
+(1 row)
+
+step s2_save_3: SAVEPOINT save3
+step s2_rollback_to_save_2: rollback to savepoint save2
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_insert_p s2_insert_p s1_select_p s2_select_p
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_truncate_p s2_truncate_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_begin s1_insert_d s2_insert_d s1_truncate_d s2_insert_d s1_commit
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s2_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s2_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_commit: commit
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_reindex_p s2_reindex_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_reindex_p: reindex table gtt_on_commit_preserve_row
+step s2_reindex_p: reindex table gtt_on_commit_preserve_row
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_reindex_i_p s2_reindex_i_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_reindex_i_p: reindex index gtt_on_commit_preserve_row_pkey
+step s2_reindex_i_p: reindex index gtt_on_commit_preserve_row_pkey
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_insert_c s3_create_c s3_insert_c s1_insert_c s1_analyze_c s2_analyze_c s3_analyze_c s1_select_c s2_select_c s3_select_c
+step s2_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s3_create_c: create unique index idx_temp_table_a on gtt_test_createindex(a)
+step s3_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s1_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s1_analyze_c: analyze gtt_test_createindex
+step s2_analyze_c: analyze gtt_test_createindex
+step s3_analyze_c: analyze gtt_test_createindex
+step s1_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                                               
+---------------------------------------------------------
+Index Scan using idx_temp_table_a on gtt_test_createindex
+  Index Cond: (a = 1)                                    
+(2 rows)
+
+step s2_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                      
+--------------------------------
+Seq Scan on gtt_test_createindex
+  Filter: (a = 1)               
+(2 rows)
+
+step s3_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                                               
+---------------------------------------------------------
+Index Scan using idx_temp_table_a on gtt_test_createindex
+  Index Cond: (a = 1)                                    
+(2 rows)
+
+
+starting permutation: s1_begin s2_begin s1_lock_p s2_lock_p s1_truncate_p s2_truncate_p s1_insert_p s2_insert_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_lock_p: LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE
+step s2_lock_p: LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE
+step s1_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index f4c01006fc1..746a17f824c 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -96,3 +96,5 @@ test: plpgsql-toast
 test: truncate-conflict
 test: serializable-parallel
 test: serializable-parallel-2
+test: gtt-sequence
+test: gtt-table
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 88594a3cb5d..ec643aadb5f 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -80,9 +80,30 @@ disconnect_atexit(void)
 {
 	int			i;
 
-	for (i = 0; i < nconns; i++)
+	for (i = 1; i < nconns; i++)
 		if (conns[i].conn)
 			PQfinish(conns[i].conn);
+
+	if (parseresult.destroy)
+	{
+		PGresult   *res;
+
+		res = PQexec(conns[0].conn, parseresult.destroy);
+		if (PQresultStatus(res) == PGRES_TUPLES_OK)
+		{
+			printResultSet(res);
+		}
+		else if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		{
+			fprintf(stderr, "destroy failed: %s",
+					PQerrorMessage(conns[0].conn));
+			/* don't exit on teardown failure */
+		}
+		PQclear(res);
+	}
+
+	if (conns[0].conn)
+		PQfinish(conns[0].conn);
 }
 
 int
@@ -214,6 +235,24 @@ main(int argc, char **argv)
 	PQclear(res);
 	termPQExpBuffer(&wait_query);
 
+	if (parseresult.initialize)
+	{
+		PGresult   *res;
+
+		res = PQexec(conns[0].conn, parseresult.initialize);
+		if (PQresultStatus(res) == PGRES_TUPLES_OK)
+		{
+			printResultSet(res);
+		}
+		else if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		{
+			fprintf(stderr, "initialize failed: %s",
+					PQerrorMessage(conns[0].conn));
+			/* don't exit on teardown failure */
+		}
+		PQclear(res);
+	}
+
 	/*
 	 * Run the permutations specified in the spec, or all if none were
 	 * explicitly specified.
diff --git a/src/test/isolation/isolationtester.h b/src/test/isolation/isolationtester.h
index 5f300219c20..b5a29893da9 100644
--- a/src/test/isolation/isolationtester.h
+++ b/src/test/isolation/isolationtester.h
@@ -81,6 +81,8 @@ typedef struct
 	int			nsessions;
 	Permutation **permutations;
 	int			npermutations;
+	char	   *initialize;
+	char	   *destroy;
 } TestSpec;
 
 extern TestSpec parseresult;
diff --git a/src/test/isolation/specparse.y b/src/test/isolation/specparse.y
index c25aa1a73fa..2784f758ed9 100644
--- a/src/test/isolation/specparse.y
+++ b/src/test/isolation/specparse.y
@@ -39,7 +39,7 @@ TestSpec		parseresult;			/* result of parsing is left here */
 }
 
 %type <ptr_list> setup_list
-%type <str>  opt_setup opt_teardown
+%type <str>  opt_setup opt_teardown opt_initialize opt_destroy
 %type <str> setup
 %type <ptr_list> step_list session_list permutation_list opt_permutation_list
 %type <ptr_list> permutation_step_list blocker_list
@@ -51,23 +51,27 @@ TestSpec		parseresult;			/* result of parsing is left here */
 
 %token <str> sqlblock identifier
 %token <integer> INTEGER
-%token NOTICES PERMUTATION SESSION SETUP STEP TEARDOWN TEST
+%token NOTICES PERMUTATION SESSION SETUP STEP TEARDOWN TEST INITIALIZE DESTROY
 
 %%
 
 TestSpec:
+			opt_initialize
 			setup_list
 			opt_teardown
+			opt_destroy
 			session_list
 			opt_permutation_list
 			{
-				parseresult.setupsqls = (char **) $1.elements;
-				parseresult.nsetupsqls = $1.nelements;
-				parseresult.teardownsql = $2;
-				parseresult.sessions = (Session **) $3.elements;
-				parseresult.nsessions = $3.nelements;
-				parseresult.permutations = (Permutation **) $4.elements;
-				parseresult.npermutations = $4.nelements;
+				parseresult.setupsqls = (char **) $2.elements;
+				parseresult.nsetupsqls = $2.nelements;
+				parseresult.teardownsql = $3;
+				parseresult.sessions = (Session **) $5.elements;
+				parseresult.nsessions = $5.nelements;
+				parseresult.permutations = (Permutation **) $6.elements;
+				parseresult.npermutations = $6.nelements;
+				parseresult.initialize = $1;
+				parseresult.destroy = $4;
 			}
 		;
 
@@ -100,6 +104,16 @@ opt_teardown:
 			| TEARDOWN sqlblock	{ $$ = $2; }
 		;
 
+opt_initialize:
+			/* EMPTY */			{ $$ = NULL; }
+			| INITIALIZE sqlblock	{ $$ = $2; }
+		;
+
+opt_destroy:
+			/* EMPTY */			{ $$ = NULL; }
+			| DESTROY sqlblock	{ $$ = $2; }
+		;
+
 session_list:
 			session_list session
 			{
diff --git a/src/test/isolation/specs/gtt-sequence.spec b/src/test/isolation/specs/gtt-sequence.spec
new file mode 100644
index 00000000000..88eece45e29
--- /dev/null
+++ b/src/test/isolation/specs/gtt-sequence.spec
@@ -0,0 +1,39 @@
+# Tests for global temporary relations
+
+initialize
+{
+  CREATE GLOBAL TEMPORARY TABLE if not exists gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+}
+
+destroy
+{
+  /* wait other backend exit */
+  select pg_sleep(1);
+
+  DROP TABLE gtt_with_seq;
+}
+
+# Session 1
+session "s1"
+step "s1_seq_next" { select nextval('gtt_with_seq_c2_seq'); }
+step "s1_seq_restart" { alter sequence gtt_with_seq_c2_seq RESTART; }
+step "s1_insert" { insert into gtt_with_seq values(1); }
+step "s1_select" { select * from gtt_with_seq order by c1,c2; }
+teardown
+{
+  TRUNCATE gtt_with_seq;
+}
+
+# Session 2
+session "s2"
+step "s2_seq_next" { select nextval('gtt_with_seq_c2_seq'); }
+step "s2_insert" { insert into gtt_with_seq values(10); }
+step "s2_select" { select * from gtt_with_seq order by c1,c2; }
+teardown
+{
+  TRUNCATE gtt_with_seq RESTART IDENTITY;
+}
+
+permutation "s1_seq_next" "s2_seq_next" "s1_seq_next"
+permutation "s1_select" "s2_select" "s1_insert" "s2_insert" "s1_select" "s2_select"
+
diff --git a/src/test/isolation/specs/gtt-table.spec b/src/test/isolation/specs/gtt-table.spec
new file mode 100644
index 00000000000..e0396b21ef0
--- /dev/null
+++ b/src/test/isolation/specs/gtt-table.spec
@@ -0,0 +1,135 @@
+# Tests for global temporary relations
+
+initialize
+{
+  create global temp table gtt_on_commit_delete_row(a bigserial primary key, b text) on commit delete rows;
+  create global temp table gtt_on_commit_preserve_row(a bigserial primary key, b text) on commit preserve rows;
+  create global temp table gtt_test_createindex(a int, b char(1000)) on commit preserve rows;
+}
+
+destroy
+{
+  /* wait other backend exit */
+  select pg_sleep(1);
+
+  DROP TABLE gtt_on_commit_delete_row;
+  DROP TABLE gtt_on_commit_preserve_row;
+  DROP TABLE gtt_test_createindex;
+}
+
+# Session 1
+session "s1"
+step "s1_begin" {begin}
+step "s1_commit" {commit}
+step "s1_rollback" {rollback}
+step "s1_insert_d" {insert into gtt_on_commit_delete_row (b) values('test1')}
+step "s1_select_d" {select a,b from gtt_on_commit_delete_row order by a,b}
+step "s1_insert_p" {insert into gtt_on_commit_preserve_row (b) values('test20')}
+step "s1_select_p" {select a,b from gtt_on_commit_preserve_row order by a,b}
+step "s1_truncate_d" {truncate gtt_on_commit_delete_row}
+step "s1_truncate_p" {truncate gtt_on_commit_preserve_row}
+step "s1_lock_p" {LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE}
+step "s1_update_d" {update gtt_on_commit_delete_row set b = 'update'}
+step "s1_save_1" {SAVEPOINT save1}
+step "s1_save_2" {SAVEPOINT save2}
+step "s1_save_3" {SAVEPOINT save3}
+step "s1_rollback_to_save_2" {rollback to savepoint save2}
+step "s1_reindex_p" {reindex table gtt_on_commit_preserve_row}
+step "s1_reindex_i_p" {reindex index gtt_on_commit_preserve_row_pkey}
+step "s1_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s1_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s1_analyze_c" {analyze gtt_test_createindex}
+teardown
+{
+  TRUNCATE gtt_on_commit_delete_row RESTART IDENTITY;
+  TRUNCATE gtt_on_commit_preserve_row RESTART IDENTITY;
+}
+
+# Session 2
+session "s2"
+step "s2_begin" {begin}
+step "s2_commit" {commit}
+step "s2_rollback" {rollback}
+step "s2_insert_p" {insert into gtt_on_commit_preserve_row (b) values('test10')}
+step "s2_select_p" {select a,b from gtt_on_commit_preserve_row order by a,b}
+step "s2_insert_d" {insert into gtt_on_commit_delete_row (b) values('test1')}
+step "s2_truncate_p" {truncate gtt_on_commit_preserve_row}
+step "s2_update_p" {update gtt_on_commit_preserve_row set b = 'update'}
+step "s2_lock_p" {LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE}
+step "s2_save_1" {SAVEPOINT save1}
+step "s2_save_2" {SAVEPOINT save2}
+step "s2_save_3" {SAVEPOINT save3}
+step "s2_rollback_to_save_2" {rollback to savepoint save2}
+step "s2_reindex_p" {reindex table gtt_on_commit_preserve_row}
+step "s2_reindex_i_p" {reindex index gtt_on_commit_preserve_row_pkey}
+step "s2_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s2_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s2_analyze_c" {analyze gtt_test_createindex}
+teardown
+{
+  TRUNCATE gtt_on_commit_delete_row RESTART IDENTITY;
+  TRUNCATE gtt_on_commit_preserve_row RESTART IDENTITY;
+}
+
+session "s3"
+step "s3_create_c" {create unique index idx_temp_table_a on gtt_test_createindex(a)}
+step "s3_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s3_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s3_analyze_c" {analyze gtt_test_createindex}
+
+
+#
+# test on commit delete temp table
+#
+
+# test update empty temp table
+permutation "s1_update_d"
+# test insert into temp table
+permutation "s1_select_d" "s1_insert_d" "s1_select_d"
+# test temp table in transaction(commit)
+permutation "s1_select_d" "s1_begin"    "s1_insert_d" "s1_select_d"   "s1_commit"   "s1_select_d" 
+# test temp table in transaction(rollback)
+permutation "s1_select_d" "s1_begin"    "s1_insert_d" "s1_select_d"   "s1_rollback" "s1_select_d" 
+# test truncate
+permutation "s1_select_d" "s1_insert_d" "s1_select_d" "s1_truncate_d" "s1_select_d"
+# test truncate in transaction block
+permutation "s1_select_d" "s1_insert_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_truncate_d" "s1_select_d"   "s1_insert_d" "s1_select_d" "s1_commit"   "s1_select_d" 
+permutation "s1_select_d" "s1_insert_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_truncate_d" "s1_select_d"   "s1_insert_d" "s1_select_d" "s1_rollback" "s1_select_d"
+# test temp table with subtransaction or savepoint
+permutation "s1_insert_d" "s1_select_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_save_1"     "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_2"   "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_3" "s1_rollback_to_save_2" "s1_select_d" "s1_insert_d" "s1_select_d" "s1_commit" "s1_select_d"
+permutation "s1_insert_d" "s1_select_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_save_1"     "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_2"   "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_3" "s1_rollback_to_save_2" "s1_select_d" "s1_insert_d" "s1_select_d" "s1_rollback" "s1_select_d"
+
+#
+# test on commit preserve table
+#
+
+# same as test on commit delete temp table
+permutation "s2_update_p"
+permutation "s2_select_p" "s2_insert_p" "s2_select_p"
+permutation "s2_select_p" "s2_begin"    "s2_insert_p" "s2_select_p"   "s2_commit"   "s2_select_p"
+permutation "s2_select_p" "s2_begin"    "s2_insert_p" "s2_select_p"   "s2_rollback" "s2_select_p"
+permutation "s2_select_p" "s2_insert_p" "s2_select_p" "s2_truncate_p" "s2_select_p"
+permutation "s2_select_p" "s2_insert_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_truncate_p" "s2_select_p"   "s2_insert_p" "s2_select_p" "s2_commit"   "s2_select_p" 
+permutation "s2_select_p" "s2_insert_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_truncate_p" "s2_select_p"   "s2_insert_p" "s2_select_p" "s2_rollback" "s2_select_p" 
+permutation "s2_insert_p" "s2_select_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_save_1"     "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_2"   "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_3" "s2_rollback_to_save_2" "s2_select_p" "s2_insert_p" "s2_select_p" "s2_commit" "s2_select_p"
+permutation "s2_insert_p" "s2_select_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_save_1"     "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_2"   "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_3" "s2_rollback_to_save_2" "s2_select_p" "s2_insert_p" "s2_select_p" "s2_rollback" "s2_select_p"
+
+#
+# test concurrent operation on temp table
+#
+
+#  test concurrent read
+permutation "s1_insert_p" "s2_insert_p" "s1_select_p" "s2_select_p" 
+#  test concurrent truncate
+permutation "s1_begin" "s2_begin"    "s1_insert_p" "s2_insert_p"   "s1_truncate_p" "s2_truncate_p"  "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+permutation "s1_begin" "s1_insert_d" "s2_insert_d" "s1_truncate_d" "s2_insert_d"   "s1_commit" 
+#  test concurrent reindex table
+permutation "s1_begin" "s2_begin" "s1_insert_p" "s2_insert_p" "s1_reindex_p"   "s2_reindex_p"   "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+#  test concurrent reindex index
+permutation "s1_begin" "s2_begin" "s1_insert_p" "s2_insert_p" "s1_reindex_i_p" "s2_reindex_i_p" "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+
+# test create index
+permutation "s2_insert_c" "s3_create_c" "s3_insert_c" "s1_insert_c" "s1_analyze_c" "s2_analyze_c" "s3_analyze_c" "s1_select_c" "s2_select_c" "s3_select_c"
+
+# test lock gtt
+permutation "s1_begin" "s2_begin" "s1_lock_p" "s2_lock_p" "s1_truncate_p" "s2_truncate_p" "s1_insert_p" "s2_insert_p" "s1_commit" "s2_commit" "s1_select_p" "s2_select_p"
diff --git a/src/test/isolation/specscanner.l b/src/test/isolation/specscanner.l
index d9fa6a5b54a..697db975479 100644
--- a/src/test/isolation/specscanner.l
+++ b/src/test/isolation/specscanner.l
@@ -67,6 +67,8 @@ session			{ return SESSION; }
 setup			{ return SETUP; }
 step			{ return STEP; }
 teardown		{ return TEARDOWN; }
+initialize		 { return INITIALIZE; }
+destroy			 { return DESTROY; }
 
  /* Whitespace and comments */
 [\n]			{ yyline++; }
diff --git a/src/test/regress/expected/global_temporary_table.out b/src/test/regress/expected/global_temporary_table.out
new file mode 100644
index 00000000000..def015dcbd6
--- /dev/null
+++ b/src/test/regress/expected/global_temporary_table.out
@@ -0,0 +1,506 @@
+--
+-- GLobal emparary table test case 
+--
+CREATE SCHEMA IF NOT EXISTS global_temporary_table;
+set search_path=global_temporary_table,sys;
+--
+--  test create global temp table basic syntax
+--
+create global temp table gtt_on_commit_default(a int primary key, b text);
+create global temp table gtt_on_commit_delete(a int primary key, b text) on commit delete rows;
+create global temp table gtt_on_commit_delete2(n int) with (on_commit_delete_rows='true');
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit PRESERVE rows;
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt_test_createindex(c0 tsvector,c1 varchar(100), c2 int);
+--
+-- test DML of temp table
+--
+-- update empty temp table
+update gtt_on_commit_delete set b ='test';
+begin;
+insert into gtt_on_commit_delete values (1);
+update gtt_on_commit_delete set b ='test';
+-- should 1 row
+select * from gtt_on_commit_delete;
+ a |  b   
+---+------
+ 1 | test
+(1 row)
+
+commit;
+-- data delete after transaction commit
+-- should 0 row
+select * from gtt_on_commit_delete;
+ a | b 
+---+---
+(0 rows)
+
+-- update empty temp table
+update gtt_on_commit_preserve set b ='test';
+insert into gtt_on_commit_preserve values (2);
+begin;
+insert into gtt_on_commit_preserve values (3);
+update gtt_on_commit_preserve set b ='test';
+-- should 2 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 2 | test
+ 3 | test
+(2 rows)
+
+delete from gtt_on_commit_preserve where a=2;
+commit;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 3 | test
+(1 row)
+
+begin;
+insert into gtt_on_commit_preserve values (4);
+-- temp table support truncate;
+truncate gtt_on_commit_preserve;
+select * from gtt_on_commit_preserve order by a;
+ a | b 
+---+---
+(0 rows)
+
+rollback;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 3 | test
+(1 row)
+
+--
+-- test unsupported global temp partition table
+--
+-- should fail
+CREATE global temp TABLE global_temp_partition_01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+on commit delete rows;
+ERROR:  Only support global temporary regular table.
+CREATE TABLE regular_partition_01_2019 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+);
+CREATE TABLE regular_partition01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time);
+-- should fail
+CREATE global temp TABLE temp_partition01_2018
+PARTITION OF regular_partition01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+ERROR:  Not support global temporary partition table or inherit table.
+CREATE global temp TABLE global_temp_partition_01_2021 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+)on commit delete rows;
+-- should fail
+ALTER TABLE regular_partition01 ATTACH PARTITION global_temp_partition_01_2021 FOR VALUES FROM ('2021-01-01 00:00:00') TO ('2022-01-01 00:00:00');
+ERROR:  cannot attach a global temporary relation as partition of permanent relation "regular_partition01"
+--
+-- test unsupported inherit table
+--
+create table 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 inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent);
+ERROR:  Not support global temporary partition table or inherit table.
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent_global_temp) on commit delete rows;
+ERROR:  Not support global temporary partition table or inherit table.
+--
+-- test DDL on global temp table
+--
+create index gtt_idx_1 on gtt_test_createindex using gin (c0);
+create index gtt_idx_2 on gtt_test_createindex using gist (c0);
+create index gtt_idx_3 on gtt_test_createindex using hash (c2);
+alter table gtt_test_rename rename to gtt_test_new;
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+create index CONCURRENTLY idx_b on gtt_on_commit_default (b);
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+insert into gtt_on_commit_default values(1,'test');
+create global temp table gtt_test_alter1 (a int primary key,b text);
+alter table gtt_test_alter1 alter a type varchar;
+-- should fail
+alter table gtt_on_commit_default alter a type varchar;
+ERROR:  Only support alter global temporary table in an empty context.
+HINT:  Please create a new connection and execute ALTER TABLE on the new connection.
+-- should fail
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+ERROR:  not support cluster global temporary table yet
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- should fail
+alter table gtt_on_commit_default SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temporary table
+-- should fail
+alter table gtt_on_commit_default set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table
+-- should fail
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- should fail
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- should fail
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit drop;
+ERROR:  global temporary table not support on commit drop clause
+-- should fail
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  could not create global temporary table with on commit and with clause at same time
+-- should fail
+CREATE MATERIALIZED VIEW mv_gtt_on_commit_default as select * from gtt_on_commit_default;
+ERROR:  materialized views must not use global temporary tables or views
+--
+-- test foreign key dependencies for global temp table
+--
+CREATE global temp TABLE temp_products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+CREATE global temp TABLE temp_orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+)on commit delete rows;
+-- should fail
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+CREATE global temp TABLE temp_orders_2 (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products(product_no),
+    quantity integer
+);
+--should fail
+insert into temp_orders values(1,1,1);
+ERROR:  insert or update on table "temp_orders" violates foreign key constraint "temp_orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "temp_products".
+insert into temp_products values(1,'test',1.0);
+begin;
+insert into temp_orders values(1,1,1);
+commit;
+-- should 1 row
+select count(*) from temp_products;
+ count 
+-------
+     1
+(1 row)
+
+-- should 0 row
+select count(*) from temp_orders;
+ count 
+-------
+     0
+(1 row)
+
+--
+-- test sequence on global temp table
+--
+create global temp table global_temp_with_serial (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+-- support insert data to temp table in read only transaction
+insert into global_temp_with_serial (b) values(1);
+select * from global_temp_with_serial;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_delete(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_preserve(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table temp_table_with_sequence_oncommit_delete add c2 int default nextval('seq_1');
+alter table temp_table_with_sequence_oncommit_preserve add c2 int default nextval('seq_1');
+begin;
+insert into temp_table_with_sequence_oncommit_delete (c1)values(1);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(2);
+insert into temp_table_with_sequence_oncommit_delete (c1)values(3);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(4);
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  3 |  3
+(2 rows)
+
+commit;
+-- should 0 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_preserve order by c1;
+ c1 | c2 
+----+----
+  2 |  2
+  4 |  4
+(2 rows)
+
+--
+-- test statistics on temp table
+--
+create global temp table temp_table_test_statistics(a int);
+insert into temp_table_test_statistics values(generate_series(1,100000));
+create index idx_test_1 on temp_table_test_statistics (a);
+create index idx_test_2 on temp_table_test_statistics((a*10));
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+                   QUERY PLAN                   
+------------------------------------------------
+ Bitmap Heap Scan on temp_table_test_statistics
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_test_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+                   QUERY PLAN                   
+------------------------------------------------
+ Bitmap Heap Scan on temp_table_test_statistics
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_test_2
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze temp_table_test_statistics;
+-- indexscan by idx_test_1
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Index Only Scan using idx_test_1 on temp_table_test_statistics
+   Index Cond: (a = 200000)
+(2 rows)
+
+-- indexscan by idx_test_2
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+                        QUERY PLAN                         
+-----------------------------------------------------------
+ Index Scan using idx_test_2 on temp_table_test_statistics
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+--
+-- test temp table with toast table
+--
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |                0 |        483328 |           98304 |                 581632
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            32768 |                  |         32768 |               0 |                  32768
+(3 rows)
+
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |             8192 |        499712 |          106496 |                 606208
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            40960 |                  |         40960 |               0 |                  40960
+(3 rows)
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,10),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',10);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |             8192 |                0 |         16384 |           32768 |                  49152
+ idx_gtt_t_kenyon_1 |            16384 |                  |         16384 |               0 |                  16384
+ idx_gtt_t_kenyon_2 |            16384 |                  |         16384 |               0 |                  16384
+(3 rows)
+
+rollback;
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |             8192 |        499712 |          106496 |                 606208
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            40960 |                  |         40960 |               0 |                  40960
+(3 rows)
+
+--
+-- test global temp table system view
+--
+create global temp table temp_table_test_systemview(a int primary key, b text) on commit PRESERVE rows;
+-- should empty, storage not initialized
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+ tablename 
+-----------
+(0 rows)
+
+-- should empty, storage not initialized
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into temp_table_test_systemview values(generate_series(1,10000),'test');
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+         tablename          
+----------------------------
+ temp_table_test_systemview
+(1 row)
+
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+       schemaname       |            tablename            | relpages | reltuples | relallvisible 
+------------------------+---------------------------------+----------+-----------+---------------
+ global_temporary_table | temp_table_test_systemview      |        0 |         0 |             0
+ global_temporary_table | temp_table_test_systemview_pkey |        1 |         0 |             0
+(2 rows)
+
+-- should empty
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+analyze temp_table_test_systemview;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+       schemaname       |            tablename            | relpages | reltuples | relallvisible 
+------------------------+---------------------------------+----------+-----------+---------------
+ global_temporary_table | temp_table_test_systemview      |       55 |     10000 |             0
+ global_temporary_table | temp_table_test_systemview_pkey |       30 |     10000 |             0
+(2 rows)
+
+-- get data after analyze;
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+       schemaname       |         tablename          | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------------------+----------------------------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ global_temporary_table | temp_table_test_systemview | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ global_temporary_table | temp_table_test_systemview | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+-- get all object info in current schema
+select relname ,relkind, relpersistence, reloptions from pg_class c, pg_namespace n where c.relnamespace = n.oid and n.nspname = 'global_temporary_table' order by relkind,relpersistence,relname;
+                     relname                     | relkind | relpersistence |          reloptions           
+-------------------------------------------------+---------+----------------+-------------------------------
+ global_temp_partition_01_2021_id_seq            | S       | g              | {on_commit_delete_rows=false}
+ global_temp_with_serial_a_seq                   | S       | g              | {on_commit_delete_rows=false}
+ regular_partition01_id_seq                      | S       | p              | 
+ regular_partition_01_2019_id_seq                | S       | p              | 
+ seq_1                                           | S       | p              | 
+ gtt_idx_1                                       | i       | g              | 
+ gtt_idx_2                                       | i       | g              | 
+ gtt_idx_3                                       | i       | g              | 
+ gtt_on_commit_default_pkey                      | i       | g              | 
+ gtt_on_commit_delete_pkey                       | i       | g              | 
+ gtt_on_commit_preserve_pkey                     | i       | g              | 
+ gtt_test_alter1_pkey                            | i       | g              | 
+ gtt_test_rename_pkey                            | i       | g              | 
+ idx_b                                           | i       | g              | 
+ idx_gtt_t_kenyon_1                              | i       | g              | 
+ idx_gtt_t_kenyon_2                              | i       | g              | 
+ idx_test_1                                      | i       | g              | 
+ idx_test_2                                      | i       | g              | 
+ products_pkey                                   | i       | g              | 
+ temp_orders_2_pkey                              | i       | g              | 
+ temp_orders_pkey                                | i       | g              | 
+ temp_products_pkey                              | i       | g              | 
+ temp_table_test_systemview_pkey                 | i       | g              | 
+ temp_table_with_sequence_oncommit_delete_pkey   | i       | g              | 
+ temp_table_with_sequence_oncommit_preserve_pkey | i       | g              | 
+ regular_partition01                             | p       | p              | 
+ global_temp_partition_01_2021                   | r       | g              | {on_commit_delete_rows=true}
+ global_temp_with_serial                         | r       | g              | {on_commit_delete_rows=false}
+ gtt_on_commit_default                           | r       | g              | {on_commit_delete_rows=false}
+ gtt_on_commit_delete                            | r       | g              | {on_commit_delete_rows=true}
+ gtt_on_commit_delete2                           | r       | g              | {on_commit_delete_rows=true}
+ gtt_on_commit_preserve                          | r       | g              | {on_commit_delete_rows=false}
+ gtt_t_kenyon                                    | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_alter1                                 | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_createindex                            | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_new                                    | r       | g              | {on_commit_delete_rows=false}
+ inherits_parent_global_temp                     | r       | g              | {on_commit_delete_rows=true}
+ products                                        | r       | g              | {on_commit_delete_rows=false}
+ temp_orders                                     | r       | g              | {on_commit_delete_rows=true}
+ temp_orders_2                                   | r       | g              | {on_commit_delete_rows=false}
+ temp_products                                   | r       | g              | {on_commit_delete_rows=false}
+ temp_table_test_statistics                      | r       | g              | {on_commit_delete_rows=false}
+ temp_table_test_systemview                      | r       | g              | {on_commit_delete_rows=false}
+ temp_table_with_sequence_oncommit_delete        | r       | g              | {on_commit_delete_rows=true}
+ temp_table_with_sequence_oncommit_preserve      | r       | g              | {on_commit_delete_rows=false}
+ foo                                             | r       | p              | 
+ inherits_parent                                 | r       | p              | 
+ regular_partition_01_2019                       | r       | p              | 
+(48 rows)
+
+reset search_path;
+drop schema global_temporary_table cascade;
+NOTICE:  drop cascades to 24 other objects
+DETAIL:  drop cascades to table global_temporary_table.gtt_on_commit_default
+drop cascades to table global_temporary_table.gtt_on_commit_delete
+drop cascades to table global_temporary_table.gtt_on_commit_delete2
+drop cascades to table global_temporary_table.gtt_on_commit_preserve
+drop cascades to table global_temporary_table.gtt_test_new
+drop cascades to table global_temporary_table.gtt_test_createindex
+drop cascades to table global_temporary_table.regular_partition_01_2019
+drop cascades to table global_temporary_table.regular_partition01
+drop cascades to table global_temporary_table.global_temp_partition_01_2021
+drop cascades to table global_temporary_table.inherits_parent
+drop cascades to table global_temporary_table.inherits_parent_global_temp
+drop cascades to table global_temporary_table.gtt_test_alter1
+drop cascades to table global_temporary_table.foo
+drop cascades to table global_temporary_table.temp_products
+drop cascades to table global_temporary_table.products
+drop cascades to table global_temporary_table.temp_orders
+drop cascades to table global_temporary_table.temp_orders_2
+drop cascades to table global_temporary_table.global_temp_with_serial
+drop cascades to sequence global_temporary_table.seq_1
+drop cascades to table global_temporary_table.temp_table_with_sequence_oncommit_delete
+drop cascades to table global_temporary_table.temp_table_with_sequence_oncommit_preserve
+drop cascades to table global_temporary_table.temp_table_test_statistics
+drop cascades to table global_temporary_table.gtt_t_kenyon
+drop cascades to table global_temporary_table.temp_table_test_systemview
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2fa00a3c29a..e0001bc3448 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1359,6 +1359,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 7be89178f0f..db8095d30bf 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -130,3 +130,6 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: global_temporary_table
diff --git a/src/test/regress/sql/global_temporary_table.sql b/src/test/regress/sql/global_temporary_table.sql
new file mode 100644
index 00000000000..cb49dc1af57
--- /dev/null
+++ b/src/test/regress/sql/global_temporary_table.sql
@@ -0,0 +1,284 @@
+--
+-- GLobal emparary table test case 
+--
+
+CREATE SCHEMA IF NOT EXISTS global_temporary_table;
+set search_path=global_temporary_table,sys;
+
+--
+--  test create global temp table basic syntax
+--
+create global temp table gtt_on_commit_default(a int primary key, b text);
+create global temp table gtt_on_commit_delete(a int primary key, b text) on commit delete rows;
+create global temp table gtt_on_commit_delete2(n int) with (on_commit_delete_rows='true');
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit PRESERVE rows;
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt_test_createindex(c0 tsvector,c1 varchar(100), c2 int);
+
+--
+-- test DML of temp table
+--
+
+-- update empty temp table
+update gtt_on_commit_delete set b ='test';
+begin;
+insert into gtt_on_commit_delete values (1);
+update gtt_on_commit_delete set b ='test';
+-- should 1 row
+select * from gtt_on_commit_delete;
+commit;
+-- data delete after transaction commit
+-- should 0 row
+select * from gtt_on_commit_delete;
+
+-- update empty temp table
+update gtt_on_commit_preserve set b ='test';
+insert into gtt_on_commit_preserve values (2);
+begin;
+insert into gtt_on_commit_preserve values (3);
+update gtt_on_commit_preserve set b ='test';
+-- should 2 row
+select * from gtt_on_commit_preserve order by a;
+delete from gtt_on_commit_preserve where a=2;
+commit;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+
+begin;
+insert into gtt_on_commit_preserve values (4);
+-- temp table support truncate;
+truncate gtt_on_commit_preserve;
+select * from gtt_on_commit_preserve order by a;
+rollback;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+
+--
+-- test unsupported global temp partition table
+--
+
+-- should fail
+CREATE global temp TABLE global_temp_partition_01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+on commit delete rows;
+
+CREATE TABLE regular_partition_01_2019 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+);
+
+CREATE TABLE regular_partition01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time);
+
+-- should fail
+CREATE global temp TABLE temp_partition01_2018
+PARTITION OF regular_partition01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE global_temp_partition_01_2021 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+)on commit delete rows;
+
+-- should fail
+ALTER TABLE regular_partition01 ATTACH PARTITION global_temp_partition_01_2021 FOR VALUES FROM ('2021-01-01 00:00:00') TO ('2022-01-01 00:00:00');
+
+--
+-- test unsupported inherit table
+--
+create table 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 inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent);
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent_global_temp) on commit delete rows;
+
+--
+-- test DDL on global temp table
+--
+create index gtt_idx_1 on gtt_test_createindex using gin (c0);
+create index gtt_idx_2 on gtt_test_createindex using gist (c0);
+create index gtt_idx_3 on gtt_test_createindex using hash (c2);
+alter table gtt_test_rename rename to gtt_test_new;
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+create index CONCURRENTLY idx_b on gtt_on_commit_default (b);
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+insert into gtt_on_commit_default values(1,'test');
+create global temp table gtt_test_alter1 (a int primary key,b text);
+alter table gtt_test_alter1 alter a type varchar;
+-- should fail
+alter table gtt_on_commit_default alter a type varchar;
+-- should fail
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) on commit delete rows;
+-- should fail
+alter table gtt_on_commit_default SET TABLESPACE pg_default;
+-- should fail
+alter table gtt_on_commit_default set ( on_commit_delete_rows='true');
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) with(on_commit_delete_rows=true);
+-- should fail
+create or replace global temp view gtt_v as select 5;
+create table foo();
+-- should fail
+alter table foo set (on_commit_delete_rows='true');
+-- should fail
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit drop;
+-- should fail
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+-- should fail
+CREATE MATERIALIZED VIEW mv_gtt_on_commit_default as select * from gtt_on_commit_default;
+
+--
+-- test foreign key dependencies for global temp table
+--
+CREATE global temp TABLE temp_products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+CREATE global temp TABLE temp_orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+)on commit delete rows;
+
+-- should fail
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+);
+
+CREATE global temp TABLE temp_orders_2 (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products(product_no),
+    quantity integer
+);
+
+--should fail
+insert into temp_orders values(1,1,1);
+
+insert into temp_products values(1,'test',1.0);
+begin;
+insert into temp_orders values(1,1,1);
+commit;
+-- should 1 row
+select count(*) from temp_products;
+-- should 0 row
+select count(*) from temp_orders;
+
+--
+-- test sequence on global temp table
+--
+create global temp table global_temp_with_serial (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+-- support insert data to temp table in read only transaction
+insert into global_temp_with_serial (b) values(1);
+select * from global_temp_with_serial;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_delete(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_preserve(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table temp_table_with_sequence_oncommit_delete add c2 int default nextval('seq_1');
+alter table temp_table_with_sequence_oncommit_preserve add c2 int default nextval('seq_1');
+begin;
+insert into temp_table_with_sequence_oncommit_delete (c1)values(1);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(2);
+insert into temp_table_with_sequence_oncommit_delete (c1)values(3);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(4);
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+commit;
+-- should 0 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_preserve order by c1;
+
+--
+-- test statistics on temp table
+--
+create global temp table temp_table_test_statistics(a int);
+insert into temp_table_test_statistics values(generate_series(1,100000));
+create index idx_test_1 on temp_table_test_statistics (a);
+create index idx_test_2 on temp_table_test_statistics((a*10));
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+analyze temp_table_test_statistics;
+-- indexscan by idx_test_1
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+-- indexscan by idx_test_2
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+
+--
+-- test temp table with toast table
+--
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,10),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',10);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+rollback;
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+
+--
+-- test global temp table system view
+--
+create global temp table temp_table_test_systemview(a int primary key, b text) on commit PRESERVE rows;
+-- should empty, storage not initialized
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+-- should empty, storage not initialized
+select count(*) from pg_list_gtt_relfrozenxids();
+insert into temp_table_test_systemview values(generate_series(1,10000),'test');
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+select count(*) from pg_list_gtt_relfrozenxids();
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+-- should empty
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+analyze temp_table_test_systemview;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+-- get data after analyze;
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+
+-- get all object info in current schema
+select relname ,relkind, relpersistence, reloptions from pg_class c, pg_namespace n where c.relnamespace = n.oid and n.nspname = 'global_temporary_table' order by relkind,relpersistence,relname;
+
+reset search_path;
+drop schema global_temporary_table cascade;
+
-- 
2.30.1 (Apple Git-130)

#328Andrew Bille
andrewbille@gmail.com
In reply to: wenjing (#327)
Re: [Proposal] Global temporary tables

On master with the v54 patches applied the following script leads to crash:
export
ASAN_OPTIONS=detect_leaks=0:abort_on_error=1:disable_coredump=0:strict_string_checks=1:check_initialization_order=1:strict_init_order=1
initdb -D data
pg_ctl -w -t 5 -D data -l server.log start
psql -c "create global temp table tmp_table_test_statistics(a int); insert
into temp_table_test_statistics values(generate_series(1,1000000000));" &
sleep 1
pg_ctl -w -t 5 -D data -l server.log stop

and i got error
=================================================================
==1022892==ERROR: AddressSanitizer: heap-use-after-free on address
0x62500004c640 at pc 0x562435348750 bp 0x7ffee8487e60 sp 0x7ffee8487e50
READ of size 8 at 0x62500004c640 thread T0
---

with backtrace:

Core was generated by `postgres: andrew regression [local] INSERT
'.
Program terminated with signal SIGABRT, Aborted.
#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
50 ../sysdeps/unix/sysv/linux/raise.c: No such file or directory.
(gdb) bt
#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
#1 0x00007fa8fd008859 in __GI_abort () at abort.c:79
#2 0x000056243471eae2 in __sanitizer::Abort() ()
#3 0x000056243472968c in __sanitizer::Die() ()
#4 0x000056243470ad1c in
__asan::ScopedInErrorReport::~ScopedInErrorReport() ()
#5 0x000056243470a793 in __asan::ReportGenericError(unsigned long,
unsigned long, unsigned long, unsigned long, bool, unsigned long, unsigned
int, bool) ()
#6 0x000056243470b5db in __asan_report_load8 ()
#7 0x0000562435348750 in DropRelFileNodesAllBuffers
(smgr_reln=smgr_reln@entry=0x62500004c640, nnodes=nnodes@entry=1) at
bufmgr.c:3211
#8 0x00005624353ec8a8 in smgrdounlinkall (rels=rels@entry=0x62500004c640,
nrels=nrels@entry=1, isRedo=isRedo@entry=false) at smgr.c:397
#9 0x0000562434aa76e1 in gtt_storage_removeall (code=<optimized out>,
arg=<optimized out>) at storage_gtt.c:726
#10 0x0000562435371962 in shmem_exit (code=code@entry=1) at ipc.c:236
#11 0x0000562435371d4f in proc_exit_prepare (code=code@entry=1) at ipc.c:194
#12 0x0000562435371f74 in proc_exit (code=code@entry=1) at ipc.c:107
#13 0x000056243581e35c in errfinish (filename=<optimized out>,
filename@entry=0x562435b800e0 "postgres.c", lineno=lineno@entry=3191,
funcname=funcname@entry=0x562435b836a0 <__func__.26025>
"ProcessInterrupts") at elog.c:666
#14 0x00005624353f5f86 in ProcessInterrupts () at postgres.c:3191
#15 0x0000562434eb26d6 in ExecProjectSet (pstate=0x62500003f150) at
nodeProjectSet.c:51
#16 0x0000562434eaae8e in ExecProcNode (node=0x62500003f150) at
../../../src/include/executor/executor.h:257
#17 ExecModifyTable (pstate=0x62500003ec98) at nodeModifyTable.c:2429
#18 0x0000562434df5755 in ExecProcNodeFirst (node=0x62500003ec98) at
execProcnode.c:463
#19 0x0000562434dd678a in ExecProcNode (node=0x62500003ec98) at
../../../src/include/executor/executor.h:257
#20 ExecutePlan (estate=estate@entry=0x62500003ea20,
planstate=0x62500003ec98, use_parallel_mode=<optimized out>,
use_parallel_mode@entry=false, operation=operation@entry=CMD_INSERT,
sendTuples=false, numberTuples=numberTuples@entry=0,
direction=ForwardScanDirection,
dest=0x625000045550, execute_once=true) at execMain.c:1555
#21 0x0000562434dd9867 in standard_ExecutorRun (queryDesc=0x6190000015a0,
direction=ForwardScanDirection, count=0, execute_once=execute_once@entry=true)
at execMain.c:361
#22 0x0000562434dd9a83 in ExecutorRun
(queryDesc=queryDesc@entry=0x6190000015a0,
direction=direction@entry=ForwardScanDirection, count=count@entry=0,
execute_once=execute_once@entry=true) at execMain.c:305
#23 0x0000562435401be6 in ProcessQuery (plan=plan@entry=0x625000045480,
sourceText=0x625000005220 "insert into temp_table_test_statistics
values(generate_series(1,1000000000));", params=0x0, queryEnv=0x0,
dest=dest@entry=0x625000045550, qc=qc@entry=0x7ffee84886d0)
at pquery.c:160
#24 0x0000562435404a32 in PortalRunMulti (portal=portal@entry=0x625000020a20,
isTopLevel=isTopLevel@entry=true, setHoldSnapshot=setHoldSnapshot@entry=false,
dest=dest@entry=0x625000045550, altdest=altdest@entry=0x625000045550,
qc=qc@entry=0x7ffee84886d0)
at pquery.c:1274
#25 0x000056243540598d in PortalRun (portal=portal@entry=0x625000020a20,
count=count@entry=9223372036854775807, isTopLevel=isTopLevel@entry=true,
run_once=run_once@entry=true, dest=dest@entry=0x625000045550,
altdest=altdest@entry=0x625000045550, qc=<optimized out>)
at pquery.c:788
#26 0x00005624353fa917 in exec_simple_query
(query_string=query_string@entry=0x625000005220
"insert into temp_table_test_statistics
values(generate_series(1,1000000000));") at postgres.c:1214
#27 0x00005624353ff61d in PostgresMain (dbname=dbname@entry=0x629000011278
"regression", username=username@entry=0x629000011258 "andrew") at
postgres.c:4497
#28 0x00005624351f65c7 in BackendRun (port=port@entry=0x615000002d80) at
postmaster.c:4560
#29 0x00005624351ff1c5 in BackendStartup (port=port@entry=0x615000002d80)
at postmaster.c:4288
#30 0x00005624351ff970 in ServerLoop () at postmaster.c:1801
#31 0x0000562435201da4 in PostmasterMain (argc=3, argv=<optimized out>) at
postmaster.c:1473
#32 0x0000562434f3ab2d in main (argc=3, argv=0x603000000280) at main.c:198
---

I've built the server with sanitizers using gcc 9 as following:
CPPFLAGS="-Og -fsanitize=address -fsanitize=undefined
-fno-sanitize=nonnull-attribute -fno-sanitize-recover
-fno-sanitize=alignment -fstack-protector" LDFLAGS='-fsanitize=address
-fsanitize=undefined -static-libasan' ./configure --enable-tap-tests
--enable-debug

#329wenjing
wjzeng2012@gmail.com
In reply to: Andrew Bille (#328)
4 attachment(s)
Re: [Proposal] Global temporary tables

Andrew Bille <andrewbille@gmail.com> 于2021年10月7日周四 上午12:30写道:

On master with the v54 patches applied the following script leads to crash:

Thank you for pointing it out.
This is a bug that occurs during transaction rollback and process exit, I
fixed it, please confirm it.

Wenjing

export

Show quoted text

ASAN_OPTIONS=detect_leaks=0:abort_on_error=1:disable_coredump=0:strict_string_checks=1:check_initialization_order=1:strict_init_order=1
initdb -D data
pg_ctl -w -t 5 -D data -l server.log start
psql -c "create global temp table tmp_table_test_statistics(a int); insert
into temp_table_test_statistics values(generate_series(1,1000000000));" &
sleep 1
pg_ctl -w -t 5 -D data -l server.log stop

and i got error
=================================================================
==1022892==ERROR: AddressSanitizer: heap-use-after-free on address
0x62500004c640 at pc 0x562435348750 bp 0x7ffee8487e60 sp 0x7ffee8487e50
READ of size 8 at 0x62500004c640 thread T0
---

with backtrace:

Core was generated by `postgres: andrew regression [local] INSERT
'.
Program terminated with signal SIGABRT, Aborted.
#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
50 ../sysdeps/unix/sysv/linux/raise.c: No such file or directory.
(gdb) bt
#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
#1 0x00007fa8fd008859 in __GI_abort () at abort.c:79
#2 0x000056243471eae2 in __sanitizer::Abort() ()
#3 0x000056243472968c in __sanitizer::Die() ()
#4 0x000056243470ad1c in
__asan::ScopedInErrorReport::~ScopedInErrorReport() ()
#5 0x000056243470a793 in __asan::ReportGenericError(unsigned long,
unsigned long, unsigned long, unsigned long, bool, unsigned long, unsigned
int, bool) ()
#6 0x000056243470b5db in __asan_report_load8 ()
#7 0x0000562435348750 in DropRelFileNodesAllBuffers
(smgr_reln=smgr_reln@entry=0x62500004c640, nnodes=nnodes@entry=1) at
bufmgr.c:3211
#8 0x00005624353ec8a8 in smgrdounlinkall (rels=rels@entry=0x62500004c640,
nrels=nrels@entry=1, isRedo=isRedo@entry=false) at smgr.c:397
#9 0x0000562434aa76e1 in gtt_storage_removeall (code=<optimized out>,
arg=<optimized out>) at storage_gtt.c:726
#10 0x0000562435371962 in shmem_exit (code=code@entry=1) at ipc.c:236
#11 0x0000562435371d4f in proc_exit_prepare (code=code@entry=1) at
ipc.c:194
#12 0x0000562435371f74 in proc_exit (code=code@entry=1) at ipc.c:107
#13 0x000056243581e35c in errfinish (filename=<optimized out>,
filename@entry=0x562435b800e0 "postgres.c", lineno=lineno@entry=3191,
funcname=funcname@entry=0x562435b836a0 <__func__.26025>
"ProcessInterrupts") at elog.c:666
#14 0x00005624353f5f86 in ProcessInterrupts () at postgres.c:3191
#15 0x0000562434eb26d6 in ExecProjectSet (pstate=0x62500003f150) at
nodeProjectSet.c:51
#16 0x0000562434eaae8e in ExecProcNode (node=0x62500003f150) at
../../../src/include/executor/executor.h:257
#17 ExecModifyTable (pstate=0x62500003ec98) at nodeModifyTable.c:2429
#18 0x0000562434df5755 in ExecProcNodeFirst (node=0x62500003ec98) at
execProcnode.c:463
#19 0x0000562434dd678a in ExecProcNode (node=0x62500003ec98) at
../../../src/include/executor/executor.h:257
#20 ExecutePlan (estate=estate@entry=0x62500003ea20,
planstate=0x62500003ec98, use_parallel_mode=<optimized out>,
use_parallel_mode@entry=false, operation=operation@entry=CMD_INSERT,
sendTuples=false, numberTuples=numberTuples@entry=0,
direction=ForwardScanDirection,
dest=0x625000045550, execute_once=true) at execMain.c:1555
#21 0x0000562434dd9867 in standard_ExecutorRun (queryDesc=0x6190000015a0,
direction=ForwardScanDirection, count=0, execute_once=execute_once@entry=true)
at execMain.c:361
#22 0x0000562434dd9a83 in ExecutorRun (queryDesc=queryDesc@entry=0x6190000015a0,
direction=direction@entry=ForwardScanDirection, count=count@entry=0,
execute_once=execute_once@entry=true) at execMain.c:305
#23 0x0000562435401be6 in ProcessQuery (plan=plan@entry=0x625000045480,
sourceText=0x625000005220 "insert into temp_table_test_statistics
values(generate_series(1,1000000000));", params=0x0, queryEnv=0x0,
dest=dest@entry=0x625000045550, qc=qc@entry=0x7ffee84886d0)
at pquery.c:160
#24 0x0000562435404a32 in PortalRunMulti (portal=portal@entry=0x625000020a20,
isTopLevel=isTopLevel@entry=true, setHoldSnapshot=setHoldSnapshot@entry=false,
dest=dest@entry=0x625000045550, altdest=altdest@entry=0x625000045550,
qc=qc@entry=0x7ffee84886d0)
at pquery.c:1274
#25 0x000056243540598d in PortalRun (portal=portal@entry=0x625000020a20,
count=count@entry=9223372036854775807, isTopLevel=isTopLevel@entry=true,
run_once=run_once@entry=true, dest=dest@entry=0x625000045550,
altdest=altdest@entry=0x625000045550, qc=<optimized out>)
at pquery.c:788
#26 0x00005624353fa917 in exec_simple_query
(query_string=query_string@entry=0x625000005220 "insert into
temp_table_test_statistics values(generate_series(1,1000000000));") at
postgres.c:1214
#27 0x00005624353ff61d in PostgresMain (dbname=dbname@entry=0x629000011278
"regression", username=username@entry=0x629000011258 "andrew") at
postgres.c:4497
#28 0x00005624351f65c7 in BackendRun (port=port@entry=0x615000002d80) at
postmaster.c:4560
#29 0x00005624351ff1c5 in BackendStartup (port=port@entry=0x615000002d80)
at postmaster.c:4288
#30 0x00005624351ff970 in ServerLoop () at postmaster.c:1801
#31 0x0000562435201da4 in PostmasterMain (argc=3, argv=<optimized out>) at
postmaster.c:1473
#32 0x0000562434f3ab2d in main (argc=3, argv=0x603000000280) at main.c:198
---

I've built the server with sanitizers using gcc 9 as following:
CPPFLAGS="-Og -fsanitize=address -fsanitize=undefined
-fno-sanitize=nonnull-attribute -fno-sanitize-recover
-fno-sanitize=alignment -fstack-protector" LDFLAGS='-fsanitize=address
-fsanitize=undefined -static-libasan' ./configure --enable-tap-tests
--enable-debug

Attachments:

0004-gtt-v55-regress.patchapplication/octet-stream; name=0004-gtt-v55-regress.patchDownload
diff --git a/src/test/isolation/expected/gtt-sequence.out b/src/test/isolation/expected/gtt-sequence.out
new file mode 100644
index 00000000000..31db2ebd423
--- /dev/null
+++ b/src/test/isolation/expected/gtt-sequence.out
@@ -0,0 +1,48 @@
+unused step name: s1_seq_restart
+Parsed test spec with 2 sessions
+
+starting permutation: s1_seq_next s2_seq_next s1_seq_next
+step s1_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      1
+(1 row)
+
+step s2_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      1
+(1 row)
+
+step s1_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      2
+(1 row)
+
+
+starting permutation: s1_select s2_select s1_insert s2_insert s1_select s2_select
+step s1_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+(0 rows)
+
+step s2_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+(0 rows)
+
+step s1_insert: insert into gtt_with_seq values(1);
+step s2_insert: insert into gtt_with_seq values(10);
+step s1_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+ 1| 3
+(1 row)
+
+step s2_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+10| 1
+(1 row)
+
diff --git a/src/test/isolation/expected/gtt-table.out b/src/test/isolation/expected/gtt-table.out
new file mode 100644
index 00000000000..5825773aa12
--- /dev/null
+++ b/src/test/isolation/expected/gtt-table.out
@@ -0,0 +1,675 @@
+Parsed test spec with 3 sessions
+
+starting permutation: s1_update_d
+step s1_update_d: update gtt_on_commit_delete_row set b = 'update'
+
+starting permutation: s1_select_d s1_insert_d s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_begin s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+1|test1
+(1 row)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_begin s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+1|test1
+(1 row)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_select_d s1_truncate_d s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_begin s1_insert_d s1_select_d s1_truncate_d s1_select_d s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_begin s1_insert_d s1_select_d s1_truncate_d s1_select_d s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_insert_d s1_select_d s1_begin s1_insert_d s1_select_d s1_save_1 s1_truncate_d s1_insert_d s1_select_d s1_save_2 s1_truncate_d s1_insert_d s1_select_d s1_save_3 s1_rollback_to_save_2 s1_select_d s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_save_1: SAVEPOINT save1
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_save_2: SAVEPOINT save2
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+4|test1
+(1 row)
+
+step s1_save_3: SAVEPOINT save3
+step s1_rollback_to_save_2: rollback to savepoint save2
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+5|test1
+(2 rows)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_insert_d s1_select_d s1_begin s1_insert_d s1_select_d s1_save_1 s1_truncate_d s1_insert_d s1_select_d s1_save_2 s1_truncate_d s1_insert_d s1_select_d s1_save_3 s1_rollback_to_save_2 s1_select_d s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_save_1: SAVEPOINT save1
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_save_2: SAVEPOINT save2
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+4|test1
+(1 row)
+
+step s1_save_3: SAVEPOINT save3
+step s1_rollback_to_save_2: rollback to savepoint save2
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+5|test1
+(2 rows)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_update_p
+step s2_update_p: update gtt_on_commit_preserve_row set b = 'update'
+
+starting permutation: s2_select_p s2_insert_p s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_begin s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_begin s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_select_p s2_insert_p s2_select_p s2_truncate_p s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_select_p s2_insert_p s2_begin s2_insert_p s2_select_p s2_truncate_p s2_select_p s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_insert_p s2_begin s2_insert_p s2_select_p s2_truncate_p s2_select_p s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_insert_p s2_select_p s2_begin s2_insert_p s2_select_p s2_save_1 s2_truncate_p s2_insert_p s2_select_p s2_save_2 s2_truncate_p s2_insert_p s2_select_p s2_save_3 s2_rollback_to_save_2 s2_select_p s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_save_1: SAVEPOINT save1
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_save_2: SAVEPOINT save2
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+4|test10
+(1 row)
+
+step s2_save_3: SAVEPOINT save3
+step s2_rollback_to_save_2: rollback to savepoint save2
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+
+starting permutation: s2_insert_p s2_select_p s2_begin s2_insert_p s2_select_p s2_save_1 s2_truncate_p s2_insert_p s2_select_p s2_save_2 s2_truncate_p s2_insert_p s2_select_p s2_save_3 s2_rollback_to_save_2 s2_select_p s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_save_1: SAVEPOINT save1
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_save_2: SAVEPOINT save2
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+4|test10
+(1 row)
+
+step s2_save_3: SAVEPOINT save3
+step s2_rollback_to_save_2: rollback to savepoint save2
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_insert_p s2_insert_p s1_select_p s2_select_p
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_truncate_p s2_truncate_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_begin s1_insert_d s2_insert_d s1_truncate_d s2_insert_d s1_commit
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s2_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s2_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_commit: commit
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_reindex_p s2_reindex_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_reindex_p: reindex table gtt_on_commit_preserve_row
+step s2_reindex_p: reindex table gtt_on_commit_preserve_row
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_reindex_i_p s2_reindex_i_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_reindex_i_p: reindex index gtt_on_commit_preserve_row_pkey
+step s2_reindex_i_p: reindex index gtt_on_commit_preserve_row_pkey
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_insert_c s3_create_c s3_insert_c s1_insert_c s1_analyze_c s2_analyze_c s3_analyze_c s1_select_c s2_select_c s3_select_c
+step s2_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s3_create_c: create unique index idx_temp_table_a on gtt_test_createindex(a)
+step s3_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s1_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s1_analyze_c: analyze gtt_test_createindex
+step s2_analyze_c: analyze gtt_test_createindex
+step s3_analyze_c: analyze gtt_test_createindex
+step s1_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                                               
+---------------------------------------------------------
+Index Scan using idx_temp_table_a on gtt_test_createindex
+  Index Cond: (a = 1)                                    
+(2 rows)
+
+step s2_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                      
+--------------------------------
+Seq Scan on gtt_test_createindex
+  Filter: (a = 1)               
+(2 rows)
+
+step s3_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                                               
+---------------------------------------------------------
+Index Scan using idx_temp_table_a on gtt_test_createindex
+  Index Cond: (a = 1)                                    
+(2 rows)
+
+
+starting permutation: s1_begin s2_begin s1_lock_p s2_lock_p s1_truncate_p s2_truncate_p s1_insert_p s2_insert_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_lock_p: LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE
+step s2_lock_p: LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE
+step s1_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index f4c01006fc1..746a17f824c 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -96,3 +96,5 @@ test: plpgsql-toast
 test: truncate-conflict
 test: serializable-parallel
 test: serializable-parallel-2
+test: gtt-sequence
+test: gtt-table
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 88594a3cb5d..ec643aadb5f 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -80,9 +80,30 @@ disconnect_atexit(void)
 {
 	int			i;
 
-	for (i = 0; i < nconns; i++)
+	for (i = 1; i < nconns; i++)
 		if (conns[i].conn)
 			PQfinish(conns[i].conn);
+
+	if (parseresult.destroy)
+	{
+		PGresult   *res;
+
+		res = PQexec(conns[0].conn, parseresult.destroy);
+		if (PQresultStatus(res) == PGRES_TUPLES_OK)
+		{
+			printResultSet(res);
+		}
+		else if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		{
+			fprintf(stderr, "destroy failed: %s",
+					PQerrorMessage(conns[0].conn));
+			/* don't exit on teardown failure */
+		}
+		PQclear(res);
+	}
+
+	if (conns[0].conn)
+		PQfinish(conns[0].conn);
 }
 
 int
@@ -214,6 +235,24 @@ main(int argc, char **argv)
 	PQclear(res);
 	termPQExpBuffer(&wait_query);
 
+	if (parseresult.initialize)
+	{
+		PGresult   *res;
+
+		res = PQexec(conns[0].conn, parseresult.initialize);
+		if (PQresultStatus(res) == PGRES_TUPLES_OK)
+		{
+			printResultSet(res);
+		}
+		else if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		{
+			fprintf(stderr, "initialize failed: %s",
+					PQerrorMessage(conns[0].conn));
+			/* don't exit on teardown failure */
+		}
+		PQclear(res);
+	}
+
 	/*
 	 * Run the permutations specified in the spec, or all if none were
 	 * explicitly specified.
diff --git a/src/test/isolation/isolationtester.h b/src/test/isolation/isolationtester.h
index 5f300219c20..b5a29893da9 100644
--- a/src/test/isolation/isolationtester.h
+++ b/src/test/isolation/isolationtester.h
@@ -81,6 +81,8 @@ typedef struct
 	int			nsessions;
 	Permutation **permutations;
 	int			npermutations;
+	char	   *initialize;
+	char	   *destroy;
 } TestSpec;
 
 extern TestSpec parseresult;
diff --git a/src/test/isolation/specparse.y b/src/test/isolation/specparse.y
index c25aa1a73fa..2784f758ed9 100644
--- a/src/test/isolation/specparse.y
+++ b/src/test/isolation/specparse.y
@@ -39,7 +39,7 @@ TestSpec		parseresult;			/* result of parsing is left here */
 }
 
 %type <ptr_list> setup_list
-%type <str>  opt_setup opt_teardown
+%type <str>  opt_setup opt_teardown opt_initialize opt_destroy
 %type <str> setup
 %type <ptr_list> step_list session_list permutation_list opt_permutation_list
 %type <ptr_list> permutation_step_list blocker_list
@@ -51,23 +51,27 @@ TestSpec		parseresult;			/* result of parsing is left here */
 
 %token <str> sqlblock identifier
 %token <integer> INTEGER
-%token NOTICES PERMUTATION SESSION SETUP STEP TEARDOWN TEST
+%token NOTICES PERMUTATION SESSION SETUP STEP TEARDOWN TEST INITIALIZE DESTROY
 
 %%
 
 TestSpec:
+			opt_initialize
 			setup_list
 			opt_teardown
+			opt_destroy
 			session_list
 			opt_permutation_list
 			{
-				parseresult.setupsqls = (char **) $1.elements;
-				parseresult.nsetupsqls = $1.nelements;
-				parseresult.teardownsql = $2;
-				parseresult.sessions = (Session **) $3.elements;
-				parseresult.nsessions = $3.nelements;
-				parseresult.permutations = (Permutation **) $4.elements;
-				parseresult.npermutations = $4.nelements;
+				parseresult.setupsqls = (char **) $2.elements;
+				parseresult.nsetupsqls = $2.nelements;
+				parseresult.teardownsql = $3;
+				parseresult.sessions = (Session **) $5.elements;
+				parseresult.nsessions = $5.nelements;
+				parseresult.permutations = (Permutation **) $6.elements;
+				parseresult.npermutations = $6.nelements;
+				parseresult.initialize = $1;
+				parseresult.destroy = $4;
 			}
 		;
 
@@ -100,6 +104,16 @@ opt_teardown:
 			| TEARDOWN sqlblock	{ $$ = $2; }
 		;
 
+opt_initialize:
+			/* EMPTY */			{ $$ = NULL; }
+			| INITIALIZE sqlblock	{ $$ = $2; }
+		;
+
+opt_destroy:
+			/* EMPTY */			{ $$ = NULL; }
+			| DESTROY sqlblock	{ $$ = $2; }
+		;
+
 session_list:
 			session_list session
 			{
diff --git a/src/test/isolation/specs/gtt-sequence.spec b/src/test/isolation/specs/gtt-sequence.spec
new file mode 100644
index 00000000000..88eece45e29
--- /dev/null
+++ b/src/test/isolation/specs/gtt-sequence.spec
@@ -0,0 +1,39 @@
+# Tests for global temporary relations
+
+initialize
+{
+  CREATE GLOBAL TEMPORARY TABLE if not exists gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+}
+
+destroy
+{
+  /* wait other backend exit */
+  select pg_sleep(1);
+
+  DROP TABLE gtt_with_seq;
+}
+
+# Session 1
+session "s1"
+step "s1_seq_next" { select nextval('gtt_with_seq_c2_seq'); }
+step "s1_seq_restart" { alter sequence gtt_with_seq_c2_seq RESTART; }
+step "s1_insert" { insert into gtt_with_seq values(1); }
+step "s1_select" { select * from gtt_with_seq order by c1,c2; }
+teardown
+{
+  TRUNCATE gtt_with_seq;
+}
+
+# Session 2
+session "s2"
+step "s2_seq_next" { select nextval('gtt_with_seq_c2_seq'); }
+step "s2_insert" { insert into gtt_with_seq values(10); }
+step "s2_select" { select * from gtt_with_seq order by c1,c2; }
+teardown
+{
+  TRUNCATE gtt_with_seq RESTART IDENTITY;
+}
+
+permutation "s1_seq_next" "s2_seq_next" "s1_seq_next"
+permutation "s1_select" "s2_select" "s1_insert" "s2_insert" "s1_select" "s2_select"
+
diff --git a/src/test/isolation/specs/gtt-table.spec b/src/test/isolation/specs/gtt-table.spec
new file mode 100644
index 00000000000..e0396b21ef0
--- /dev/null
+++ b/src/test/isolation/specs/gtt-table.spec
@@ -0,0 +1,135 @@
+# Tests for global temporary relations
+
+initialize
+{
+  create global temp table gtt_on_commit_delete_row(a bigserial primary key, b text) on commit delete rows;
+  create global temp table gtt_on_commit_preserve_row(a bigserial primary key, b text) on commit preserve rows;
+  create global temp table gtt_test_createindex(a int, b char(1000)) on commit preserve rows;
+}
+
+destroy
+{
+  /* wait other backend exit */
+  select pg_sleep(1);
+
+  DROP TABLE gtt_on_commit_delete_row;
+  DROP TABLE gtt_on_commit_preserve_row;
+  DROP TABLE gtt_test_createindex;
+}
+
+# Session 1
+session "s1"
+step "s1_begin" {begin}
+step "s1_commit" {commit}
+step "s1_rollback" {rollback}
+step "s1_insert_d" {insert into gtt_on_commit_delete_row (b) values('test1')}
+step "s1_select_d" {select a,b from gtt_on_commit_delete_row order by a,b}
+step "s1_insert_p" {insert into gtt_on_commit_preserve_row (b) values('test20')}
+step "s1_select_p" {select a,b from gtt_on_commit_preserve_row order by a,b}
+step "s1_truncate_d" {truncate gtt_on_commit_delete_row}
+step "s1_truncate_p" {truncate gtt_on_commit_preserve_row}
+step "s1_lock_p" {LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE}
+step "s1_update_d" {update gtt_on_commit_delete_row set b = 'update'}
+step "s1_save_1" {SAVEPOINT save1}
+step "s1_save_2" {SAVEPOINT save2}
+step "s1_save_3" {SAVEPOINT save3}
+step "s1_rollback_to_save_2" {rollback to savepoint save2}
+step "s1_reindex_p" {reindex table gtt_on_commit_preserve_row}
+step "s1_reindex_i_p" {reindex index gtt_on_commit_preserve_row_pkey}
+step "s1_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s1_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s1_analyze_c" {analyze gtt_test_createindex}
+teardown
+{
+  TRUNCATE gtt_on_commit_delete_row RESTART IDENTITY;
+  TRUNCATE gtt_on_commit_preserve_row RESTART IDENTITY;
+}
+
+# Session 2
+session "s2"
+step "s2_begin" {begin}
+step "s2_commit" {commit}
+step "s2_rollback" {rollback}
+step "s2_insert_p" {insert into gtt_on_commit_preserve_row (b) values('test10')}
+step "s2_select_p" {select a,b from gtt_on_commit_preserve_row order by a,b}
+step "s2_insert_d" {insert into gtt_on_commit_delete_row (b) values('test1')}
+step "s2_truncate_p" {truncate gtt_on_commit_preserve_row}
+step "s2_update_p" {update gtt_on_commit_preserve_row set b = 'update'}
+step "s2_lock_p" {LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE}
+step "s2_save_1" {SAVEPOINT save1}
+step "s2_save_2" {SAVEPOINT save2}
+step "s2_save_3" {SAVEPOINT save3}
+step "s2_rollback_to_save_2" {rollback to savepoint save2}
+step "s2_reindex_p" {reindex table gtt_on_commit_preserve_row}
+step "s2_reindex_i_p" {reindex index gtt_on_commit_preserve_row_pkey}
+step "s2_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s2_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s2_analyze_c" {analyze gtt_test_createindex}
+teardown
+{
+  TRUNCATE gtt_on_commit_delete_row RESTART IDENTITY;
+  TRUNCATE gtt_on_commit_preserve_row RESTART IDENTITY;
+}
+
+session "s3"
+step "s3_create_c" {create unique index idx_temp_table_a on gtt_test_createindex(a)}
+step "s3_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s3_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s3_analyze_c" {analyze gtt_test_createindex}
+
+
+#
+# test on commit delete temp table
+#
+
+# test update empty temp table
+permutation "s1_update_d"
+# test insert into temp table
+permutation "s1_select_d" "s1_insert_d" "s1_select_d"
+# test temp table in transaction(commit)
+permutation "s1_select_d" "s1_begin"    "s1_insert_d" "s1_select_d"   "s1_commit"   "s1_select_d" 
+# test temp table in transaction(rollback)
+permutation "s1_select_d" "s1_begin"    "s1_insert_d" "s1_select_d"   "s1_rollback" "s1_select_d" 
+# test truncate
+permutation "s1_select_d" "s1_insert_d" "s1_select_d" "s1_truncate_d" "s1_select_d"
+# test truncate in transaction block
+permutation "s1_select_d" "s1_insert_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_truncate_d" "s1_select_d"   "s1_insert_d" "s1_select_d" "s1_commit"   "s1_select_d" 
+permutation "s1_select_d" "s1_insert_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_truncate_d" "s1_select_d"   "s1_insert_d" "s1_select_d" "s1_rollback" "s1_select_d"
+# test temp table with subtransaction or savepoint
+permutation "s1_insert_d" "s1_select_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_save_1"     "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_2"   "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_3" "s1_rollback_to_save_2" "s1_select_d" "s1_insert_d" "s1_select_d" "s1_commit" "s1_select_d"
+permutation "s1_insert_d" "s1_select_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_save_1"     "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_2"   "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_3" "s1_rollback_to_save_2" "s1_select_d" "s1_insert_d" "s1_select_d" "s1_rollback" "s1_select_d"
+
+#
+# test on commit preserve table
+#
+
+# same as test on commit delete temp table
+permutation "s2_update_p"
+permutation "s2_select_p" "s2_insert_p" "s2_select_p"
+permutation "s2_select_p" "s2_begin"    "s2_insert_p" "s2_select_p"   "s2_commit"   "s2_select_p"
+permutation "s2_select_p" "s2_begin"    "s2_insert_p" "s2_select_p"   "s2_rollback" "s2_select_p"
+permutation "s2_select_p" "s2_insert_p" "s2_select_p" "s2_truncate_p" "s2_select_p"
+permutation "s2_select_p" "s2_insert_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_truncate_p" "s2_select_p"   "s2_insert_p" "s2_select_p" "s2_commit"   "s2_select_p" 
+permutation "s2_select_p" "s2_insert_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_truncate_p" "s2_select_p"   "s2_insert_p" "s2_select_p" "s2_rollback" "s2_select_p" 
+permutation "s2_insert_p" "s2_select_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_save_1"     "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_2"   "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_3" "s2_rollback_to_save_2" "s2_select_p" "s2_insert_p" "s2_select_p" "s2_commit" "s2_select_p"
+permutation "s2_insert_p" "s2_select_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_save_1"     "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_2"   "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_3" "s2_rollback_to_save_2" "s2_select_p" "s2_insert_p" "s2_select_p" "s2_rollback" "s2_select_p"
+
+#
+# test concurrent operation on temp table
+#
+
+#  test concurrent read
+permutation "s1_insert_p" "s2_insert_p" "s1_select_p" "s2_select_p" 
+#  test concurrent truncate
+permutation "s1_begin" "s2_begin"    "s1_insert_p" "s2_insert_p"   "s1_truncate_p" "s2_truncate_p"  "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+permutation "s1_begin" "s1_insert_d" "s2_insert_d" "s1_truncate_d" "s2_insert_d"   "s1_commit" 
+#  test concurrent reindex table
+permutation "s1_begin" "s2_begin" "s1_insert_p" "s2_insert_p" "s1_reindex_p"   "s2_reindex_p"   "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+#  test concurrent reindex index
+permutation "s1_begin" "s2_begin" "s1_insert_p" "s2_insert_p" "s1_reindex_i_p" "s2_reindex_i_p" "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+
+# test create index
+permutation "s2_insert_c" "s3_create_c" "s3_insert_c" "s1_insert_c" "s1_analyze_c" "s2_analyze_c" "s3_analyze_c" "s1_select_c" "s2_select_c" "s3_select_c"
+
+# test lock gtt
+permutation "s1_begin" "s2_begin" "s1_lock_p" "s2_lock_p" "s1_truncate_p" "s2_truncate_p" "s1_insert_p" "s2_insert_p" "s1_commit" "s2_commit" "s1_select_p" "s2_select_p"
diff --git a/src/test/isolation/specscanner.l b/src/test/isolation/specscanner.l
index d9fa6a5b54a..697db975479 100644
--- a/src/test/isolation/specscanner.l
+++ b/src/test/isolation/specscanner.l
@@ -67,6 +67,8 @@ session			{ return SESSION; }
 setup			{ return SETUP; }
 step			{ return STEP; }
 teardown		{ return TEARDOWN; }
+initialize		 { return INITIALIZE; }
+destroy			 { return DESTROY; }
 
  /* Whitespace and comments */
 [\n]			{ yyline++; }
diff --git a/src/test/regress/expected/global_temporary_table.out b/src/test/regress/expected/global_temporary_table.out
new file mode 100644
index 00000000000..def015dcbd6
--- /dev/null
+++ b/src/test/regress/expected/global_temporary_table.out
@@ -0,0 +1,506 @@
+--
+-- GLobal emparary table test case 
+--
+CREATE SCHEMA IF NOT EXISTS global_temporary_table;
+set search_path=global_temporary_table,sys;
+--
+--  test create global temp table basic syntax
+--
+create global temp table gtt_on_commit_default(a int primary key, b text);
+create global temp table gtt_on_commit_delete(a int primary key, b text) on commit delete rows;
+create global temp table gtt_on_commit_delete2(n int) with (on_commit_delete_rows='true');
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit PRESERVE rows;
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt_test_createindex(c0 tsvector,c1 varchar(100), c2 int);
+--
+-- test DML of temp table
+--
+-- update empty temp table
+update gtt_on_commit_delete set b ='test';
+begin;
+insert into gtt_on_commit_delete values (1);
+update gtt_on_commit_delete set b ='test';
+-- should 1 row
+select * from gtt_on_commit_delete;
+ a |  b   
+---+------
+ 1 | test
+(1 row)
+
+commit;
+-- data delete after transaction commit
+-- should 0 row
+select * from gtt_on_commit_delete;
+ a | b 
+---+---
+(0 rows)
+
+-- update empty temp table
+update gtt_on_commit_preserve set b ='test';
+insert into gtt_on_commit_preserve values (2);
+begin;
+insert into gtt_on_commit_preserve values (3);
+update gtt_on_commit_preserve set b ='test';
+-- should 2 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 2 | test
+ 3 | test
+(2 rows)
+
+delete from gtt_on_commit_preserve where a=2;
+commit;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 3 | test
+(1 row)
+
+begin;
+insert into gtt_on_commit_preserve values (4);
+-- temp table support truncate;
+truncate gtt_on_commit_preserve;
+select * from gtt_on_commit_preserve order by a;
+ a | b 
+---+---
+(0 rows)
+
+rollback;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 3 | test
+(1 row)
+
+--
+-- test unsupported global temp partition table
+--
+-- should fail
+CREATE global temp TABLE global_temp_partition_01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+on commit delete rows;
+ERROR:  Only support global temporary regular table.
+CREATE TABLE regular_partition_01_2019 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+);
+CREATE TABLE regular_partition01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time);
+-- should fail
+CREATE global temp TABLE temp_partition01_2018
+PARTITION OF regular_partition01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+ERROR:  Not support global temporary partition table or inherit table.
+CREATE global temp TABLE global_temp_partition_01_2021 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+)on commit delete rows;
+-- should fail
+ALTER TABLE regular_partition01 ATTACH PARTITION global_temp_partition_01_2021 FOR VALUES FROM ('2021-01-01 00:00:00') TO ('2022-01-01 00:00:00');
+ERROR:  cannot attach a global temporary relation as partition of permanent relation "regular_partition01"
+--
+-- test unsupported inherit table
+--
+create table 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 inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent);
+ERROR:  Not support global temporary partition table or inherit table.
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent_global_temp) on commit delete rows;
+ERROR:  Not support global temporary partition table or inherit table.
+--
+-- test DDL on global temp table
+--
+create index gtt_idx_1 on gtt_test_createindex using gin (c0);
+create index gtt_idx_2 on gtt_test_createindex using gist (c0);
+create index gtt_idx_3 on gtt_test_createindex using hash (c2);
+alter table gtt_test_rename rename to gtt_test_new;
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+create index CONCURRENTLY idx_b on gtt_on_commit_default (b);
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+insert into gtt_on_commit_default values(1,'test');
+create global temp table gtt_test_alter1 (a int primary key,b text);
+alter table gtt_test_alter1 alter a type varchar;
+-- should fail
+alter table gtt_on_commit_default alter a type varchar;
+ERROR:  Only support alter global temporary table in an empty context.
+HINT:  Please create a new connection and execute ALTER TABLE on the new connection.
+-- should fail
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+ERROR:  not support cluster global temporary table yet
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- should fail
+alter table gtt_on_commit_default SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temporary table
+-- should fail
+alter table gtt_on_commit_default set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table
+-- should fail
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- should fail
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- should fail
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit drop;
+ERROR:  global temporary table not support on commit drop clause
+-- should fail
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  could not create global temporary table with on commit and with clause at same time
+-- should fail
+CREATE MATERIALIZED VIEW mv_gtt_on_commit_default as select * from gtt_on_commit_default;
+ERROR:  materialized views must not use global temporary tables or views
+--
+-- test foreign key dependencies for global temp table
+--
+CREATE global temp TABLE temp_products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+CREATE global temp TABLE temp_orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+)on commit delete rows;
+-- should fail
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+CREATE global temp TABLE temp_orders_2 (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products(product_no),
+    quantity integer
+);
+--should fail
+insert into temp_orders values(1,1,1);
+ERROR:  insert or update on table "temp_orders" violates foreign key constraint "temp_orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "temp_products".
+insert into temp_products values(1,'test',1.0);
+begin;
+insert into temp_orders values(1,1,1);
+commit;
+-- should 1 row
+select count(*) from temp_products;
+ count 
+-------
+     1
+(1 row)
+
+-- should 0 row
+select count(*) from temp_orders;
+ count 
+-------
+     0
+(1 row)
+
+--
+-- test sequence on global temp table
+--
+create global temp table global_temp_with_serial (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+-- support insert data to temp table in read only transaction
+insert into global_temp_with_serial (b) values(1);
+select * from global_temp_with_serial;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_delete(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_preserve(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table temp_table_with_sequence_oncommit_delete add c2 int default nextval('seq_1');
+alter table temp_table_with_sequence_oncommit_preserve add c2 int default nextval('seq_1');
+begin;
+insert into temp_table_with_sequence_oncommit_delete (c1)values(1);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(2);
+insert into temp_table_with_sequence_oncommit_delete (c1)values(3);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(4);
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  3 |  3
+(2 rows)
+
+commit;
+-- should 0 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_preserve order by c1;
+ c1 | c2 
+----+----
+  2 |  2
+  4 |  4
+(2 rows)
+
+--
+-- test statistics on temp table
+--
+create global temp table temp_table_test_statistics(a int);
+insert into temp_table_test_statistics values(generate_series(1,100000));
+create index idx_test_1 on temp_table_test_statistics (a);
+create index idx_test_2 on temp_table_test_statistics((a*10));
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+                   QUERY PLAN                   
+------------------------------------------------
+ Bitmap Heap Scan on temp_table_test_statistics
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_test_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+                   QUERY PLAN                   
+------------------------------------------------
+ Bitmap Heap Scan on temp_table_test_statistics
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_test_2
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze temp_table_test_statistics;
+-- indexscan by idx_test_1
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Index Only Scan using idx_test_1 on temp_table_test_statistics
+   Index Cond: (a = 200000)
+(2 rows)
+
+-- indexscan by idx_test_2
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+                        QUERY PLAN                         
+-----------------------------------------------------------
+ Index Scan using idx_test_2 on temp_table_test_statistics
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+--
+-- test temp table with toast table
+--
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |                0 |        483328 |           98304 |                 581632
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            32768 |                  |         32768 |               0 |                  32768
+(3 rows)
+
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |             8192 |        499712 |          106496 |                 606208
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            40960 |                  |         40960 |               0 |                  40960
+(3 rows)
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,10),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',10);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |             8192 |                0 |         16384 |           32768 |                  49152
+ idx_gtt_t_kenyon_1 |            16384 |                  |         16384 |               0 |                  16384
+ idx_gtt_t_kenyon_2 |            16384 |                  |         16384 |               0 |                  16384
+(3 rows)
+
+rollback;
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |             8192 |        499712 |          106496 |                 606208
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            40960 |                  |         40960 |               0 |                  40960
+(3 rows)
+
+--
+-- test global temp table system view
+--
+create global temp table temp_table_test_systemview(a int primary key, b text) on commit PRESERVE rows;
+-- should empty, storage not initialized
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+ tablename 
+-----------
+(0 rows)
+
+-- should empty, storage not initialized
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into temp_table_test_systemview values(generate_series(1,10000),'test');
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+         tablename          
+----------------------------
+ temp_table_test_systemview
+(1 row)
+
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+       schemaname       |            tablename            | relpages | reltuples | relallvisible 
+------------------------+---------------------------------+----------+-----------+---------------
+ global_temporary_table | temp_table_test_systemview      |        0 |         0 |             0
+ global_temporary_table | temp_table_test_systemview_pkey |        1 |         0 |             0
+(2 rows)
+
+-- should empty
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+analyze temp_table_test_systemview;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+       schemaname       |            tablename            | relpages | reltuples | relallvisible 
+------------------------+---------------------------------+----------+-----------+---------------
+ global_temporary_table | temp_table_test_systemview      |       55 |     10000 |             0
+ global_temporary_table | temp_table_test_systemview_pkey |       30 |     10000 |             0
+(2 rows)
+
+-- get data after analyze;
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+       schemaname       |         tablename          | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------------------+----------------------------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ global_temporary_table | temp_table_test_systemview | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ global_temporary_table | temp_table_test_systemview | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+-- get all object info in current schema
+select relname ,relkind, relpersistence, reloptions from pg_class c, pg_namespace n where c.relnamespace = n.oid and n.nspname = 'global_temporary_table' order by relkind,relpersistence,relname;
+                     relname                     | relkind | relpersistence |          reloptions           
+-------------------------------------------------+---------+----------------+-------------------------------
+ global_temp_partition_01_2021_id_seq            | S       | g              | {on_commit_delete_rows=false}
+ global_temp_with_serial_a_seq                   | S       | g              | {on_commit_delete_rows=false}
+ regular_partition01_id_seq                      | S       | p              | 
+ regular_partition_01_2019_id_seq                | S       | p              | 
+ seq_1                                           | S       | p              | 
+ gtt_idx_1                                       | i       | g              | 
+ gtt_idx_2                                       | i       | g              | 
+ gtt_idx_3                                       | i       | g              | 
+ gtt_on_commit_default_pkey                      | i       | g              | 
+ gtt_on_commit_delete_pkey                       | i       | g              | 
+ gtt_on_commit_preserve_pkey                     | i       | g              | 
+ gtt_test_alter1_pkey                            | i       | g              | 
+ gtt_test_rename_pkey                            | i       | g              | 
+ idx_b                                           | i       | g              | 
+ idx_gtt_t_kenyon_1                              | i       | g              | 
+ idx_gtt_t_kenyon_2                              | i       | g              | 
+ idx_test_1                                      | i       | g              | 
+ idx_test_2                                      | i       | g              | 
+ products_pkey                                   | i       | g              | 
+ temp_orders_2_pkey                              | i       | g              | 
+ temp_orders_pkey                                | i       | g              | 
+ temp_products_pkey                              | i       | g              | 
+ temp_table_test_systemview_pkey                 | i       | g              | 
+ temp_table_with_sequence_oncommit_delete_pkey   | i       | g              | 
+ temp_table_with_sequence_oncommit_preserve_pkey | i       | g              | 
+ regular_partition01                             | p       | p              | 
+ global_temp_partition_01_2021                   | r       | g              | {on_commit_delete_rows=true}
+ global_temp_with_serial                         | r       | g              | {on_commit_delete_rows=false}
+ gtt_on_commit_default                           | r       | g              | {on_commit_delete_rows=false}
+ gtt_on_commit_delete                            | r       | g              | {on_commit_delete_rows=true}
+ gtt_on_commit_delete2                           | r       | g              | {on_commit_delete_rows=true}
+ gtt_on_commit_preserve                          | r       | g              | {on_commit_delete_rows=false}
+ gtt_t_kenyon                                    | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_alter1                                 | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_createindex                            | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_new                                    | r       | g              | {on_commit_delete_rows=false}
+ inherits_parent_global_temp                     | r       | g              | {on_commit_delete_rows=true}
+ products                                        | r       | g              | {on_commit_delete_rows=false}
+ temp_orders                                     | r       | g              | {on_commit_delete_rows=true}
+ temp_orders_2                                   | r       | g              | {on_commit_delete_rows=false}
+ temp_products                                   | r       | g              | {on_commit_delete_rows=false}
+ temp_table_test_statistics                      | r       | g              | {on_commit_delete_rows=false}
+ temp_table_test_systemview                      | r       | g              | {on_commit_delete_rows=false}
+ temp_table_with_sequence_oncommit_delete        | r       | g              | {on_commit_delete_rows=true}
+ temp_table_with_sequence_oncommit_preserve      | r       | g              | {on_commit_delete_rows=false}
+ foo                                             | r       | p              | 
+ inherits_parent                                 | r       | p              | 
+ regular_partition_01_2019                       | r       | p              | 
+(48 rows)
+
+reset search_path;
+drop schema global_temporary_table cascade;
+NOTICE:  drop cascades to 24 other objects
+DETAIL:  drop cascades to table global_temporary_table.gtt_on_commit_default
+drop cascades to table global_temporary_table.gtt_on_commit_delete
+drop cascades to table global_temporary_table.gtt_on_commit_delete2
+drop cascades to table global_temporary_table.gtt_on_commit_preserve
+drop cascades to table global_temporary_table.gtt_test_new
+drop cascades to table global_temporary_table.gtt_test_createindex
+drop cascades to table global_temporary_table.regular_partition_01_2019
+drop cascades to table global_temporary_table.regular_partition01
+drop cascades to table global_temporary_table.global_temp_partition_01_2021
+drop cascades to table global_temporary_table.inherits_parent
+drop cascades to table global_temporary_table.inherits_parent_global_temp
+drop cascades to table global_temporary_table.gtt_test_alter1
+drop cascades to table global_temporary_table.foo
+drop cascades to table global_temporary_table.temp_products
+drop cascades to table global_temporary_table.products
+drop cascades to table global_temporary_table.temp_orders
+drop cascades to table global_temporary_table.temp_orders_2
+drop cascades to table global_temporary_table.global_temp_with_serial
+drop cascades to sequence global_temporary_table.seq_1
+drop cascades to table global_temporary_table.temp_table_with_sequence_oncommit_delete
+drop cascades to table global_temporary_table.temp_table_with_sequence_oncommit_preserve
+drop cascades to table global_temporary_table.temp_table_test_statistics
+drop cascades to table global_temporary_table.gtt_t_kenyon
+drop cascades to table global_temporary_table.temp_table_test_systemview
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2fa00a3c29a..e0001bc3448 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1359,6 +1359,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 7be89178f0f..db8095d30bf 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -130,3 +130,6 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: global_temporary_table
diff --git a/src/test/regress/sql/global_temporary_table.sql b/src/test/regress/sql/global_temporary_table.sql
new file mode 100644
index 00000000000..cb49dc1af57
--- /dev/null
+++ b/src/test/regress/sql/global_temporary_table.sql
@@ -0,0 +1,284 @@
+--
+-- GLobal emparary table test case 
+--
+
+CREATE SCHEMA IF NOT EXISTS global_temporary_table;
+set search_path=global_temporary_table,sys;
+
+--
+--  test create global temp table basic syntax
+--
+create global temp table gtt_on_commit_default(a int primary key, b text);
+create global temp table gtt_on_commit_delete(a int primary key, b text) on commit delete rows;
+create global temp table gtt_on_commit_delete2(n int) with (on_commit_delete_rows='true');
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit PRESERVE rows;
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt_test_createindex(c0 tsvector,c1 varchar(100), c2 int);
+
+--
+-- test DML of temp table
+--
+
+-- update empty temp table
+update gtt_on_commit_delete set b ='test';
+begin;
+insert into gtt_on_commit_delete values (1);
+update gtt_on_commit_delete set b ='test';
+-- should 1 row
+select * from gtt_on_commit_delete;
+commit;
+-- data delete after transaction commit
+-- should 0 row
+select * from gtt_on_commit_delete;
+
+-- update empty temp table
+update gtt_on_commit_preserve set b ='test';
+insert into gtt_on_commit_preserve values (2);
+begin;
+insert into gtt_on_commit_preserve values (3);
+update gtt_on_commit_preserve set b ='test';
+-- should 2 row
+select * from gtt_on_commit_preserve order by a;
+delete from gtt_on_commit_preserve where a=2;
+commit;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+
+begin;
+insert into gtt_on_commit_preserve values (4);
+-- temp table support truncate;
+truncate gtt_on_commit_preserve;
+select * from gtt_on_commit_preserve order by a;
+rollback;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+
+--
+-- test unsupported global temp partition table
+--
+
+-- should fail
+CREATE global temp TABLE global_temp_partition_01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+on commit delete rows;
+
+CREATE TABLE regular_partition_01_2019 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+);
+
+CREATE TABLE regular_partition01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time);
+
+-- should fail
+CREATE global temp TABLE temp_partition01_2018
+PARTITION OF regular_partition01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE global_temp_partition_01_2021 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+)on commit delete rows;
+
+-- should fail
+ALTER TABLE regular_partition01 ATTACH PARTITION global_temp_partition_01_2021 FOR VALUES FROM ('2021-01-01 00:00:00') TO ('2022-01-01 00:00:00');
+
+--
+-- test unsupported inherit table
+--
+create table 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 inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent);
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent_global_temp) on commit delete rows;
+
+--
+-- test DDL on global temp table
+--
+create index gtt_idx_1 on gtt_test_createindex using gin (c0);
+create index gtt_idx_2 on gtt_test_createindex using gist (c0);
+create index gtt_idx_3 on gtt_test_createindex using hash (c2);
+alter table gtt_test_rename rename to gtt_test_new;
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+create index CONCURRENTLY idx_b on gtt_on_commit_default (b);
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+insert into gtt_on_commit_default values(1,'test');
+create global temp table gtt_test_alter1 (a int primary key,b text);
+alter table gtt_test_alter1 alter a type varchar;
+-- should fail
+alter table gtt_on_commit_default alter a type varchar;
+-- should fail
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) on commit delete rows;
+-- should fail
+alter table gtt_on_commit_default SET TABLESPACE pg_default;
+-- should fail
+alter table gtt_on_commit_default set ( on_commit_delete_rows='true');
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) with(on_commit_delete_rows=true);
+-- should fail
+create or replace global temp view gtt_v as select 5;
+create table foo();
+-- should fail
+alter table foo set (on_commit_delete_rows='true');
+-- should fail
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit drop;
+-- should fail
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+-- should fail
+CREATE MATERIALIZED VIEW mv_gtt_on_commit_default as select * from gtt_on_commit_default;
+
+--
+-- test foreign key dependencies for global temp table
+--
+CREATE global temp TABLE temp_products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+CREATE global temp TABLE temp_orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+)on commit delete rows;
+
+-- should fail
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+);
+
+CREATE global temp TABLE temp_orders_2 (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products(product_no),
+    quantity integer
+);
+
+--should fail
+insert into temp_orders values(1,1,1);
+
+insert into temp_products values(1,'test',1.0);
+begin;
+insert into temp_orders values(1,1,1);
+commit;
+-- should 1 row
+select count(*) from temp_products;
+-- should 0 row
+select count(*) from temp_orders;
+
+--
+-- test sequence on global temp table
+--
+create global temp table global_temp_with_serial (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+-- support insert data to temp table in read only transaction
+insert into global_temp_with_serial (b) values(1);
+select * from global_temp_with_serial;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_delete(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_preserve(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table temp_table_with_sequence_oncommit_delete add c2 int default nextval('seq_1');
+alter table temp_table_with_sequence_oncommit_preserve add c2 int default nextval('seq_1');
+begin;
+insert into temp_table_with_sequence_oncommit_delete (c1)values(1);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(2);
+insert into temp_table_with_sequence_oncommit_delete (c1)values(3);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(4);
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+commit;
+-- should 0 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_preserve order by c1;
+
+--
+-- test statistics on temp table
+--
+create global temp table temp_table_test_statistics(a int);
+insert into temp_table_test_statistics values(generate_series(1,100000));
+create index idx_test_1 on temp_table_test_statistics (a);
+create index idx_test_2 on temp_table_test_statistics((a*10));
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+analyze temp_table_test_statistics;
+-- indexscan by idx_test_1
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+-- indexscan by idx_test_2
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+
+--
+-- test temp table with toast table
+--
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,10),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',10);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+rollback;
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+
+--
+-- test global temp table system view
+--
+create global temp table temp_table_test_systemview(a int primary key, b text) on commit PRESERVE rows;
+-- should empty, storage not initialized
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+-- should empty, storage not initialized
+select count(*) from pg_list_gtt_relfrozenxids();
+insert into temp_table_test_systemview values(generate_series(1,10000),'test');
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+select count(*) from pg_list_gtt_relfrozenxids();
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+-- should empty
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+analyze temp_table_test_systemview;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+-- get data after analyze;
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+
+-- get all object info in current schema
+select relname ,relkind, relpersistence, reloptions from pg_class c, pg_namespace n where c.relnamespace = n.oid and n.nspname = 'global_temporary_table' order by relkind,relpersistence,relname;
+
+reset search_path;
+drop schema global_temporary_table cascade;
+
-- 
2.30.1 (Apple Git-130)

0003-gtt-v55-implementation.patchapplication/octet-stream; name=0003-gtt-v55-implementation.patchDownload
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index b5602f53233..21b2d2a9527 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -159,6 +159,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * In order to avoid consistency problems, the global temporary table
+	 * uses ShareUpdateExclusiveLock.
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temporary table on commit options",
+			RELOPT_KIND_HEAP,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},
 	/* list terminator */
 	{{NULL}}
 };
@@ -1834,6 +1847,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 43ba03b6eb9..49f1052fdb1 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1023,7 +1023,7 @@ gistproperty(Oid index_oid, int attno,
 XLogRecPtr
 gistGetFakeLSN(Relation rel)
 {
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	if (RELATION_IS_TEMP(rel))
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index eb3810494f2..cbd22909582 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -151,7 +151,7 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	if (!RELATION_IS_TEMP(index))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 9befe012a9e..26fce0c4b83 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -593,7 +593,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
@@ -645,7 +645,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(RelationGetSmgr(rel), dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 05221cc1d6d..c4a0dc1ac0e 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -63,6 +63,7 @@
 #include "access/xlog.h"
 #include "catalog/index.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -508,6 +509,14 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 	TransactionId FreezeLimit;
 	MultiXactId MultiXactCutoff;
 
+	/*
+	 * not every AM requires these to be valid, but regular heap does.
+	 * Transaction information for the global temp table will be stored
+	 * in the local hash table, not the catalog.
+	 */
+	Assert(RELATION_IS_GLOBAL_TEMP(rel) ^ TransactionIdIsNormal(rel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(rel) ^ MultiXactIdIsValid(rel->rd_rel->relminmxid));
+
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
 	{
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 5bc7c3616a9..0b261f723d7 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -677,6 +678,14 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		/*
+		 * If a global temporary table storage file is not initialized in the
+		 * current backend, its index does not have a root page, just returns 0.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e773612..8c21979625f 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -44,6 +44,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index aa7d4d5456b..595cb03eb4a 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -504,6 +504,7 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 5898203972b..c785d61db20 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -62,6 +62,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,
@@ -366,13 +368,24 @@ heap_create(const char *relname,
 			break;
 	}
 
+	/* For global temporary table, even if the storage is not initialized,
+	 * the relfilenode needs to be generated and put into the catalog.
+	 */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		create_storage = false;
+		if (!OidIsValid(relfilenode))
+			relfilenode = relid;
+	}
 	/*
 	 * Decide whether to create storage. If caller passed a valid relfilenode,
 	 * storage is already created, so don't do it here.  Also don't create it
 	 * for relkinds without physical storage.
 	 */
-	if (!RELKIND_HAS_STORAGE(relkind) || OidIsValid(relfilenode))
+	else if (!RELKIND_HAS_STORAGE(relkind) || OidIsValid(relfilenode))
+	{
 		create_storage = false;
+	}
 	else
 	{
 		create_storage = true;
@@ -427,7 +440,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:
@@ -445,7 +458,8 @@ heap_create(const char *relname,
 	 * protected by the existence of a physical file; but for relations with
 	 * no files, add a pg_shdepend entry to account for that.
 	 */
-	if (!create_storage && reltablespace != InvalidOid)
+	if (!create_storage && reltablespace != InvalidOid &&
+		relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
 		recordDependencyOnTablespace(RelationRelationId, relid,
 									 reltablespace);
 
@@ -998,6 +1012,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -1036,8 +1051,21 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/*
+	 * The transaction information of the global temporary table is stored
+	 * in the local hash table, not in catalog.
+	 */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1410,6 +1438,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1495,8 +1524,9 @@ heap_create_with_catalog(const char *relname,
 	/*
 	 * If there's a special on-commit action, remember it
 	 */
-	if (oncommit != ONCOMMIT_NOOP)
-		register_on_commit_action(relid, oncommit);
+	if (oncommit != ONCOMMIT_NOOP &&
+		relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+		register_on_commit_action(relid, oncommit, false);
 
 	/*
 	 * ok, the relation has been cataloged, so close our relations and return
@@ -1993,6 +2023,19 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/*
+	 * Only when other sessions are not using this Global temporary table,
+	 * is it allowed to DROP it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot drop global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3277,7 +3320,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3289,7 +3332,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3325,7 +3368,7 @@ RelationTruncateIndexes(Relation heapRelation)
  * ON COMMIT truncation of temporary tables, where it doesn't matter.
  */
 void
-heap_truncate(List *relids)
+heap_truncate(List *relids, bool is_global_temp)
 {
 	List	   *relations = NIL;
 	ListCell   *cell;
@@ -3335,8 +3378,23 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode;
+
+		/*
+		 * Truncate global temporary table only clears local data,
+		 * so only low-level locks need to be held.
+		 */
+		if (is_global_temp)
+		{
+			if (!gtt_storage_attached(rid))
+				continue;
+
+			lockmode = RowExclusiveLock;
+		}
+		else
+			lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3369,6 +3427,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3377,23 +3436,39 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	/*
+	 * Truncate GTT only clears local data, so only low-level locks
+	 * need to be held.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		lockmode = AccessShareLock;
+	else
+		lockmode = AccessExclusiveLock;
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	/*
+	 * After the data is cleaned up on the GTT, the transaction information
+	 * for the data(stored in local hash table) is also need reset.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(RelationGetRelid(rel), 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 26bfa74ce75..c32b45f9673 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -54,6 +54,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"
@@ -733,6 +734,25 @@ index_create(Relation heapRelation,
 	TransactionId relfrozenxid;
 	MultiXactId relminmxid;
 
+	/* For global temporary table only */
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temporary table with concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot reindex global temporary tables concurrently")));
+
+		/*
+		 * For the case that some backend is applied relcache message to create
+		 * an index on a global temporary table, if this table in the current
+		 * backend are not initialized, the creation of index storage on the
+		 * table are also skipped.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/* constraint flags can only be set when a constraint is requested */
 	Assert((constr_flags == 0) ||
 		   ((flags & INDEX_CREATE_ADD_CONSTRAINT) != 0));
@@ -2107,7 +2127,7 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 * lock (see comments in RemoveRelations), and a non-concurrent DROP is
 	 * more efficient.
 	 */
-	Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+	Assert(!RelpersistenceTsTemp(get_rel_persistence(indexId)) ||
 		   (!concurrent && !concurrent_lock_mode));
 
 	/*
@@ -2139,6 +2159,21 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/*
+	 * Allow to drop index on global temporary table when only current
+	 * backend use it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation) &&
+		is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+	{
+		ereport(ERROR,
+			(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+			 errmsg("cannot drop index %s on global temporary table %s",
+					RelationGetRelationName(userIndexRelation), RelationGetRelationName(userHeapRelation)),
+					errdetail("Because the index is created on the global temporary table and other backend attached it."),
+					errhint("Please try detach all sessions using this temporary table and try again.")));
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2747,6 +2782,7 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = RELATION_IS_GLOBAL_TEMP(rel);
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2841,20 +2877,37 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
-		{
-			rd_rel->relpages = (int32) relpages;
-			dirty = true;
-		}
-		if (rd_rel->reltuples != (float4) reltuples)
+		/* For global temporary table */
+		if (is_gtt)
 		{
-			rd_rel->reltuples = (float4) reltuples;
-			dirty = true;
+			/* Update GTT'statistics into local relcache */
+			rel->rd_rel->relpages = (int32) relpages;
+			rel->rd_rel->reltuples = (float4) reltuples;
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+
+			/* Update GTT'statistics into local hashtable */
+			up_gtt_relstats(RelationGetRelid(rel), relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+		else
 		{
-			rd_rel->relallvisible = (int32) relallvisible;
-			dirty = true;
+			if (rd_rel->relpages != (int32) relpages)
+			{
+				rd_rel->relpages = (int32) relpages;
+				dirty = true;
+			}
+
+			if (rd_rel->reltuples != (float4) reltuples)
+			{
+				rd_rel->reltuples = (float4) reltuples;
+				dirty = true;
+			}
+
+			if (rd_rel->relallvisible != (int32) relallvisible)
+			{
+				rd_rel->relallvisible = (int32) relallvisible;
+				dirty = true;
+			}
 		}
 	}
 
@@ -2967,6 +3020,26 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, progress_index, progress_vals);
 	}
 
+	/* For build index on global temporary table */
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		/*
+		 * If the storage for the index in this session is not initialized,
+		 * it needs to be created.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			/* Before create init storage, fix the local Relcache first */
+			force_enable_gtt_index(indexRelation);
+
+			Assert(gtt_storage_attached(RelationGetRelid(heapRelation)));
+
+			/* Init storage for index */
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3508,6 +3581,8 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	PGRUsage	ru0;
 	bool		progress = ((params->options & REINDEXOPT_REPORT_PROGRESS) != 0);
 	bool		set_tablespace = false;
+	LOCKMODE	lockmode_on_heap = ShareLock;
+	LOCKMODE	lockmode_on_index = AccessExclusiveLock;
 
 	pg_rusage_init(&ru0);
 
@@ -3521,10 +3596,29 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	if (!OidIsValid(heapId))
 		return;
 
+	/*
+	 * For reindex on global temporary table, If the storage for the index
+	 * in current backend is not initialized, nothing is done.
+	 */
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		if (!gtt_storage_attached(indexId))
+		{
+			/* Suppress use of the target index while rebuilding it */
+			SetReindexProcessing(heapId, indexId);
+			/* Re-allow use of target index */
+			ResetReindexProcessing();
+			return;
+		}
+
+		lockmode_on_heap = AccessShareLock;
+		lockmode_on_index = AccessShareLock;
+	}
+
 	if ((params->options & REINDEXOPT_MISSING_OK) != 0)
-		heapRelation = try_table_open(heapId, ShareLock);
+		heapRelation = try_table_open(heapId, lockmode_on_heap);
 	else
-		heapRelation = table_open(heapId, ShareLock);
+		heapRelation = table_open(heapId, lockmode_on_heap);
 
 	/* if relation is gone, leave */
 	if (!heapRelation)
@@ -3550,7 +3644,7 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	 * Open the target index relation and get an exclusive lock on it, to
 	 * ensure that no one else is touching this particular index.
 	 */
-	iRel = index_open(indexId, AccessExclusiveLock);
+	iRel = index_open(indexId, lockmode_on_index);
 
 	if (progress)
 		pgstat_progress_update_param(PROGRESS_CREATEIDX_ACCESS_METHOD_OID,
@@ -3801,6 +3895,12 @@ reindex_relation(Oid relid, int flags, ReindexParams *params)
 	bool		result;
 	ListCell   *indexId;
 	int			i;
+	LOCKMODE	lockmode;
+
+	if (flags & REINDEX_REL_PROCESS_GLOBAL_TEMP)
+		lockmode = AccessShareLock;
+	else
+		lockmode = ShareLock;
 
 	/*
 	 * Open and lock the relation.  ShareLock is sufficient since we only need
@@ -3808,9 +3908,9 @@ reindex_relation(Oid relid, int flags, ReindexParams *params)
 	 * should match ReindexTable().
 	 */
 	if ((params->options & REINDEXOPT_MISSING_OK) != 0)
-		rel = try_table_open(relid, ShareLock);
+		rel = try_table_open(relid, lockmode);
 	else
-		rel = table_open(relid, ShareLock);
+		rel = table_open(relid, lockmode);
 
 	/* if relation is gone, leave */
 	if (!rel)
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 4de8400fd0f..fe3fcc712cb 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -656,6 +656,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/* Do not allow create global temporary table in temporary schemas */
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp table in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index c5ad28d71fe..707068a6fd8 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,6 +27,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -61,6 +62,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			temprelOid;			/* InvalidOid if not a global temporary rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -115,7 +117,7 @@ AddPendingSync(const RelFileNode *rnode)
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -126,7 +128,12 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/*
+		 * Global temporary table and local temporary table use same
+		 * design on storage module.
+		 */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -154,6 +161,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
@@ -165,6 +173,21 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		AddPendingSync(&rnode);
 	}
 
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		Assert(rel && RELATION_IS_GLOBAL_TEMP(rel));
+
+		/*
+		 * Remember the reloid of global temporary table, which is used for
+		 * transaction commit or rollback.
+		 * see smgrDoPendingDeletes.
+		 */
+		pending->temprelOid = RelationGetRelid(rel);
+
+		/* Remember global temporary table storage info to localhash */
+		remember_gtt_storage_info(rnode, rel);
+	}
+
 	return srel;
 }
 
@@ -201,11 +224,20 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/*
+	 * Remember the reloid of global temporary table, which is used for
+	 * transaction commit or rollback.
+	 * see smgrDoPendingDeletes.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->temprelOid = RelationGetRelid(rel);
+
 	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
@@ -618,6 +650,7 @@ smgrDoPendingDeletes(bool isCommit)
 	int			nrels = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -647,14 +680,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->temprelOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -664,12 +701,21 @@ smgrDoPendingDeletes(bool isCommit)
 
 	if (nrels > 0)
 	{
+		int	i;
+
 		smgrdounlinkall(srels, nrels, false);
 
-		for (int i = 0; i < nrels; i++)
+		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* Delete global temporary table info in localhash */
+			if (gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 00000000000..7d5b5705458
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1519 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  The body implementation of Global Temparary table.
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *	  See src/backend/catalog/GTT_README for Global temparary table's
+ *	  requirements and design.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_info_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+int		vacuum_gtt_defer_check_age = 0;
+
+/*
+ * The Global temporary table's shared hash table data structure
+ */
+typedef struct gtt_ctl_data
+{
+	LWLock		lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+/* record this global temporary table in which backends are being used */
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+/*
+ * The Global temporary table's local hash table data structure
+ */
+/* Record the storage information and statistical information of the global temporary table */
+typedef struct
+{
+	Oid			relfilenode;
+	Oid			spcnode;
+
+	/* pg_class relstat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+
+	/* pg_statistic column stat */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_free_statistics(gtt_relfilenode *rnode);
+static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok);
+static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok);
+static Bitmapset *copy_active_gtt_bitmap(Oid relid);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+/*
+ * Calculate shared hash table entry size for GTT.
+ */
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	/* hash entry header size */
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	/*
+	 * hash entry data size
+	 * this is a bitmap in shared memory, each backend have a bit.
+	 */
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+/*
+ * Calculate shared hash table max size for GTT.
+ */
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	/* shared hash header size */
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	/* hash entry size */
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	/* max size */
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+/*
+ * Initialization shared hash table for GTT.
+ */
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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();
+	}
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(gtt_fnode);
+	info.entrysize = action_gtt_shared_hash_entry_size();
+	active_gtt_shared_hash =
+		ShmemInitHash("active gtt shared hash",
+						gtt_shared_ctl->max_entry,
+						gtt_shared_ctl->max_entry,
+						&info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+/*
+ * Record GTT relid to shared hash table, which means that current backend is using this GTT.
+ */
+static void
+gtt_storage_checkin(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			found;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *)&(fnode), 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_global_temporary_table.")));
+	}
+
+	if (!found)
+	{
+		int			wordnum;
+
+		/* init bitmap */
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	/* record itself in bitmap */
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+/*
+ * Remove the GTT relid record from the shared hash table which means that current backend is
+ * not use this GTT.
+ */
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	if (!skiplock)
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		if (!skiplock)
+			LWLockRelease(&gtt_shared_ctl->lock);
+
+		if (isCommit)
+			elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+	/* remove itself from bitmap */
+	bms_del_member(entry->map, MyBackendId);
+
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+
+	if (!skiplock)
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+	return;
+}
+
+/*
+ * Gets usage information for a GTT from shared hash table.
+ * The information is in the form of bitmap.
+ * Quickly copy the entire bitmap from shared memory and return it.
+ * that to avoid holding locks for a long time.
+ */
+static Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	Bitmapset		*map_copy = NULL;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		return NULL;
+	}
+
+	Assert(entry->map);
+
+	/* copy the entire bitmap */
+	if (!bms_is_empty(entry->map))
+		map_copy = bms_copy(entry->map);
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return map_copy;
+}
+
+/*
+ * Check if there are other backends using this GTT besides the current backend.
+ */
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			in_use = false;
+	int			num_use = 0;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		return false;
+	}
+
+	Assert(entry->map);
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+	/* how many backend are using this GTT */
+	num_use = bms_num_members(entry->map);
+	if (num_use == 0)
+		in_use = false;
+	else if (num_use == 1)
+	{
+		/* check if this is itself */
+		if(bms_is_member(MyBackendId, entry->map))
+			in_use = false;
+		else
+			in_use = true;
+	}
+	else
+		in_use = true;
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return in_use;
+}
+
+/*
+ * Record GTT information to local hash.
+ * They include GTT storage info, transaction info and statistical info.
+ */
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+	gtt_local_hash_entry		*entry;
+	MemoryContext			oldcontext;
+	gtt_relfilenode			*new_node = NULL;
+	Oid				relid = RelationGetRelid(rel);
+	int				natts = 0;
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	/* First time through: initialize the hash table */
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		HASHCTL		ctl;
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_info_context =
+			AllocSetContextCreate(CacheMemoryContext,
+								"gtt info context",
+								ALLOCSET_DEFAULT_SIZES);
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		ctl.hcxt = gtt_info_context;
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+	}
+
+	Assert(CacheMemoryContext);
+	Assert(gtt_info_context);
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	entry = gtt_search_by_relid(relid, true);
+	if (!entry)
+	{
+		bool		found = false;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash,
+						(void *) &relid, HASH_ENTER, &found);
+
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			/* record the on commit clause */
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS, true);
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			gtt_storage_checkin(relid);
+		}
+	}
+
+	/* record storage info relstat columnstats and transaction info to relfilenode list */
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	new_node->natts = 0;
+	new_node->attnum = NULL;
+	new_node->att_stat_tups = NULL;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* init column stats structure */
+	natts = RelationGetNumberOfAttributes(rel);
+	new_node->attnum = palloc0(sizeof(int) * natts);
+	new_node->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+	new_node->natts = natts;
+
+	/* only heap have transaction info */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+
+		/**/
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+
+	/* Registration callbacks are used to trigger cleanup during process exit */
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+/*
+ * Remove GTT information from local hash when transaction commit/rollback.
+ */
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	gtt_relfilenode		*d_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+		return;
+	}
+
+	d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+		else
+		{
+			/* rollback transaction */
+			if (entry->relfilenode_list == NIL)
+			{
+				if (entry->relkind == RELKIND_RELATION ||
+					entry->relkind == RELKIND_SEQUENCE)
+					gtt_storage_checkout(relid, false, isCommit);
+
+				hash_search(gtt_storage_local_hash,
+						(void *) &(relid), HASH_REMOVE, NULL);
+			}
+
+			return;
+		}
+	}
+
+	/* Clean up transaction info from Local order list and MyProc */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+
+		/* this is valid relfrozenxid */
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	/* delete relfilenode from rel entry */
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	gtt_free_statistics(d_rnode);
+
+	if (entry->relfilenode_list == NIL)
+	{
+		/* this means we truncate this GTT at current backend */
+
+		/* tell shared hash that current backend will no longer use this GTT */
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(relid, false, isCommit);
+
+		hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_REMOVE, NULL);
+	}
+
+	return;
+}
+
+/*
+ * Check if current backend is using this GTT.
+ */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool			found = false;
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (!OidIsValid(relid))
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry)
+		found = true;
+
+	return found;
+}
+
+/*
+ * When backend exit, bulk cleaning all GTT storage and local buffer of this backend.
+ */
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS			status;
+	gtt_local_hash_entry	*entry;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	/* Need to ensure we have a usable transaction. */
+	AbortOutOfAnyTransaction();
+
+	/* Search all relfilenode for GTT in current backend */
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel[1];
+			RelFileNode		rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel[0] = smgropen(rnode, MyBackendId);
+			smgrdounlinkall(srel, 1, false);
+			smgrclose(srel[0]);
+		}
+
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(entry->relid, true, false);
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+		hash_search(gtt_storage_local_hash, (void *) &(entry->relid), HASH_REMOVE, NULL);
+	}
+
+	/* set to global area */
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;
+
+	return;
+}
+
+/*
+ * Update GTT relstats(relpage/reltuple/relallvisible)
+ * to local hash.
+ */
+void
+up_gtt_relstats(Oid relid,
+					BlockNumber num_pages,
+					double num_tuples,
+					BlockNumber num_all_visible_pages,
+					TransactionId relfrozenxid,
+					TransactionId relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (!OidIsValid(relid))
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (num_pages > 0 &&
+		gtt_rnode->relpages != (int32)num_pages)
+		gtt_rnode->relpages = (int32)num_pages;
+
+	if (num_tuples > 0 &&
+		gtt_rnode->reltuples != (float4)num_tuples)
+		gtt_rnode->reltuples = (float4)num_tuples;
+
+	/* only heap contain transaction information and relallvisible */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (num_all_visible_pages > 0 &&
+			gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+		{
+			gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+		}
+
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNextTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			/* set to local order list */
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			/* set to global area */
+			set_gtt_session_relfrozenxid();
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search GTT relstats(relpage/reltuple/relallvisible)
+ * from local has.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update GTT info(definition is same as pg_statistic)
+ * to local hash.
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	MemoryContext		oldcontext;
+	int			i = 0;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return;
+
+	Assert(entry->relid == reloid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (gtt_rnode->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	/* switch context to gtt_info_context for store tuple at heap_form_tuple */
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == 0)
+		{
+			gtt_rnode->attnum[i] = attnum;
+			break;
+		}
+		else if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			heap_freetuple(gtt_rnode->att_stat_tups[i]);
+			gtt_rnode->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < gtt_rnode->natts);
+	Assert(gtt_rnode->att_stat_tups[i] == NULL);
+	gtt_rnode->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search GTT statistic info(definition is same as pg_statistic)
+ * from local hash.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	int			i = 0;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return NULL;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return NULL;
+
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			return gtt_rnode->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Insert a RelfrozenXID into the list and keep the list in order.
+ */
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int		i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Remove a RelfrozenXID from order list gtt_session_relfrozenxid_list.
+ */
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+/*
+ * Update of backend Level oldest relfrozenxid to MyProc.
+ * This makes each backend's oldest RelFrozenxID globally visible.
+ */
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->backend_gtt_frozenxid != gtt_frozenxid)
+		MyProc->backend_gtt_frozenxid = gtt_frozenxid;
+}
+
+/*
+ * Get GTT column level data statistics.
+ */
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate		*tupstore;
+	HeapTuple		tuple;
+	Relation		rel = NULL;
+	Oid			reloid = PG_GETARG_OID(0);
+	int			attnum = PG_GETARG_INT32(1);
+	char			rel_persistence;
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	/* get data from local hash */
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum		values[31];
+		bool		isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, NoLock);
+	relation_close(pg_tatistic, AccessShareLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get GTT table level data statistics.
+ */
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate	*tupstore;
+	TupleDesc	tupdesc;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid			reloid = PG_GETARG_OID(0);
+	Oid			relnode = 0;
+	char			rel_persistence;
+	BlockNumber		relpages = 0;
+	BlockNumber		relallvisible = 0;
+	uint32			relfrozenxid = 0;
+	uint32			relminmxid = 0;
+	double			reltuples = 0;
+	Relation		rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[6];
+		bool	isnull[6];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get a list of backend pids that are currently using this GTT.
+ */
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	PGPROC			*proc = NULL;
+	Bitmapset		*map = NULL;
+	Tuplestorestate		*tupstore;
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	HeapTuple		tuple;
+	Oid			reloid = PG_GETARG_OID(0);
+	char			rel_persistence;
+	Relation		rel = NULL;
+	pid_t			pid = 0;
+	int				backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	/* get data from share hash */
+	map = copy_active_gtt_bitmap(reloid);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			/* backendid map to process pid */
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get backend level oldest relfrozenxid of each backend using GTT in current database.
+ */
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate		*tupstore;
+	int			*pids = NULL;
+	uint32			*xids = NULL;
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	HeapTuple		tuple;
+	int			num_xid = MaxBackends + 1;
+	int			i = 0;
+	int			j = 0;
+	uint32			oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+
+	/* Get backend level oldest relfrozenxid in all backend that in MyDatabaseId use GTT */
+	oldest = list_all_backend_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+/*
+ * In order to build the GTT index, force enable GTT'index.
+ */
+void
+force_enable_gtt_index(Relation index)
+{
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(RelationGetRelid(index)));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+/*
+ * Fix the local state of the GTT's index.
+ */
+void
+gtt_fix_index_backend_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid heapOid = index->rd_index->indrelid;
+
+	/* Must be GTT */
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	/*
+	 * If this GTT is not initialized in the current backend,
+	 * its index status is temporarily set to invalid(local relcache).
+	 */
+	if (gtt_storage_attached(heapOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+/*
+ * During the SQL initialization of the executor (InitPlan)
+ * Initialize storage of GTT GTT'indexes and build empty index.
+ */
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int		i;
+	Oid		toastrelid;
+
+	if (!(operation == CMD_UTILITY || operation == CMD_INSERT))
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	/* Each GTT is initialized once in each backend */
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	/* init heap storage */
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+
+		index_build(relation, index, info, true, false);
+	}
+
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+
+		/* init index storage */
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid			indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+			/* build empty index */
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+/*
+ * Release the data structure memory used to store GTT storage info.
+ */
+static void
+gtt_free_statistics(gtt_relfilenode *rnode)
+{
+	int i;
+
+	Assert(rnode);
+
+	for (i = 0; i < rnode->natts; i++)
+	{
+		if (rnode->att_stat_tups[i])
+		{
+			heap_freetuple(rnode->att_stat_tups[i]);
+			rnode->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (rnode->attnum)
+		pfree(rnode->attnum);
+
+	if (rnode->att_stat_tups)
+		pfree(rnode->att_stat_tups);
+
+	pfree(rnode);
+
+	return;
+}
+
+/*
+ * Get the current relfilenode of this GTT.
+ */
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
+/*
+ * Get a relfilenode used by this GTT during the transaction life cycle.
+ */
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok)
+{
+	gtt_relfilenode		*rnode = NULL;
+	ListCell		*lc;
+
+	Assert(entry);
+
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == relfilenode)
+		{
+			rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (!missing_ok && rnode == NULL)
+		elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+	return rnode;
+}
+
+/*
+ * Get one GTT info from local hash.
+ */
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry == NULL && !missing_ok)
+		elog(ERROR, "relid %u not found in local hash", relid);
+
+	return entry;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 55f6e3711d8..fc75533263b 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 8bfb2ad9584..500305e8ae8 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -104,7 +105,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -185,6 +186,17 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -601,14 +613,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]));
 		}
 
 		/*
@@ -1633,7 +1646,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;
@@ -1735,31 +1748,48 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/*
+		 * For global temporary table,
+		 * Update column statistic to localhash, not catalog.
+		 */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 9d22f648a84..a44eefa1f6b 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/toasting.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
@@ -390,6 +391,22 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+	{
+		if (gtt_storage_attached(RelationGetRelid(OldHeap)))
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("not support cluster global temporary table yet")));
+
+		relation_close(OldHeap, AccessExclusiveLock);
+		pgstat_progress_end_command();
+		return;
+	}
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
@@ -585,6 +602,8 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
 	TransactionId frozenXid;
 	MultiXactId cutoffMulti;
 
+	Assert(!RELATION_IS_GLOBAL_TEMP(OldHeap));
+
 	/* Mark the correct index as clustered */
 	if (OidIsValid(indexOid))
 		mark_index_clustered(OldHeap, indexOid, true);
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 53f48531419..c03191cce94 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -289,7 +289,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP_ON_CURRENT_SESSION(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, whereClause,
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 40a54ad0bd7..7895e7d99b9 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -30,6 +30,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/copyfrom_internal.h"
 #include "commands/progress.h"
@@ -659,6 +660,9 @@ CopyFrom(CopyFromState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	/* Check and init global temporary table storage in current backend */
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	/*
 	 * Set up a ModifyTableState so we can let FDW(s) init themselves for
 	 * foreign-table result relation(s).
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index c14ca27c5ed..d65ae895356 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -111,6 +111,7 @@ struct ReindexIndexCallbackState
 {
 	ReindexParams params;		/* options from statement */
 	Oid			locked_table_oid;	/* tracks previously locked table */
+	LOCKMODE	lockmode;
 };
 
 /*
@@ -570,7 +571,7 @@ DefineIndex(Oid relationId,
 	 * is more efficient.  Do this before any use of the concurrent option is
 	 * done.
 	 */
-	if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+	if (stmt->concurrent && !RelpersistenceTsTemp(get_rel_persistence(relationId)))
 		concurrent = true;
 	else
 		concurrent = false;
@@ -2581,9 +2582,9 @@ ReindexIndex(RangeVar *indexRelation, ReindexParams *params, bool isTopLevel)
 	 */
 	state.params = *params;
 	state.locked_table_oid = InvalidOid;
+	state.lockmode = AccessShareLock;
 	indOid = RangeVarGetRelidExtended(indexRelation,
-									  (params->options & REINDEXOPT_CONCURRENTLY) != 0 ?
-									  ShareUpdateExclusiveLock : AccessExclusiveLock,
+									  AccessShareLock,
 									  0,
 									  RangeVarCallbackForReindexIndex,
 									  &state);
@@ -2594,11 +2595,25 @@ ReindexIndex(RangeVar *indexRelation, ReindexParams *params, bool isTopLevel)
 	 */
 	persistence = get_rel_persistence(indOid);
 	relkind = get_rel_relkind(indOid);
+	if (persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		LOCKMODE	lockmode;
+
+		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0)
+			lockmode = ShareUpdateExclusiveLock;
+		else
+			lockmode = AccessExclusiveLock;
+
+		/* lock heap first */
+		Assert(OidIsValid(state.locked_table_oid));
+		LockRelationOid(state.locked_table_oid, lockmode);
+		LockRelationOid(indOid, lockmode);
+	}
 
 	if (relkind == RELKIND_PARTITIONED_INDEX)
 		ReindexPartitions(indOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 persistence != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(persistence))
 		ReindexRelationConcurrently(indOid, params);
 	else
 	{
@@ -2620,15 +2635,7 @@ RangeVarCallbackForReindexIndex(const RangeVar *relation,
 {
 	char		relkind;
 	struct ReindexIndexCallbackState *state = arg;
-	LOCKMODE	table_lockmode;
-
-	/*
-	 * Lock level here should match table lock in reindex_index() for
-	 * non-concurrent case and table locks used by index_concurrently_*() for
-	 * concurrent case.
-	 */
-	table_lockmode = (state->params.options & REINDEXOPT_CONCURRENTLY) != 0 ?
-		ShareUpdateExclusiveLock : ShareLock;
+	LOCKMODE	table_lockmode = state->lockmode;
 
 	/*
 	 * If we previously locked some other index's heap, and the name we're
@@ -2689,6 +2696,8 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 {
 	Oid			heapOid;
 	bool		result;
+	char		relpersistence;
+	int 		reindex_flags = 0;
 
 	/*
 	 * The lock level used here should match reindex_relation().
@@ -2699,15 +2708,27 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 	 * locks on our temporary table.
 	 */
 	heapOid = RangeVarGetRelidExtended(relation,
-									   (params->options & REINDEXOPT_CONCURRENTLY) != 0 ?
-									   ShareUpdateExclusiveLock : ShareLock,
+									   AccessShareLock,
 									   0,
 									   RangeVarCallbackOwnsTable, NULL);
 
+	relpersistence = get_rel_persistence(heapOid);
+	if (relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		LOCKMODE	lockmode;
+
+		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0)
+			lockmode = ShareUpdateExclusiveLock;
+		else
+			lockmode = ShareLock;
+
+		LockRelationOid(heapOid, lockmode);
+	}
+
 	if (get_rel_relkind(heapOid) == RELKIND_PARTITIONED_TABLE)
 		ReindexPartitions(heapOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(relpersistence))
 	{
 		result = ReindexRelationConcurrently(heapOid, params);
 
@@ -2721,9 +2742,14 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 		ReindexParams newparams = *params;
 
 		newparams.options |= REINDEXOPT_REPORT_PROGRESS;
+
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			reindex_flags |= REINDEX_REL_PROCESS_GLOBAL_TEMP;
+
+		reindex_flags |= REINDEX_REL_PROCESS_TOAST;
+		reindex_flags |= REINDEX_REL_CHECK_CONSTRAINTS;
 		result = reindex_relation(heapOid,
-								  REINDEX_REL_PROCESS_TOAST |
-								  REINDEX_REL_CHECK_CONSTRAINTS,
+								  reindex_flags,
 								  &newparams);
 		if (!result)
 			ereport(NOTICE,
@@ -3122,7 +3148,7 @@ ReindexMultipleInternal(List *relids, ReindexParams *params)
 			   relkind != RELKIND_PARTITIONED_TABLE);
 
 		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			relpersistence != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(relpersistence))
 		{
 			ReindexParams newparams = *params;
 
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 62465bacd81..ef37f79ba68 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -51,12 +51,32 @@ LockTableCommand(LockStmt *lockstmt)
 		RangeVar   *rv = (RangeVar *) lfirst(p);
 		bool		recurse = rv->inh;
 		Oid			reloid;
+		LOCKMODE	lockmode = lockstmt->mode;
+		char		relpersistence;
 
-		reloid = RangeVarGetRelidExtended(rv, lockstmt->mode,
-										  lockstmt->nowait ? RVR_NOWAIT : 0,
+		reloid = RangeVarGetRelidExtended(rv, NoLock, 0,
 										  RangeVarCallbackForLockTable,
 										  (void *) &lockstmt->mode);
 
+		relpersistence = get_rel_persistence(reloid);
+		if (relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			if (!lockstmt->nowait)
+				LockRelationOid(reloid, lockmode);
+			else if (!ConditionalLockRelationOid(reloid, lockmode))
+			{
+				/* try to throw error by name; relation could be deleted... */
+				char	   *relname = get_rel_name(reloid);
+
+				if (!relname)
+					return;		/* child concurrently dropped, just skip it */
+				ereport(ERROR,
+						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						 errmsg("could not obtain lock on relation \"%s\"",
+								relname)));
+			}
+		}
+
 		if (get_rel_relkind(reloid) == RELKIND_VIEW)
 			LockViewRecurse(reloid, lockstmt->mode, lockstmt->nowait, NIL);
 		else if (recurse)
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 72bfdc07a49..e5257f610f6 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -220,9 +223,12 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	rel = table_open(seqoid, AccessExclusiveLock);
 	tupDesc = RelationGetDescr(rel);
 
-	/* now initialize the sequence's data */
-	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	if (!RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		/* now initialize the sequence's data */
+		tuple = heap_form_tuple(tupDesc, value, null);
+		fill_seq_with_data(rel, tuple);
+	}
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -275,8 +281,6 @@ ResetSequence(Oid seq_relid)
 	Buffer		buf;
 	HeapTupleData seqdatatuple;
 	HeapTuple	tuple;
-	HeapTuple	pgstuple;
-	Form_pg_sequence pgsform;
 	int64		startv;
 
 	/*
@@ -287,12 +291,7 @@ ResetSequence(Oid seq_relid)
 	init_sequence(seq_relid, &elm, &seq_rel);
 	(void) read_seq_tuple(seq_rel, &buf, &seqdatatuple);
 
-	pgstuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seq_relid));
-	if (!HeapTupleIsValid(pgstuple))
-		elog(ERROR, "cache lookup failed for sequence %u", seq_relid);
-	pgsform = (Form_pg_sequence) GETSTRUCT(pgstuple);
-	startv = pgsform->seqstart;
-	ReleaseSysCache(pgstuple);
+	startv = get_seqence_start_value(seq_relid);
 
 	/*
 	 * Copy the existing sequence tuple.
@@ -451,6 +450,15 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 
 	init_sequence(relid, &elm, &seqrel);
 
+	if (RELATION_IS_GLOBAL_TEMP(seqrel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(seqrel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary sequence %s when other backend attached it.",
+						RelationGetRelationName(seqrel))));
+	}
+
 	rel = table_open(SequenceRelationId, RowExclusiveLock);
 	seqtuple = SearchSysCacheCopy1(SEQRELID,
 								   ObjectIdGetDatum(relid));
@@ -611,7 +619,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +944,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1153,6 +1161,14 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	/* Initializes the storage for sequence which the global temporary table belongs. */
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1927,3 +1943,58 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+/*
+ * Get the startValue of the sequence from syscache.
+ */
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+/*
+ * Initialize sequence which global temporary table belongs.
+ */
+void
+gtt_init_seq(Relation rel)
+{
+	Datum		value[SEQ_COL_LASTCOL];
+	bool		null[SEQ_COL_LASTCOL];
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL - 1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+	null[SEQ_COL_LASTVAL - 1] = false;
+
+	value[SEQ_COL_LOG - 1] = Int64GetDatum((int64)0);
+	null[SEQ_COL_LOG - 1] = false;
+
+	value[SEQ_COL_CALLED - 1] = BoolGetDatum(false);
+	null[SEQ_COL_CALLED - 1] = false;
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index ff97b618e63..67f1043d08e 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -118,6 +119,7 @@ typedef struct OnCommitItem
 	 */
 	SubTransactionId creating_subid;
 	SubTransactionId deleting_subid;
+	bool			 is_global_temp;
 } OnCommitItem;
 
 static List *on_commits = NIL;
@@ -602,7 +604,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
 static char GetAttributeCompression(Oid atttypid, char *compression);
-
+static OnCommitAction gtt_oncommit_option(List *options);
 
 /* ----------------------------------------------------------------
  *		DefineRelation
@@ -647,6 +649,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -658,7 +661,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * Check consistency of arguments
 	 */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !RelpersistenceTsTemp(stmt->relation->relpersistence))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -688,7 +691,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	if (RelpersistenceTsTemp(stmt->relation->relpersistence)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -789,6 +792,50 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* For global temporary table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		if (!(relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE))
+			elog(ERROR, "Only support global temporary regular table.");
+
+		/* Check parent table */
+		if (inheritOids)
+			elog(ERROR, "Not support global temporary partition table or inherit table.");
+
+		/* Check oncommit clause and save to reloptions */
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temporary table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1415,7 +1462,7 @@ RemoveRelations(DropStmt *drop)
 		 * relation persistence cannot be known without its OID.
 		 */
 		if (drop->concurrent &&
-			get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(get_rel_persistence(relOid)))
 		{
 			Assert(list_length(drop->objects) == 1 &&
 				   drop->removeType == OBJECT_INDEX);
@@ -1624,9 +1671,9 @@ ExecuteTruncate(TruncateStmt *stmt)
 		Relation	rel;
 		bool		recurse = rv->inh;
 		Oid			myrelid;
-		LOCKMODE	lockmode = AccessExclusiveLock;
+		LOCKMODE	lockmode;
 
-		myrelid = RangeVarGetRelidExtended(rv, lockmode,
+		myrelid = RangeVarGetRelidExtended(rv, AccessShareLock,
 										   0, RangeVarCallbackForTruncate,
 										   NULL);
 
@@ -1634,9 +1681,21 @@ ExecuteTruncate(TruncateStmt *stmt)
 		if (list_member_oid(relids, myrelid))
 			continue;
 
-		/* open the relation, we already hold a lock on it */
+		/* open the relation, we need hold a low-level lock first */
 		rel = table_open(myrelid, NoLock);
 
+		/*
+		 * Truncate global temp table only cleans up the data in current backend,
+		 * only low-level locks are required.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel))
+			lockmode = AccessShareLock;
+		else
+		{
+			lockmode = AccessExclusiveLock;
+			LockRelationOid(myrelid, lockmode);
+		}
+
 		/*
 		 * RangeVarGetRelidExtended() has done most checks with its callback,
 		 * but other checks with the now-opened Relation remain.
@@ -1886,6 +1945,7 @@ ExecuteTruncateGuts(List *explicit_rels,
 	foreach(cell, rels)
 	{
 		Relation	rel = (Relation) lfirst(cell);
+		LOCKMODE	lockmode;
 
 		/* Skip partitioned tables as there is nothing to do */
 		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
@@ -1936,6 +1996,19 @@ ExecuteTruncateGuts(List *explicit_rels,
 			continue;
 		}
 
+		/*
+		 * Skip the global temporary table that is not initialized for storage
+		 * in current backend.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel))
+		{
+			lockmode = AccessShareLock;
+			if (!gtt_storage_attached(RelationGetRelid(rel)))
+				continue;
+		}
+		else
+			lockmode = AccessExclusiveLock;
+
 		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
@@ -1954,6 +2027,7 @@ ExecuteTruncateGuts(List *explicit_rels,
 			Oid			heap_relid;
 			Oid			toast_relid;
 			ReindexParams reindex_params = {0};
+			int			reindex_flags = 0;
 
 			/*
 			 * This effectively deletes all rows in the table, and may be done
@@ -1981,17 +2055,21 @@ ExecuteTruncateGuts(List *explicit_rels,
 			if (OidIsValid(toast_relid))
 			{
 				Relation	toastrel = relation_open(toast_relid,
-													 AccessExclusiveLock);
+													 lockmode);
 
 				RelationSetNewRelfilenode(toastrel,
 										  toastrel->rd_rel->relpersistence);
 				table_close(toastrel, NoLock);
 			}
 
+			reindex_flags = REINDEX_REL_PROCESS_TOAST;
+			if (RELATION_IS_GLOBAL_TEMP(rel))
+				reindex_flags |= REINDEX_REL_PROCESS_GLOBAL_TEMP;
+
 			/*
 			 * Reconstruct the indexes to match, and we're done.
 			 */
-			reindex_relation(heap_relid, REINDEX_REL_PROCESS_TOAST,
+			reindex_relation(heap_relid, reindex_flags,
 							 &reindex_params);
 		}
 
@@ -3998,6 +4076,16 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temporary table only current backend use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -5329,6 +5417,24 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 
 			rel = table_open(tab->relid, NoLock);
 			find_composite_type_dependencies(rel->rd_rel->reltype, rel, NULL);
+
+			if (RELATION_IS_GLOBAL_TEMP(rel) && tab->rewrite > 0)
+			{
+				if (tab->chgPersistence)
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot change global temporary table persistence setting")));
+
+				if(gtt_storage_attached(tab->relid))
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("Only support alter global temporary table in an empty context."),
+						 errhint("Please create a new connection and execute ALTER TABLE on the new connection.")));
+
+				/* There is no need to override the whole temp table */
+				tab->rewrite = 0;
+			}
+
 			table_close(rel, NoLock);
 		}
 
@@ -5380,6 +5486,8 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot rewrite temporary tables of other sessions")));
 
+			Assert(!RELATION_IS_GLOBAL_TEMP(OldHeap));
+
 			/*
 			 * Select destination tablespace (same as original unless user
 			 * requested a change)
@@ -9011,6 +9119,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -13709,6 +13823,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -13908,6 +14025,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temporary table");
+
 	/* Check first if relation can be moved to new tablespace */
 	if (!CheckRelationTableSpaceMove(rel, newTableSpace))
 	{
@@ -14211,7 +14331,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(RelationGetSmgr(rel), dstrel, MAIN_FORKNUM,
@@ -15809,6 +15929,7 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
 	switch (rel->rd_rel->relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -16223,7 +16344,7 @@ AlterSeqNamespaces(Relation classRel, Relation rel,
  * Register a newly-created relation's ON COMMIT action.
  */
 void
-register_on_commit_action(Oid relid, OnCommitAction action)
+register_on_commit_action(Oid relid, OnCommitAction action, bool is_gloal_temp)
 {
 	OnCommitItem *oc;
 	MemoryContext oldcxt;
@@ -16242,6 +16363,7 @@ register_on_commit_action(Oid relid, OnCommitAction action)
 	oc->oncommit = action;
 	oc->creating_subid = GetCurrentSubTransactionId();
 	oc->deleting_subid = InvalidSubTransactionId;
+	oc->is_global_temp = is_gloal_temp;
 
 	/*
 	 * We use lcons() here so that ON COMMIT actions are processed in reverse
@@ -16287,6 +16409,7 @@ PreCommit_on_commit_actions(void)
 	ListCell   *l;
 	List	   *oids_to_truncate = NIL;
 	List	   *oids_to_drop = NIL;
+	List	   *oids_to_truncate_gtt = NIL;
 
 	foreach(l, on_commits)
 	{
@@ -16310,7 +16433,12 @@ PreCommit_on_commit_actions(void)
 				 * tables, as they must still be empty.
 				 */
 				if ((MyXactFlags & XACT_FLAGS_ACCESSEDTEMPNAMESPACE))
-					oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid);
+				{
+					if (oc->is_global_temp)
+						oids_to_truncate_gtt = lappend_oid(oids_to_truncate_gtt, oc->relid);
+					else
+						oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid);
+				}
 				break;
 			case ONCOMMIT_DROP:
 				oids_to_drop = lappend_oid(oids_to_drop, oc->relid);
@@ -16327,7 +16455,10 @@ PreCommit_on_commit_actions(void)
 	 * exists at truncation time.
 	 */
 	if (oids_to_truncate != NIL)
-		heap_truncate(oids_to_truncate);
+		heap_truncate(oids_to_truncate, false);
+
+	if (oids_to_truncate_gtt != NIL)
+		heap_truncate(oids_to_truncate_gtt, true);
 
 	if (oids_to_drop != NIL)
 	{
@@ -17326,6 +17457,13 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot attach temporary relation of another session as partition")));
 
+	/* If the parent is permanent, so must be all of its partitions. */
+	if (attachrel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach a global temporary relation as partition of permanent relation \"%s\"",
+						RelationGetRelationName(rel))));
+
 	/* Check if there are any columns in attachrel that aren't in the parent */
 	tupleDesc = RelationGetDescr(attachrel);
 	natts = tupleDesc->natts;
@@ -18761,3 +18899,40 @@ GetAttributeCompression(Oid atttypid, char *compression)
 
 	return cmethod;
 }
+
+/*
+ * Parse the on commit clause for the temporary table
+ */
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			bool	res = false;
+			char	*sval = defGetString(def);
+
+			/* It has to be a Boolean value */
+			if (!parse_bool(sval, &res))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
+
+			if (res)
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 5c4bc15b441..77c0f384ed1 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"
@@ -1315,6 +1316,22 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = RELATION_IS_GLOBAL_TEMP(relation);
+
+	 /* For global temporary table */
+	if (is_gtt)
+	{
+		/* Store relation statistics and transaction information to the localhash */
+		up_gtt_relstats(RelationGetRelid(relation),
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+
+		/* Update relation statistics to local relcache */
+		relation->rd_rel->relpages = (int32) num_pages;
+		relation->rd_rel->reltuples = (float4) num_tuples;
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1328,17 +1345,23 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (!is_gtt &&
+		pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (!is_gtt &&
+		pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (!is_gtt &&
+		pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1351,7 +1374,15 @@ vac_update_relstats(Relation relation,
 		/*
 		 * If we didn't find any indexes, reset relhasindex.
 		 */
-		if (pgcform->relhasindex && !hasindex)
+		if (is_gtt &&
+			RelationGetIndexList(relation) != NIL)
+		{
+			/*
+			 * Global temporary tables may contain indexes that are not valid locally.
+			 * The catalog should not be updated based on local invalid index.
+			 */
+		}
+		else if (pgcform->relhasindex && !hasindex)
 		{
 			pgcform->relhasindex = false;
 			dirty = true;
@@ -1383,7 +1414,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNextTransactionId(),
@@ -1394,7 +1426,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1502,6 +1535,13 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/*
+		 * The relfrozenxid for a global temporary talble is stored in localhash,
+		 * not pg_class, See list_all_session_gtt_frozenxids()
+		 */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1559,6 +1599,43 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/* If enable global temporary table */
+	if (max_active_gtt > 0)
+	{
+		TransactionId	safe_age;
+		/*  */
+		TransactionId	oldest_gtt_frozenxid =
+			list_all_backend_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid))
+		{
+			safe_age = oldest_gtt_frozenxid + vacuum_gtt_defer_check_age;
+			if (safe_age < FirstNormalTransactionId)
+				safe_age += FirstNormalTransactionId;
+
+			/*
+			 * We tolerate that the minimum age of gtt is less than
+			 * the minimum age of conventional tables, otherwise it will
+			 * throw warning message.
+			 */
+			if (TransactionIdIsNormal(safe_age) &&
+				TransactionIdPrecedes(safe_age, newFrozenXid))
+			{
+				ereport(WARNING,
+					(errmsg("global temp table oldest relfrozenxid %u is the oldest in the entire db",
+							oldest_gtt_frozenxid),
+					 errdetail("The oldest relfrozenxid in pg_class is %u", newFrozenXid),
+					 errhint("If they differ greatly, please consider cleaning up the data in global temp table.")));
+			}
+
+			/*
+			 * We need to ensure that the clog required by gtt is not cleand.
+			 */
+			if (TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+				newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1910,6 +1987,19 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	/*
+	 * Skip those global temporary table that are not initialized in
+	 * current backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel) &&
+		!gtt_storage_attached(RelationGetRelid(rel)))
+	{
+		relation_close(rel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 4df05a0b33d..4c181e2e14e 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -527,6 +527,12 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	/* Global temporary table are not sensible. */
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b3ce4bae530..611e3f18a70 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -784,6 +784,10 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		/* This is one kind of temp table */
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 5c723bc54e1..a7edceb1a5c 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
@@ -533,6 +534,9 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	/* Init storage for partitioned global temporary table in current backend */
+	init_gtt_storage(mtstate->operation, leaf_part_rri);
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d328856ae5b..4e2bbb224cc 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -38,6 +38,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -633,6 +634,9 @@ ExecInsert(ModifyTableState *mtstate,
 		resultRelInfo->ri_IndexRelationDescs == NULL)
 		ExecOpenIndices(resultRelInfo, onconflict != ONCONFLICT_NONE);
 
+	/* Init storage for global temporary table in current backend */
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	/*
 	 * BEFORE ROW INSERT Triggers.
 	 *
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 296dd75c1b6..d971aea2546 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -48,7 +48,7 @@
 #include "partitioning/partprune.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/lsyscache.h"
-
+#include "utils/rel.h"
 
 /* results of subquery_is_pushdown_safe */
 typedef struct pushdown_safety_info
@@ -619,7 +619,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			if (RelpersistenceTsTemp(get_rel_persistence(rte->relid)))
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 1e42d75465e..15a76b2326b 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6070,7 +6070,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
-	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+	if (RELATION_IS_TEMP(heap) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index c5194fdbbf2..38d7c658541 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -30,6 +30,7 @@
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temporary table in current backend */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 146ee8dd1ea..2d4e9393f00 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2907,6 +2907,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 08f1bf1031c..d6048f2ee57 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3410,17 +3410,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; }
@@ -11683,19 +11677,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 c5c3f26ecf1..2a2b2789077 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -82,6 +82,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);
 
 
 /*
@@ -3665,3 +3666,53 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * Like function isQueryUsingTempRelation_walker
+ * return true if any relation underlying
+ * the query is a global temporary table.
+ */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* Check if the query uses global temporary table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 313d7b6ff02..38e0e162e82 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -447,6 +447,13 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	/*
+	 * If a sequence is bound to a global temporary table, then the sequence
+	 * must been "global temporary"
+	 */
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
@@ -3326,6 +3333,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		cxt.isforeign = false;
 	}
 	cxt.relation = stmt->relation;
+	/* Sets the table persistence to the context */
+	cxt.relation->relpersistence = RelationGetRelPersistence(rel);
 	cxt.rel = rel;
 	cxt.inhRelations = NIL;
 	cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 3b3df8fa8cc..ca6c68879d2 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2111,6 +2111,14 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/*
+			 * Aotuvacuum cannot vacuum the private data stored in each backend
+			 * that belongs to global temporary table, so skip them.
+			 */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2177,7 +2185,7 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		if (RelpersistenceTsTemp(classForm->relpersistence))
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 08ebabfe96a..c346c59c7f4 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlogutils.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -2934,6 +2935,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * Returns 0 if this global temporary table is not initialized in current
+	 * backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 9fa3e0631e6..cc3eb928bc6 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -23,6 +23,7 @@
 #include "access/syncscan.h"
 #include "access/twophase.h"
 #include "commands/async.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
@@ -143,6 +144,7 @@ CalculateShmemSize(int *num_semaphores)
 	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
@@ -246,6 +248,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* For global temporary table shared hashtable */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index bd3c7a47fe2..22481374778 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -65,6 +65,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -5131,3 +5132,78 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temporary table.
+ */
+int
+list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct		*arrayP = procArray;
+	TransactionId		result = InvalidTransactionId;
+	int			index;
+	uint8			flags = 0;
+	int			i = 0;
+
+	/* return 0 if feature is disabled */
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+		*n = 0;
+	}
+
+	/* Disable in standby node */
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		uint8           statusFlags = ProcGlobal->statusFlags[index];
+
+		if (statusFlags & flags)
+			continue;
+
+		/* Fetch all backend that is belonging to MyDatabaseId */
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->backend_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->backend_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->backend_gtt_frozenxid, result))
+				result = proc->backend_gtt_frozenxid;
+
+			/* save backend pid and backend level oldest relfrozenxid */
+			if (max_size > 0)
+			{
+				pids[i] = proc->pid;
+				xids[i] = proc->backend_gtt_frozenxid;
+				i++;
+			}
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (max_size > 0)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 862097352bb..4edd3b31f7a 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -176,7 +176,9 @@ static const char *const BuiltinTrancheNames[] = {
 	/* LWTRANCHE_PARALLEL_APPEND: */
 	"ParallelAppend",
 	/* LWTRANCHE_PER_XACT_PREDICATE_LIST: */
-	"PerXactPredicateList"
+	"PerXactPredicateList",
+	/* LWTRANCHE_GTT_CTL */
+	"GlobalTempTableControl"
 };
 
 StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index b7d9da0aa9f..8051f2053f9 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -393,6 +393,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;	/* init backend level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
@@ -578,6 +579,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;	/* init backend level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index d5a7fb13f3c..8225cf6219f 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -982,6 +982,13 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/*
+			 * For global temporary table ,each backend has its own storage,
+			 * also only sees its own storage. Use Backendid to identify them.
+			 */
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 10895fb2876..66255eb7604 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -108,6 +108,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -5115,12 +5116,26 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								/* For global temporary table, get statistic data from localhash */
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -5368,15 +5383,28 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6820,6 +6848,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;
@@ -6837,6 +6866,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6848,6 +6885,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;
@@ -6863,6 +6902,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7781,6 +7828,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))
@@ -7793,6 +7842,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7805,6 +7863,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.
@@ -7824,6 +7884,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 4ebaa552a27..78c33d2ac87 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -3113,6 +3114,19 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		/* For global temporary table, get statistic data from localhash */
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3e..94fdb998aae 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -65,6 +65,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1116,6 +1117,28 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+
+				/* For global temporary table, get relstat data from localhash */
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+
+				/* And put them to local relcache */
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1173,6 +1196,8 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			/* The state of the global temporary table's index may need to be set */
+			gtt_fix_index_backend_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -1300,7 +1325,22 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+			/*
+			 * For global temporary table, get the latest relfilenode
+			 * from localhash and put it in relcache.
+			 */
+			if (OidIsValid(newrelnode) &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2251,6 +2291,9 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		/* The state of the global temporary table's index may need to be set */
+		gtt_fix_index_backend_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3489,6 +3532,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3598,28 +3645,39 @@ void
 RelationSetNewRelfilenode(Relation relation, char persistence)
 {
 	Oid			newrelfilenode;
-	Relation	pg_class;
-	HeapTuple	tuple;
+	Relation	pg_class = NULL;
+	HeapTuple	tuple = NULL;
 	Form_pg_class classform;
 	MultiXactId minmulti = InvalidMultiXactId;
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
 
+	/*
+	 * For global temporary table, storage information for the table is
+	 * maintained locally, not in catalog.
+	 */
+	bool		update_catalog = !RELATION_IS_GLOBAL_TEMP(relation);
+
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
 
-	/*
-	 * Get a writable copy of the pg_class tuple for the given relation.
-	 */
-	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+	memset(&classform, 0, sizeof(classform));
 
-	tuple = SearchSysCacheCopy1(RELOID,
-								ObjectIdGetDatum(RelationGetRelid(relation)));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "could not find tuple for relation %u",
-			 RelationGetRelid(relation));
-	classform = (Form_pg_class) GETSTRUCT(tuple);
+	if (update_catalog)
+	{
+		/*
+		 * Get a writable copy of the pg_class tuple for the given relation.
+		 */
+		pg_class = table_open(RelationRelationId, RowExclusiveLock);
+
+		tuple = SearchSysCacheCopy1(RELOID,
+									ObjectIdGetDatum(RelationGetRelid(relation)));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u",
+				 RelationGetRelid(relation));
+		classform = (Form_pg_class) GETSTRUCT(tuple);
+	}
 
 	/*
 	 * Schedule unlinking of the old storage at transaction commit.
@@ -3645,7 +3703,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;
@@ -3665,6 +3723,18 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 			break;
 	}
 
+	/* For global temporary table */
+	if (!update_catalog)
+	{
+		Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+		Assert(RELATION_IS_GLOBAL_TEMP(relation));
+		Assert(!RelationIsMapped(relation));
+
+		/* Make cache invalid and set new relnode to local cache. */
+		CacheInvalidateRelcache(relation);
+		relation->rd_node.relNode = relnode;
+	}
 	/*
 	 * If we're dealing with a mapped index, pg_class.relfilenode doesn't
 	 * change; instead we have to send the update to the relation mapper.
@@ -3674,7 +3744,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	 * possibly-inaccurate values of relpages etc, but those will be fixed up
 	 * later.
 	 */
-	if (RelationIsMapped(relation))
+	else if (RelationIsMapped(relation))
 	{
 		/* This case is only supported for indexes */
 		Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3720,9 +3790,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 		CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 	}
 
-	heap_freetuple(tuple);
+	if (update_catalog)
+	{
+		heap_freetuple(tuple);
 
-	table_close(pg_class, RowExclusiveLock);
+		table_close(pg_class, RowExclusiveLock);
+	}
 
 	/*
 	 * Make the pg_class row change or relation map change visible.  This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index d2ce4a84506..7717ee2ca13 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -44,6 +44,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "commands/prepare.h"
 #include "commands/trigger.h"
@@ -152,6 +153,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temporary table feature.
+ * table schema are still saved in catalog.
+ *
+ * num > 0 means allows the database to manage multiple active tables at the same time.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2127,6 +2140,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 "
@@ -2697,6 +2719,16 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"vacuum_gtt_defer_check_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("The defer check age of GTT, used to check expired data after vacuum."),
+			NULL
+		},
+		&vacuum_gtt_defer_check_age,
+		10000, 0, 1000000,
+		NULL, NULL, NULL
+	},
+
 	/*
 	 * See also CheckRequiredParameterValues() if this parameter changes
 	 */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a485fb2d070..80658c4f3ba 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2503,6 +2503,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
 		dopt->no_unlogged_table_data)
 		return;
 
+	/* Don't dump data in global temporary table/sequence */
+	if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		return;
+
 	/* Check that the data is not explicitly excluded */
 	if (simple_oid_list_member(&tabledata_exclude_oids,
 							   tbinfo->dobj.catId.oid))
@@ -15961,6 +15965,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
 		char	   *foreign = "";
+		char	   *table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -16014,9 +16019,15 @@ dumpTableSchema(Archive *fout, const 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);
 
@@ -16380,6 +16391,15 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 			}
 		}
 
+		/*
+		 * Transaction information for the global temporary table is not stored
+		 * in the pg_class.
+		 */
+		if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			Assert(tbinfo->frozenxid == 0);
+			Assert(tbinfo->minmxid == 0);
+		}
 		/*
 		 * In binary_upgrade mode, arrange to restore the old relfrozenxid and
 		 * relminmxid of all vacuumable relations.  (While vacuum.c processes
@@ -16387,7 +16407,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		 * of other relations; so this "if" lacks RELKIND_TOASTVALUE, and the
 		 * child toast table is handled below.)
 		 */
-		if (dopt->binary_upgrade &&
+		else if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
 			 tbinfo->relkind == RELKIND_MATVIEW))
 		{
@@ -17390,6 +17410,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	PQExpBuffer query = createPQExpBuffer();
 	PQExpBuffer delqry = createPQExpBuffer();
 	char	   *qseqname;
+	bool	   global_temp_seq = false;
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
@@ -17399,9 +17420,12 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
-						  "FROM pg_catalog.pg_sequence "
-						  "WHERE seqrelid = '%u'::oid",
+						  "seqcache, seqcycle, "
+						  "c.relpersistence "
+						  "FROM pg_catalog.pg_sequence s, "
+						  "pg_catalog.pg_class c "
+						  "WHERE seqrelid = '%u'::oid "
+						  "and s.seqrelid = c.oid",
 						  tbinfo->dobj.catId.oid);
 	}
 	else if (fout->remoteVersion >= 80400)
@@ -17446,6 +17470,9 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
 
+	if (fout->remoteVersion >= 140000)
+		global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
 	if (strcmp(seqtype, "smallint") == 0)
@@ -17523,9 +17550,13 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	}
 	else
 	{
-		appendPQExpBuffer(query,
-						  "CREATE SEQUENCE %s\n",
-						  fmtQualifiedDumpable(tbinfo));
+		appendPQExpBuffer(query, "CREATE ");
+
+		if (global_temp_seq)
+			appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+		appendPQExpBuffer(query, "SEQUENCE %s\n",
+								  fmtQualifiedDumpable(tbinfo));
 
 		if (strcmp(seqtype, "bigint") != 0)
 			appendPQExpBuffer(query, "    AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index ad5f3919956..f3819860096 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -88,7 +88,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -178,7 +178,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 5d9a26cf822..2de11d5d707 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -447,8 +449,17 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
 			 "         ON c.relnamespace = n.oid "
 			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
+			 CppAsString2(RELKIND_MATVIEW) ") AND ");
+
+	if (skip_gtt)
+	{
+		/* exclude global temp tables */
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+			"    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND ");
+	}
+
 	/* exclude possible orphaned temp tables */
+	snprintf(query + strlen(query), sizeof(query) - strlen(query),
 			 "    ((n.nspname !~ '^pg_temp_' AND "
 			 "      n.nspname !~ '^pg_toast_temp_' AND "
 			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 3628bd74a7b..bbb9b5ea13d 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -407,7 +407,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -645,7 +645,10 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+			/* exclude global temp tables */
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -656,7 +659,10 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+		/* exclude global temp tables */
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index ca0795f68ff..018a2effd4b 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -389,7 +389,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index a33d77c0efc..42fd7ec0c70 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -4067,7 +4067,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ecae9df8eda..d226b2dfef9 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1059,6 +1059,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2542,6 +2544,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2755,6 +2760,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE", "SEQUENCE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 6ce480b49c1..dda3f3c5a60 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -85,7 +85,7 @@ extern Oid	heap_create_with_catalog(const char *relname,
 
 extern void heap_drop_with_catalog(Oid relid);
 
-extern void heap_truncate(List *relids);
+extern void heap_truncate(List *relids, bool is_global_temp);
 
 extern void heap_truncate_one_rel(Relation rel);
 
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 008f723e104..875b1003899 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -157,6 +157,7 @@ extern void reindex_index(Oid indexId, bool skip_constraint_checks,
 #define REINDEX_REL_CHECK_CONSTRAINTS		0x04
 #define REINDEX_REL_FORCE_INDEXES_UNLOGGED	0x08
 #define REINDEX_REL_FORCE_INDEXES_PERMANENT 0x10
+#define REINDEX_REL_PROCESS_GLOBAL_TEMP		0x20
 
 extern bool reindex_relation(Oid relid, int flags, ReindexParams *params);
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index fef9945ed8f..9176b7dcc07 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -172,6 +172,7 @@ DECLARE_INDEX(pg_class_tblspc_relfilenode_index, 3455, ClassTblspcRelfilenodeInd
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index d068d6532ec..fd5089388f2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5735,6 +5735,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '9874',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '9875',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '9876',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '9877',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 0ab32b44e91..92e9f8ba485 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,7 +22,7 @@
 /* GUC variables */
 extern int	wal_skip_threshold;
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationPreTruncate(Relation rel);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 00000000000..8a3d9558712
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,45 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern int		vacuum_gtt_defer_check_age;
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Oid relid,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void force_enable_gtt_index(Relation index);
+extern void gtt_fix_index_backend_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index 40544dd4c70..7b66d808fc5 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 336549cc5f0..3e8167134b7 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -86,7 +86,7 @@ extern void find_composite_type_dependencies(Oid typeOid,
 
 extern void check_of_type(HeapTuple typetuple);
 
-extern void register_on_commit_action(Oid relid, OnCommitAction action);
+extern void register_on_commit_action(Oid relid, OnCommitAction action, bool is_gloal_temp);
 extern void remove_on_commit_action(Oid relid);
 
 extern void PreCommit_on_commit_actions(void);
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 8336c2c5a29..bddcfe7256d 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index c86ccdaf608..6b395551c1d 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -399,6 +399,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index a8f052e4845..4b4ed1a13aa 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -189,6 +189,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_SHARED_TIDBITMAP,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_PER_XACT_PREDICATE_LIST,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index be67d8a8616..e2f8bb5162d 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -157,6 +157,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId backend_gtt_frozenxid;	/* backend level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index b01fa52139a..8efffa55ac5 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -94,4 +94,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index aa18d304ac0..524c9d7de3f 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -288,6 +288,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 b4faa1c1238..a74558a8383 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	SMgrRelation rd_smgr;		/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -326,6 +326,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -608,11 +609,13 @@ RelationGetSmgr(Relation rel)
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -620,6 +623,7 @@ RelationGetSmgr(Relation rel)
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -632,6 +636,30 @@ RelationGetSmgr(Relation rel)
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP_ON_CURRENT_SESSION
+ *		Test a rel is either local temp relation of this session
+ *		or global temp relation.
+ */
+#define RELATION_IS_TEMP_ON_CURRENT_SESSION(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is local temp relation or global temporary relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RelpersistenceTsTemp
+ *		Test a relpersistence is local temp relation or global temporary relation.
+ */
+#define RelpersistenceTsTemp(relpersistence) \
+	(relpersistence == RELPERSISTENCE_TEMP || \
+	 relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -677,6 +705,19 @@ RelationGetSmgr(Relation rel)
 	 (relation)->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&	\
 	 !IsCatalogRelation(relation))
 
+/* For global temporary table */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/* Get on commit clause value only for global temporary table */
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+/* Get relpersistence for relation */
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
-- 
2.30.1 (Apple Git-130)

0001-gtt-v55-reademe.patchapplication/octet-stream; name=0001-gtt-v55-reademe.patchDownload
diff --git a/README.gtt.txt b/README.gtt.txt
new file mode 100644
index 00000000000..d181df9acd7
--- /dev/null
+++ b/README.gtt.txt
@@ -0,0 +1,172 @@
+Global Temporary Table(GTT)
+=========================================
+
+Feature description
+-----------------------------------------
+
+Previously, temporary tables are defined once and automatically
+exist (starting with empty contents) in every session before using them.
+
+The temporary table implementation in PostgreSQL, known as Local temp tables(LTT),
+did not fully comply with the SQL standard. This version added the support of
+Global Temporary Table .
+
+The metadata of Global Temporary Table is persistent and shared among sessions.
+The data stored in the Global temporary table is independent of sessions. This
+means, when a session creates a Global Temporary Table and writes some data.
+Other sessions cannot see those data, but they have an empty Global Temporary
+Table with same schema.
+
+Like local temporary table, Global Temporary Table supports ON COMMIT PRESERVE ROWS
+or ON COMMIT DELETE ROWS clause, so that data in the temporary table can be
+cleaned up or preserved automatically when a session exits or a transaction COMMITs.
+
+Unlike Local Temporary Table, Global Temporary Table does not support
+ON COMMIT DROP clauses.
+
+In following paragraphs, we use GTT for Global Temporary Table and LTT for
+local temporary table.
+
+Main design ideas
+-----------------------------------------
+In general, GTT and LTT use the same storage and buffer design and
+implementation. The storage files for both types of temporary tables are named
+as t_backendid_relfilenode, and the local buffer is used to cache the data.
+
+The schema of GTTs is shared among sessions while their data are not. We build
+a new mechanisms to manage those non-shared data and their statistics.
+Here is the summary of changes:
+
+1) CATALOG
+GTTs store session-specific data. The storage information of GTTs'data, their
+transaction information, and their statistics are not stored in the catalog.
+
+2) STORAGE INFO & STATISTICS INFO & TRANSACTION INFO
+In order to maintain durability and availability of GTTs'session-specific data,
+their storage information, statistics, and transaction information is managed
+in a local hash table tt_storage_local_hash.
+
+3) DDL
+Currently, GTT supports almost all table'DDL except CLUSTER/VACUUM FULL.
+Part of the DDL behavior is limited by shared definitions and multiple copies of
+local data, and we added some structures to handle this.
+
+A shared hash table active_gtt_shared_hash is added to track the state of the
+GTT in a different session. This information is recorded in the hash table
+during the DDL execution of the GTT.
+
+The data stored in a GTT can only be modified or accessed by owning session.
+The statements that only modify data in a GTT do not need a high level of
+table locking. The operations making those changes include truncate GTT,
+reindex GTT, and lock GTT.
+
+4) MVCC commit log(clog) cleanup
+Each GTT in a session has its own piece of data, and they have their own
+transaction information. We set up data structures to track and maintain
+this information. The cleaning of CLOGs also needs to consider the transaction
+information of GTT.
+
+Detailed design
+-----------------------------------------
+
+1. CATALOG
+1.1 relpersistence
+define RELPERSISTENCEGLOBALTEMP 'g'
+Mark Global Temporary Table in pg_class relpersistence to 'g'. The relpersistence
+of indexes created on the GTT, sequences on GTT and toast tables on GTT are
+also set to 'g'
+
+1.2 on commit clause
+LTT's status associated with on commit DELETE ROWS and on commit PRESERVE ROWS
+is not stored in catalog. Instead, GTTs need a bool value on_commit_delete_rows
+in reloptions which is shared among sessions.
+
+1.3 gram.y
+GTT is already supported in syntax tree. We remove the warning message
+"GLOBAL is deprecated in temporary table creation" and mark
+relpersistence = RELPERSISTENCEGLOBALTEMP.
+
+2. STORAGE INFO & STATISTICS INFO & TRANSACTION INFO
+2.1. gtt_storage_local_hash
+Each backend creates a local hashtable gtt_storage_local_hash to track a GTT's
+storage file information, statistics, and transaction information.
+
+2.2 GTT storage file info track
+1) When one session inserts data into a GTT for the first time, record the
+storage info to gtt_storage_local_hash.
+2) Use beforeshmemexit to ensure that all files of session GTT are deleted when
+the session exits.
+
+2.3 statistics info
+1) relpages reltuples relallvisible relfilenode
+2) The statistics of each column from pg_statistic
+All the above information is stored in gtt_storage_local_hash.
+When doing vacuum or analyze, GTT's statistic is updated, which is used by
+the SQL planner.
+The statistics summarizes only data in the current session.
+
+2.3 transaction info track
+frozenxid minmulti from pg_class is stored to gtt_storage_local_hash.
+
+3 DDL
+3.1. active_gtt_shared_hash
+This is the hash table created in shared memory to trace the GTT files initialized
+in each session. Each hash entry contains a bitmap that records the backendid of
+the initialized GTT file. With this hash table, we know which backend/session
+is using this GTT. Such information is used during GTT's DDL operations.
+
+3.2 DROP GTT
+One GTT is allowed to be deleted when there is only one session using the table
+and the session is the current session. After holding the lock on GTT,
+active_gtt_shared_hash is checked to ensure that.
+
+3.3 ALTER GTT/DROP INDEX ON GTT
+Same as drop GTT.
+
+3.4 CREATE INDEX ON GTT
+1) create index on GTT statements build index based on local data in a session.
+2) After the index is created, record the index metadata to the catalog.
+3) Other sessions can enable or disable the local GTT index.
+
+3.5 TRUNCATE/REINDEX GTT
+The SQL truncate/reindex command open the GTT using AccessShareLock lock,
+not AccessExclusiveLock, because this command only cleans up local data and
+local buffers in current session. This allows these operations to be executed
+concurrently between sessions, unlike normal tables.
+
+3.6 LOCK GTT
+A lock GTT statement does not hold any relation lock.
+
+3.7 CLUSTER GTT/VACUUM FULL GTT
+The current version does not support.
+
+4 MVCC commit log(clog) cleanup
+
+The GTT storage file contains transaction information. Queries for GTT data rely
+on transaction information such as clog. The transaction information required by
+each session may be completely different. We need to ensure that the transaction
+information of the GTT data is not cleaned up during its lifetime and that
+transaction resources are recycled at the instance level.
+
+4.1 The session level GTT oldest frozenxid
+1) To manage all GTT transaction information, add session level oldest frozenxid
+in each session. When one GTT is created or removed, record the session level
+oldest frozenxid and store it in MyProc.
+2) When vacuum advances the database's frozenxid, session level oldest frozenxid
+should be considered. This is acquired by searching all of MyProc. This way,
+we can avoid the clog required by GTTs to be cleaned.
+
+4.2 vacuum GTT
+Users can perform vacuum over a GTT to clean up local data in the GTT.
+
+4.3 autovacuum GTT
+Autovacuum skips all GTTs, because the data in GTTs is only visible in current session.
+
+5 OTHERS
+5.1 Parallel query
+Planner does not produce parallel query plans for SQL related to GTT. Because
+GTT private data cannot be accessed across processes.
+
+5.2 WAL and Logical replication
+Like LTT, the DML on GTT does not record WAL and is not parsed or replay by
+the logical replication.
\ No newline at end of file
-- 
2.30.1 (Apple Git-130)

0002-gtt-v55-doc.patchapplication/octet-stream; name=0002-gtt-v55-doc.patchDownload
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 473a0a4aeb..e510bde8ac 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -169,32 +169,67 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     <listitem>
      <para>
       If specified, the table is created as a temporary table.
-      Temporary tables are automatically dropped at the end of a
-      session, or optionally at the end of the current transaction
-      (see <literal>ON COMMIT</literal> below).  The default
-      search_path includes the temporary schema first and so identically
-      named existing permanent tables are not chosen for new plans
+      Optionally, <literal>GLOBAL</literal> or <literal>LOCAL</literal>
+      can be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
+      They represent two types of temporary tables supported by <productname>PostgreSQL</productname>:
+      global temporary table and local temporary table. Without specified
+      GLOBAL or LOCAL, a local temporary table is created by default.
+     </para>
+
+    <para>
+     Both types of temporary tables’ data are truncated at the
+     end of a session or optionally at the end of the current transaction.
+     (see <literal>ON COMMIT</literal> below). For global temporary table,
+     its schema is reserved and reused by future sessions or transactions.
+     For local temporary table, both its data and its schema are dropped.
+    </para>
+
+    <variablelist>
+     <varlistentry>
+      <term><literal>Global Temporary Table</literal></term>
+      <listitem>
+       <para>
+        Global temporary table are defined just once and automatically exist
+        (starting with empty contents) in every session that needs them.
+        The schema definition of temporary tables is persistent and shared among sessions.
+        However, the data in temporary tables are kept private to sessions themselves,
+        even though they use same name and same schema.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>Local Temporary Table</literal></term>
+     <listitem>
+     <para>
+      Local temporary table are automatically dropped at the end of a
+      session (include schema and data). Future sessions need to create
+      their own temporary tables when they are used.
+     </para>
+     <para>
+      The default search_path includes the temporary schema first and so
+      identically named existing permanent tables are not chosen for new plans
       while the temporary table exists, unless they are referenced
       with schema-qualified names. Any indexes created on a temporary
       table are automatically temporary as well.
      </para>
+     </listitem>
+     </varlistentry>
+    </variablelist>
 
-     <para>
-      The <link linkend="autovacuum">autovacuum daemon</link> cannot
-      access and therefore cannot vacuum or analyze temporary tables.
-      For this reason, appropriate vacuum and analyze operations should be
-      performed via session SQL commands.  For example, if a temporary
-      table is going to be used in complex queries, it is wise to run
-      <command>ANALYZE</command> on the temporary table after it is populated.
-     </para>
+    <para>
+     The <link linkend="autovacuum">autovacuum daemon</link> cannot
+     access and therefore cannot vacuum or analyze temporary tables.
+     For this reason, appropriate vacuum and analyze operations should be
+     performed via session SQL commands.  For example, if a temporary
+     table is going to be used in complex queries, it is wise to run
+     <command>ANALYZE</command> on the temporary table after it is populated.
+    </para>
+    <para>
+     The Temporary table resembles the SQL standard, but has some differences.
+     see <xref linkend="sql-createtable-compatibility"/> below.
+    </para>
 
-     <para>
-      Optionally, <literal>GLOBAL</literal> or <literal>LOCAL</literal>
-      can be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
-      This presently makes no difference in <productname>PostgreSQL</productname>
-      and is deprecated; see
-      <xref linkend="sql-createtable-compatibility"/> below.
-     </para>
     </listitem>
    </varlistentry>
 
@@ -2133,13 +2168,17 @@ CREATE TABLE cities_partdef
    <title>Temporary Tables</title>
 
    <para>
-    Although the syntax of <literal>CREATE TEMPORARY TABLE</literal>
-    resembles that of the SQL standard, the effect is not the same.  In the
-    standard,
-    temporary tables are defined just once and automatically exist (starting
-    with empty contents) in every session that needs them.
-    <productname>PostgreSQL</productname> instead
-    requires each session to issue its own <literal>CREATE TEMPORARY
+    Although the syntax of <literal>CREATE GLOBAL/LOCAL TEMPORARY TABLE</literal>
+    resembles that of the SQL standard, the effect is not the same.
+    The global temporary table follows the SQL standards while local temporary
+    table does not.
+   </para>
+
+   <para>
+    First, in the standard, both global and local temporary tables are defined just
+    once and automatically exist (starting with empty contents) in every session
+    that needs them. For local temporary tables, <productname>PostgreSQL</productname>
+    instead requires each session to issue its own <literal>CREATE LOCAL TEMPORARY
     TABLE</literal> command for each temporary table to be used.  This allows
     different sessions to use the same temporary table name for different
     purposes, whereas the standard's approach constrains all instances of a
@@ -2147,29 +2186,14 @@ CREATE TABLE cities_partdef
    </para>
 
    <para>
-    The standard's definition of the behavior of temporary tables is
-    widely ignored.  <productname>PostgreSQL</productname>'s behavior
-    on this point is similar to that of several other SQL databases.
-   </para>
-
-   <para>
-    The SQL standard also distinguishes between global and local temporary
+    Second, the SQL standard distinguishes between global and local temporary
     tables, where a local temporary table has a separate set of contents for
     each SQL module within each session, though its definition is still shared
-    across sessions.  Since <productname>PostgreSQL</productname> does not
+    across sessions. Since <productname>PostgreSQL</productname> does not
     support SQL modules, this distinction is not relevant in
     <productname>PostgreSQL</productname>.
    </para>
 
-   <para>
-    For compatibility's sake, <productname>PostgreSQL</productname> will
-    accept the <literal>GLOBAL</literal> and <literal>LOCAL</literal> keywords
-    in a temporary table declaration, but they currently have no effect.
-    Use of these keywords is discouraged, since future versions of
-    <productname>PostgreSQL</productname> might adopt a more
-    standard-compliant interpretation of their meaning.
-   </para>
-
    <para>
     The <literal>ON COMMIT</literal> clause for temporary tables
     also resembles the SQL standard, but has some differences.
@@ -2177,7 +2201,8 @@ CREATE TABLE cities_partdef
     default behavior is <literal>ON COMMIT DELETE ROWS</literal>.  However, the
     default behavior in <productname>PostgreSQL</productname> is
     <literal>ON COMMIT PRESERVE ROWS</literal>.  The <literal>ON COMMIT
-    DROP</literal> option does not exist in SQL.
+    DROP</literal> option does not exist in SQL and is not supported by
+    global temporary table.
    </para>
   </refsect2>
 
-- 
2.30.1 (Apple Git-130)

#330Andrew Bille
andrewbille@gmail.com
In reply to: wenjing (#329)
Re: [Proposal] Global temporary tables

Thanks for the fix. It works for me.

Now I'm exploring another crash related to GTT, but I need a few days to
present a simple repro.

On Sat, Oct 9, 2021 at 2:41 PM wenjing <wjzeng2012@gmail.com> wrote:

Show quoted text

Thank you for pointing it out.
This is a bug that occurs during transaction rollback and process exit, I
fixed it, please confirm it.

Wenjing

#331wenjing zeng
wjzeng2012@gmail.com
In reply to: Andrew Bille (#330)
Re: [Proposal] Global temporary tables

2021年10月13日 13:08,Andrew Bille <andrewbille@gmail.com> 写道:

Thanks for the fix. It works for me.

Now I'm exploring another crash related to GTT, but I need a few days to present a simple repro.

Be deeply grateful.
Perhaps you can give the stack of problems so that you can start analyzing them as soon as possible.

Wenjing

Show quoted text

On Sat, Oct 9, 2021 at 2:41 PM wenjing <wjzeng2012@gmail.com <mailto:wjzeng2012@gmail.com>> wrote:

Thank you for pointing it out.
This is a bug that occurs during transaction rollback and process exit, I fixed it, please confirm it.

Wenjing

#332Andrew Bille
andrewbille@gmail.com
In reply to: wenjing zeng (#331)
Re: [Proposal] Global temporary tables

On master with the v55 patches applied the following script leads to crash:
initdb -D data
pg_ctl -w -t 5 -D data -l server.log start

psql -t -c "begin; create global temp table gtt_with_index(a int primary
key, b text); commit; select pg_sleep(5);" >psql1.log &
psql -t -c "select pg_sleep(1); create index idx_b on gtt_with_index(b);"

psql2.log &

for i in `seq 40`; do (psql -t -c "select pg_sleep(1); insert into
gtt_with_index values(1,'test');" &); done

sleep 10

and I got crash
INSERT 0 1
...
INSERT 0 1
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
connection to server was lost
WARNING: terminating connection because of crash of another server process
DETAIL: The postmaster has commanded this server process to roll back the
current transaction and exit, because another server process exited
abnormally and possibly corrupted shared memory.
HINT: In a moment you should be able to reconnect to the database and
repeat your command.
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
connection to server was lost

and some coredumps with the following stack:

[New LWP 1821493]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Core was generated by `postgres: andrew regression [local] INSERT
'.
Program terminated with signal SIGABRT, Aborted.
#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
50 ../sysdeps/unix/sysv/linux/raise.c: No such file or directory.
(gdb) bt
#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
#1 0x00007f021d809859 in __GI_abort () at abort.c:79
#2 0x0000564dc1bd22e8 in ExceptionalCondition
(conditionName=conditionName@entry=0x564dc1c5c957
"index->rd_index->indisvalid", errorType=errorType@entry=0x564dc1c2a00b
"FailedAssertion", fileName=fileName@entry=0x564dc1c5c854 "storage_gtt.c",
lineNumber=lineNumber@entry=1381) at assert.c:69
#3 0x0000564dc185778b in init_gtt_storage
(operation=operation@entry=CMD_INSERT,
resultRelInfo=resultRelInfo@entry=0x564dc306f6c0) at storage_gtt.c:1381
#4 0x0000564dc194c888 in ExecInsert (mtstate=0x564dc306f4a8,
resultRelInfo=0x564dc306f6c0, slot=0x564dc30706d0, planSlot=0x564dc306fca0,
estate=0x564dc306f230, canSetTag=<optimized out>) at nodeModifyTable.c:638
#5 0x0000564dc194d945 in ExecModifyTable (pstate=<optimized out>) at
nodeModifyTable.c:2565
#6 0x0000564dc191ca83 in ExecProcNode (node=0x564dc306f4a8) at
../../../src/include/executor/executor.h:257
#7 ExecutePlan (execute_once=<optimized out>, dest=0x564dc310ed80,
direction=<optimized out>, numberTuples=0, sendTuples=<optimized out>,
operation=CMD_INSERT, use_parallel_mode=<optimized out>,
planstate=0x564dc306f4a8, estate=0x564dc306f230) at execMain.c:1555
#8 standard_ExecutorRun (queryDesc=0x564dc306bce0, direction=<optimized
out>, count=0, execute_once=<optimized out>) at execMain.c:361
#9 0x0000564dc1ab47a0 in ProcessQuery (plan=<optimized out>,
sourceText=0x564dc3049a30 "select pg_sleep(1); insert into gtt_with_index
values(1,'test');", params=0x0, queryEnv=0x0, dest=0x564dc310ed80,
qc=0x7ffd3a6cf2e0) at pquery.c:160
#10 0x0000564dc1ab52e2 in PortalRunMulti (portal=portal@entry=0x564dc30acd80,
isTopLevel=isTopLevel@entry=true, setHoldSnapshot=setHoldSnapshot@entry=false,
dest=dest@entry=0x564dc310ed80, altdest=altdest@entry=0x564dc310ed80,
qc=qc@entry=0x7ffd3a6cf2e0)
at pquery.c:1274
#11 0x0000564dc1ab5861 in PortalRun (portal=portal@entry=0x564dc30acd80,
count=count@entry=9223372036854775807, isTopLevel=isTopLevel@entry=true,
run_once=run_once@entry=true, dest=dest@entry=0x564dc310ed80,
altdest=altdest@entry=0x564dc310ed80, qc=0x7ffd3a6cf2e0)
at pquery.c:788
#12 0x0000564dc1ab1522 in exec_simple_query (query_string=0x564dc3049a30
"select pg_sleep(1); insert into gtt_with_index values(1,'test');") at
postgres.c:1214
#13 0x0000564dc1ab327a in PostgresMain (dbname=<optimized out>,
username=<optimized out>) at postgres.c:4497
#14 0x0000564dc1a1f539 in BackendRun (port=<optimized out>, port=<optimized
out>) at postmaster.c:4560
#15 BackendStartup (port=<optimized out>) at postmaster.c:4288
#16 ServerLoop () at postmaster.c:1801
#17 0x0000564dc1a2053c in PostmasterMain (argc=<optimized out>,
argv=0x564dc3043fc0) at postmaster.c:1473
#18 0x0000564dc1750180 in main (argc=3, argv=0x564dc3043fc0) at main.c:198
(gdb) q

I've built the server using gcc 9 as following:
./configure --enable-debug --enable-cassert

Thanks to Alexander Lakhin for simplifying the repro.

On Thu, Oct 14, 2021 at 3:29 PM wenjing zeng <wjzeng2012@gmail.com> wrote:

Show quoted text

Be deeply grateful.
Perhaps you can give the stack of problems so that you can start analyzing
them as soon as possible.

Wenjing

#333wenjing
wjzeng2012@gmail.com
In reply to: Andrew Bille (#332)
4 attachment(s)
Re: [Proposal] Global temporary tables

Andrew Bille <andrewbille@gmail.com> 于2021年10月15日周五 下午3:44写道:

On master with the v55 patches applied the following script leads to crash:
initdb -D data
pg_ctl -w -t 5 -D data -l server.log start

psql -t -c "begin; create global temp table gtt_with_index(a int primary
key, b text); commit; select pg_sleep(5);" >psql1.log &
psql -t -c "select pg_sleep(1); create index idx_b on gtt_with_index(b);"

psql2.log &

for i in `seq 40`; do (psql -t -c "select pg_sleep(1); insert into
gtt_with_index values(1,'test');" &); done

sleep 10

and I got crash
INSERT 0 1
...
INSERT 0 1
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
connection to server was lost
WARNING: terminating connection because of crash of another server process
DETAIL: The postmaster has commanded this server process to roll back the
current transaction and exit, because another server process exited
abnormally and possibly corrupted shared memory.
HINT: In a moment you should be able to reconnect to the database and
repeat your command.
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
connection to server was lost

and some coredumps with the following stack:

[New LWP 1821493]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Core was generated by `postgres: andrew regression [local] INSERT
'.
Program terminated with signal SIGABRT, Aborted.
#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
50 ../sysdeps/unix/sysv/linux/raise.c: No such file or directory.
(gdb) bt
#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
#1 0x00007f021d809859 in __GI_abort () at abort.c:79
#2 0x0000564dc1bd22e8 in ExceptionalCondition
(conditionName=conditionName@entry=0x564dc1c5c957
"index->rd_index->indisvalid", errorType=errorType@entry=0x564dc1c2a00b
"FailedAssertion", fileName=fileName@entry=0x564dc1c5c854
"storage_gtt.c",
lineNumber=lineNumber@entry=1381) at assert.c:69
#3 0x0000564dc185778b in init_gtt_storage (operation=operation@entry=CMD_INSERT,
resultRelInfo=resultRelInfo@entry=0x564dc306f6c0) at storage_gtt.c:1381
#4 0x0000564dc194c888 in ExecInsert (mtstate=0x564dc306f4a8,
resultRelInfo=0x564dc306f6c0, slot=0x564dc30706d0, planSlot=0x564dc306fca0,
estate=0x564dc306f230, canSetTag=<optimized out>) at nodeModifyTable.c:638
#5 0x0000564dc194d945 in ExecModifyTable (pstate=<optimized out>) at
nodeModifyTable.c:2565
#6 0x0000564dc191ca83 in ExecProcNode (node=0x564dc306f4a8) at
../../../src/include/executor/executor.h:257
#7 ExecutePlan (execute_once=<optimized out>, dest=0x564dc310ed80,
direction=<optimized out>, numberTuples=0, sendTuples=<optimized out>,
operation=CMD_INSERT, use_parallel_mode=<optimized out>,
planstate=0x564dc306f4a8, estate=0x564dc306f230) at execMain.c:1555
#8 standard_ExecutorRun (queryDesc=0x564dc306bce0, direction=<optimized
out>, count=0, execute_once=<optimized out>) at execMain.c:361
#9 0x0000564dc1ab47a0 in ProcessQuery (plan=<optimized out>,
sourceText=0x564dc3049a30 "select pg_sleep(1); insert into gtt_with_index
values(1,'test');", params=0x0, queryEnv=0x0, dest=0x564dc310ed80,
qc=0x7ffd3a6cf2e0) at pquery.c:160
#10 0x0000564dc1ab52e2 in PortalRunMulti (portal=portal@entry=0x564dc30acd80,
isTopLevel=isTopLevel@entry=true, setHoldSnapshot=setHoldSnapshot@entry=false,
dest=dest@entry=0x564dc310ed80, altdest=altdest@entry=0x564dc310ed80,
qc=qc@entry=0x7ffd3a6cf2e0)
at pquery.c:1274
#11 0x0000564dc1ab5861 in PortalRun (portal=portal@entry=0x564dc30acd80,
count=count@entry=9223372036854775807, isTopLevel=isTopLevel@entry=true,
run_once=run_once@entry=true, dest=dest@entry=0x564dc310ed80,
altdest=altdest@entry=0x564dc310ed80, qc=0x7ffd3a6cf2e0)
at pquery.c:788
#12 0x0000564dc1ab1522 in exec_simple_query (query_string=0x564dc3049a30
"select pg_sleep(1); insert into gtt_with_index values(1,'test');") at
postgres.c:1214
#13 0x0000564dc1ab327a in PostgresMain (dbname=<optimized out>,
username=<optimized out>) at postgres.c:4497
#14 0x0000564dc1a1f539 in BackendRun (port=<optimized out>,
port=<optimized out>) at postmaster.c:4560
#15 BackendStartup (port=<optimized out>) at postmaster.c:4288
#16 ServerLoop () at postmaster.c:1801
#17 0x0000564dc1a2053c in PostmasterMain (argc=<optimized out>,
argv=0x564dc3043fc0) at postmaster.c:1473
#18 0x0000564dc1750180 in main (argc=3, argv=0x564dc3043fc0) at main.c:198
(gdb) q

I've built the server using gcc 9 as following:
./configure --enable-debug --enable-cassert

Thanks to Alexander Lakhin for simplifying the repro.

On Thu, Oct 14, 2021 at 3:29 PM wenjing zeng <wjzeng2012@gmail.com> wrote:

Be deeply grateful.
Perhaps you can give the stack of problems so that you can start
analyzing them as soon as possible.

Wenjing

Hi Andrew
I fixed the problem, please confirm again.
Thanks

Wenjing

Attachments:

0004-gtt-v56-regress.patchapplication/octet-stream; name=0004-gtt-v56-regress.patchDownload
diff --git a/src/test/isolation/expected/gtt-sequence.out b/src/test/isolation/expected/gtt-sequence.out
new file mode 100644
index 00000000000..31db2ebd423
--- /dev/null
+++ b/src/test/isolation/expected/gtt-sequence.out
@@ -0,0 +1,48 @@
+unused step name: s1_seq_restart
+Parsed test spec with 2 sessions
+
+starting permutation: s1_seq_next s2_seq_next s1_seq_next
+step s1_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      1
+(1 row)
+
+step s2_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      1
+(1 row)
+
+step s1_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      2
+(1 row)
+
+
+starting permutation: s1_select s2_select s1_insert s2_insert s1_select s2_select
+step s1_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+(0 rows)
+
+step s2_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+(0 rows)
+
+step s1_insert: insert into gtt_with_seq values(1);
+step s2_insert: insert into gtt_with_seq values(10);
+step s1_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+ 1| 3
+(1 row)
+
+step s2_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+10| 1
+(1 row)
+
diff --git a/src/test/isolation/expected/gtt-table.out b/src/test/isolation/expected/gtt-table.out
new file mode 100644
index 00000000000..5825773aa12
--- /dev/null
+++ b/src/test/isolation/expected/gtt-table.out
@@ -0,0 +1,675 @@
+Parsed test spec with 3 sessions
+
+starting permutation: s1_update_d
+step s1_update_d: update gtt_on_commit_delete_row set b = 'update'
+
+starting permutation: s1_select_d s1_insert_d s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_begin s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+1|test1
+(1 row)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_begin s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+1|test1
+(1 row)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_select_d s1_truncate_d s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_begin s1_insert_d s1_select_d s1_truncate_d s1_select_d s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_begin s1_insert_d s1_select_d s1_truncate_d s1_select_d s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_insert_d s1_select_d s1_begin s1_insert_d s1_select_d s1_save_1 s1_truncate_d s1_insert_d s1_select_d s1_save_2 s1_truncate_d s1_insert_d s1_select_d s1_save_3 s1_rollback_to_save_2 s1_select_d s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_save_1: SAVEPOINT save1
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_save_2: SAVEPOINT save2
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+4|test1
+(1 row)
+
+step s1_save_3: SAVEPOINT save3
+step s1_rollback_to_save_2: rollback to savepoint save2
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+5|test1
+(2 rows)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_insert_d s1_select_d s1_begin s1_insert_d s1_select_d s1_save_1 s1_truncate_d s1_insert_d s1_select_d s1_save_2 s1_truncate_d s1_insert_d s1_select_d s1_save_3 s1_rollback_to_save_2 s1_select_d s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_save_1: SAVEPOINT save1
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_save_2: SAVEPOINT save2
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+4|test1
+(1 row)
+
+step s1_save_3: SAVEPOINT save3
+step s1_rollback_to_save_2: rollback to savepoint save2
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+5|test1
+(2 rows)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_update_p
+step s2_update_p: update gtt_on_commit_preserve_row set b = 'update'
+
+starting permutation: s2_select_p s2_insert_p s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_begin s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_begin s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_select_p s2_insert_p s2_select_p s2_truncate_p s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_select_p s2_insert_p s2_begin s2_insert_p s2_select_p s2_truncate_p s2_select_p s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_insert_p s2_begin s2_insert_p s2_select_p s2_truncate_p s2_select_p s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_insert_p s2_select_p s2_begin s2_insert_p s2_select_p s2_save_1 s2_truncate_p s2_insert_p s2_select_p s2_save_2 s2_truncate_p s2_insert_p s2_select_p s2_save_3 s2_rollback_to_save_2 s2_select_p s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_save_1: SAVEPOINT save1
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_save_2: SAVEPOINT save2
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+4|test10
+(1 row)
+
+step s2_save_3: SAVEPOINT save3
+step s2_rollback_to_save_2: rollback to savepoint save2
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+
+starting permutation: s2_insert_p s2_select_p s2_begin s2_insert_p s2_select_p s2_save_1 s2_truncate_p s2_insert_p s2_select_p s2_save_2 s2_truncate_p s2_insert_p s2_select_p s2_save_3 s2_rollback_to_save_2 s2_select_p s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_save_1: SAVEPOINT save1
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_save_2: SAVEPOINT save2
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+4|test10
+(1 row)
+
+step s2_save_3: SAVEPOINT save3
+step s2_rollback_to_save_2: rollback to savepoint save2
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_insert_p s2_insert_p s1_select_p s2_select_p
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_truncate_p s2_truncate_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_begin s1_insert_d s2_insert_d s1_truncate_d s2_insert_d s1_commit
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s2_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s2_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_commit: commit
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_reindex_p s2_reindex_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_reindex_p: reindex table gtt_on_commit_preserve_row
+step s2_reindex_p: reindex table gtt_on_commit_preserve_row
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_reindex_i_p s2_reindex_i_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_reindex_i_p: reindex index gtt_on_commit_preserve_row_pkey
+step s2_reindex_i_p: reindex index gtt_on_commit_preserve_row_pkey
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_insert_c s3_create_c s3_insert_c s1_insert_c s1_analyze_c s2_analyze_c s3_analyze_c s1_select_c s2_select_c s3_select_c
+step s2_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s3_create_c: create unique index idx_temp_table_a on gtt_test_createindex(a)
+step s3_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s1_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s1_analyze_c: analyze gtt_test_createindex
+step s2_analyze_c: analyze gtt_test_createindex
+step s3_analyze_c: analyze gtt_test_createindex
+step s1_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                                               
+---------------------------------------------------------
+Index Scan using idx_temp_table_a on gtt_test_createindex
+  Index Cond: (a = 1)                                    
+(2 rows)
+
+step s2_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                      
+--------------------------------
+Seq Scan on gtt_test_createindex
+  Filter: (a = 1)               
+(2 rows)
+
+step s3_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                                               
+---------------------------------------------------------
+Index Scan using idx_temp_table_a on gtt_test_createindex
+  Index Cond: (a = 1)                                    
+(2 rows)
+
+
+starting permutation: s1_begin s2_begin s1_lock_p s2_lock_p s1_truncate_p s2_truncate_p s1_insert_p s2_insert_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_lock_p: LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE
+step s2_lock_p: LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE
+step s1_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index f4c01006fc1..746a17f824c 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -96,3 +96,5 @@ test: plpgsql-toast
 test: truncate-conflict
 test: serializable-parallel
 test: serializable-parallel-2
+test: gtt-sequence
+test: gtt-table
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 88594a3cb5d..ec643aadb5f 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -80,9 +80,30 @@ disconnect_atexit(void)
 {
 	int			i;
 
-	for (i = 0; i < nconns; i++)
+	for (i = 1; i < nconns; i++)
 		if (conns[i].conn)
 			PQfinish(conns[i].conn);
+
+	if (parseresult.destroy)
+	{
+		PGresult   *res;
+
+		res = PQexec(conns[0].conn, parseresult.destroy);
+		if (PQresultStatus(res) == PGRES_TUPLES_OK)
+		{
+			printResultSet(res);
+		}
+		else if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		{
+			fprintf(stderr, "destroy failed: %s",
+					PQerrorMessage(conns[0].conn));
+			/* don't exit on teardown failure */
+		}
+		PQclear(res);
+	}
+
+	if (conns[0].conn)
+		PQfinish(conns[0].conn);
 }
 
 int
@@ -214,6 +235,24 @@ main(int argc, char **argv)
 	PQclear(res);
 	termPQExpBuffer(&wait_query);
 
+	if (parseresult.initialize)
+	{
+		PGresult   *res;
+
+		res = PQexec(conns[0].conn, parseresult.initialize);
+		if (PQresultStatus(res) == PGRES_TUPLES_OK)
+		{
+			printResultSet(res);
+		}
+		else if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		{
+			fprintf(stderr, "initialize failed: %s",
+					PQerrorMessage(conns[0].conn));
+			/* don't exit on teardown failure */
+		}
+		PQclear(res);
+	}
+
 	/*
 	 * Run the permutations specified in the spec, or all if none were
 	 * explicitly specified.
diff --git a/src/test/isolation/isolationtester.h b/src/test/isolation/isolationtester.h
index 5f300219c20..b5a29893da9 100644
--- a/src/test/isolation/isolationtester.h
+++ b/src/test/isolation/isolationtester.h
@@ -81,6 +81,8 @@ typedef struct
 	int			nsessions;
 	Permutation **permutations;
 	int			npermutations;
+	char	   *initialize;
+	char	   *destroy;
 } TestSpec;
 
 extern TestSpec parseresult;
diff --git a/src/test/isolation/specparse.y b/src/test/isolation/specparse.y
index c25aa1a73fa..2784f758ed9 100644
--- a/src/test/isolation/specparse.y
+++ b/src/test/isolation/specparse.y
@@ -39,7 +39,7 @@ TestSpec		parseresult;			/* result of parsing is left here */
 }
 
 %type <ptr_list> setup_list
-%type <str>  opt_setup opt_teardown
+%type <str>  opt_setup opt_teardown opt_initialize opt_destroy
 %type <str> setup
 %type <ptr_list> step_list session_list permutation_list opt_permutation_list
 %type <ptr_list> permutation_step_list blocker_list
@@ -51,23 +51,27 @@ TestSpec		parseresult;			/* result of parsing is left here */
 
 %token <str> sqlblock identifier
 %token <integer> INTEGER
-%token NOTICES PERMUTATION SESSION SETUP STEP TEARDOWN TEST
+%token NOTICES PERMUTATION SESSION SETUP STEP TEARDOWN TEST INITIALIZE DESTROY
 
 %%
 
 TestSpec:
+			opt_initialize
 			setup_list
 			opt_teardown
+			opt_destroy
 			session_list
 			opt_permutation_list
 			{
-				parseresult.setupsqls = (char **) $1.elements;
-				parseresult.nsetupsqls = $1.nelements;
-				parseresult.teardownsql = $2;
-				parseresult.sessions = (Session **) $3.elements;
-				parseresult.nsessions = $3.nelements;
-				parseresult.permutations = (Permutation **) $4.elements;
-				parseresult.npermutations = $4.nelements;
+				parseresult.setupsqls = (char **) $2.elements;
+				parseresult.nsetupsqls = $2.nelements;
+				parseresult.teardownsql = $3;
+				parseresult.sessions = (Session **) $5.elements;
+				parseresult.nsessions = $5.nelements;
+				parseresult.permutations = (Permutation **) $6.elements;
+				parseresult.npermutations = $6.nelements;
+				parseresult.initialize = $1;
+				parseresult.destroy = $4;
 			}
 		;
 
@@ -100,6 +104,16 @@ opt_teardown:
 			| TEARDOWN sqlblock	{ $$ = $2; }
 		;
 
+opt_initialize:
+			/* EMPTY */			{ $$ = NULL; }
+			| INITIALIZE sqlblock	{ $$ = $2; }
+		;
+
+opt_destroy:
+			/* EMPTY */			{ $$ = NULL; }
+			| DESTROY sqlblock	{ $$ = $2; }
+		;
+
 session_list:
 			session_list session
 			{
diff --git a/src/test/isolation/specs/gtt-sequence.spec b/src/test/isolation/specs/gtt-sequence.spec
new file mode 100644
index 00000000000..88eece45e29
--- /dev/null
+++ b/src/test/isolation/specs/gtt-sequence.spec
@@ -0,0 +1,39 @@
+# Tests for global temporary relations
+
+initialize
+{
+  CREATE GLOBAL TEMPORARY TABLE if not exists gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+}
+
+destroy
+{
+  /* wait other backend exit */
+  select pg_sleep(1);
+
+  DROP TABLE gtt_with_seq;
+}
+
+# Session 1
+session "s1"
+step "s1_seq_next" { select nextval('gtt_with_seq_c2_seq'); }
+step "s1_seq_restart" { alter sequence gtt_with_seq_c2_seq RESTART; }
+step "s1_insert" { insert into gtt_with_seq values(1); }
+step "s1_select" { select * from gtt_with_seq order by c1,c2; }
+teardown
+{
+  TRUNCATE gtt_with_seq;
+}
+
+# Session 2
+session "s2"
+step "s2_seq_next" { select nextval('gtt_with_seq_c2_seq'); }
+step "s2_insert" { insert into gtt_with_seq values(10); }
+step "s2_select" { select * from gtt_with_seq order by c1,c2; }
+teardown
+{
+  TRUNCATE gtt_with_seq RESTART IDENTITY;
+}
+
+permutation "s1_seq_next" "s2_seq_next" "s1_seq_next"
+permutation "s1_select" "s2_select" "s1_insert" "s2_insert" "s1_select" "s2_select"
+
diff --git a/src/test/isolation/specs/gtt-table.spec b/src/test/isolation/specs/gtt-table.spec
new file mode 100644
index 00000000000..e0396b21ef0
--- /dev/null
+++ b/src/test/isolation/specs/gtt-table.spec
@@ -0,0 +1,135 @@
+# Tests for global temporary relations
+
+initialize
+{
+  create global temp table gtt_on_commit_delete_row(a bigserial primary key, b text) on commit delete rows;
+  create global temp table gtt_on_commit_preserve_row(a bigserial primary key, b text) on commit preserve rows;
+  create global temp table gtt_test_createindex(a int, b char(1000)) on commit preserve rows;
+}
+
+destroy
+{
+  /* wait other backend exit */
+  select pg_sleep(1);
+
+  DROP TABLE gtt_on_commit_delete_row;
+  DROP TABLE gtt_on_commit_preserve_row;
+  DROP TABLE gtt_test_createindex;
+}
+
+# Session 1
+session "s1"
+step "s1_begin" {begin}
+step "s1_commit" {commit}
+step "s1_rollback" {rollback}
+step "s1_insert_d" {insert into gtt_on_commit_delete_row (b) values('test1')}
+step "s1_select_d" {select a,b from gtt_on_commit_delete_row order by a,b}
+step "s1_insert_p" {insert into gtt_on_commit_preserve_row (b) values('test20')}
+step "s1_select_p" {select a,b from gtt_on_commit_preserve_row order by a,b}
+step "s1_truncate_d" {truncate gtt_on_commit_delete_row}
+step "s1_truncate_p" {truncate gtt_on_commit_preserve_row}
+step "s1_lock_p" {LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE}
+step "s1_update_d" {update gtt_on_commit_delete_row set b = 'update'}
+step "s1_save_1" {SAVEPOINT save1}
+step "s1_save_2" {SAVEPOINT save2}
+step "s1_save_3" {SAVEPOINT save3}
+step "s1_rollback_to_save_2" {rollback to savepoint save2}
+step "s1_reindex_p" {reindex table gtt_on_commit_preserve_row}
+step "s1_reindex_i_p" {reindex index gtt_on_commit_preserve_row_pkey}
+step "s1_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s1_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s1_analyze_c" {analyze gtt_test_createindex}
+teardown
+{
+  TRUNCATE gtt_on_commit_delete_row RESTART IDENTITY;
+  TRUNCATE gtt_on_commit_preserve_row RESTART IDENTITY;
+}
+
+# Session 2
+session "s2"
+step "s2_begin" {begin}
+step "s2_commit" {commit}
+step "s2_rollback" {rollback}
+step "s2_insert_p" {insert into gtt_on_commit_preserve_row (b) values('test10')}
+step "s2_select_p" {select a,b from gtt_on_commit_preserve_row order by a,b}
+step "s2_insert_d" {insert into gtt_on_commit_delete_row (b) values('test1')}
+step "s2_truncate_p" {truncate gtt_on_commit_preserve_row}
+step "s2_update_p" {update gtt_on_commit_preserve_row set b = 'update'}
+step "s2_lock_p" {LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE}
+step "s2_save_1" {SAVEPOINT save1}
+step "s2_save_2" {SAVEPOINT save2}
+step "s2_save_3" {SAVEPOINT save3}
+step "s2_rollback_to_save_2" {rollback to savepoint save2}
+step "s2_reindex_p" {reindex table gtt_on_commit_preserve_row}
+step "s2_reindex_i_p" {reindex index gtt_on_commit_preserve_row_pkey}
+step "s2_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s2_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s2_analyze_c" {analyze gtt_test_createindex}
+teardown
+{
+  TRUNCATE gtt_on_commit_delete_row RESTART IDENTITY;
+  TRUNCATE gtt_on_commit_preserve_row RESTART IDENTITY;
+}
+
+session "s3"
+step "s3_create_c" {create unique index idx_temp_table_a on gtt_test_createindex(a)}
+step "s3_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s3_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s3_analyze_c" {analyze gtt_test_createindex}
+
+
+#
+# test on commit delete temp table
+#
+
+# test update empty temp table
+permutation "s1_update_d"
+# test insert into temp table
+permutation "s1_select_d" "s1_insert_d" "s1_select_d"
+# test temp table in transaction(commit)
+permutation "s1_select_d" "s1_begin"    "s1_insert_d" "s1_select_d"   "s1_commit"   "s1_select_d" 
+# test temp table in transaction(rollback)
+permutation "s1_select_d" "s1_begin"    "s1_insert_d" "s1_select_d"   "s1_rollback" "s1_select_d" 
+# test truncate
+permutation "s1_select_d" "s1_insert_d" "s1_select_d" "s1_truncate_d" "s1_select_d"
+# test truncate in transaction block
+permutation "s1_select_d" "s1_insert_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_truncate_d" "s1_select_d"   "s1_insert_d" "s1_select_d" "s1_commit"   "s1_select_d" 
+permutation "s1_select_d" "s1_insert_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_truncate_d" "s1_select_d"   "s1_insert_d" "s1_select_d" "s1_rollback" "s1_select_d"
+# test temp table with subtransaction or savepoint
+permutation "s1_insert_d" "s1_select_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_save_1"     "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_2"   "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_3" "s1_rollback_to_save_2" "s1_select_d" "s1_insert_d" "s1_select_d" "s1_commit" "s1_select_d"
+permutation "s1_insert_d" "s1_select_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_save_1"     "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_2"   "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_3" "s1_rollback_to_save_2" "s1_select_d" "s1_insert_d" "s1_select_d" "s1_rollback" "s1_select_d"
+
+#
+# test on commit preserve table
+#
+
+# same as test on commit delete temp table
+permutation "s2_update_p"
+permutation "s2_select_p" "s2_insert_p" "s2_select_p"
+permutation "s2_select_p" "s2_begin"    "s2_insert_p" "s2_select_p"   "s2_commit"   "s2_select_p"
+permutation "s2_select_p" "s2_begin"    "s2_insert_p" "s2_select_p"   "s2_rollback" "s2_select_p"
+permutation "s2_select_p" "s2_insert_p" "s2_select_p" "s2_truncate_p" "s2_select_p"
+permutation "s2_select_p" "s2_insert_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_truncate_p" "s2_select_p"   "s2_insert_p" "s2_select_p" "s2_commit"   "s2_select_p" 
+permutation "s2_select_p" "s2_insert_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_truncate_p" "s2_select_p"   "s2_insert_p" "s2_select_p" "s2_rollback" "s2_select_p" 
+permutation "s2_insert_p" "s2_select_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_save_1"     "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_2"   "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_3" "s2_rollback_to_save_2" "s2_select_p" "s2_insert_p" "s2_select_p" "s2_commit" "s2_select_p"
+permutation "s2_insert_p" "s2_select_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_save_1"     "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_2"   "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_3" "s2_rollback_to_save_2" "s2_select_p" "s2_insert_p" "s2_select_p" "s2_rollback" "s2_select_p"
+
+#
+# test concurrent operation on temp table
+#
+
+#  test concurrent read
+permutation "s1_insert_p" "s2_insert_p" "s1_select_p" "s2_select_p" 
+#  test concurrent truncate
+permutation "s1_begin" "s2_begin"    "s1_insert_p" "s2_insert_p"   "s1_truncate_p" "s2_truncate_p"  "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+permutation "s1_begin" "s1_insert_d" "s2_insert_d" "s1_truncate_d" "s2_insert_d"   "s1_commit" 
+#  test concurrent reindex table
+permutation "s1_begin" "s2_begin" "s1_insert_p" "s2_insert_p" "s1_reindex_p"   "s2_reindex_p"   "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+#  test concurrent reindex index
+permutation "s1_begin" "s2_begin" "s1_insert_p" "s2_insert_p" "s1_reindex_i_p" "s2_reindex_i_p" "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+
+# test create index
+permutation "s2_insert_c" "s3_create_c" "s3_insert_c" "s1_insert_c" "s1_analyze_c" "s2_analyze_c" "s3_analyze_c" "s1_select_c" "s2_select_c" "s3_select_c"
+
+# test lock gtt
+permutation "s1_begin" "s2_begin" "s1_lock_p" "s2_lock_p" "s1_truncate_p" "s2_truncate_p" "s1_insert_p" "s2_insert_p" "s1_commit" "s2_commit" "s1_select_p" "s2_select_p"
diff --git a/src/test/isolation/specscanner.l b/src/test/isolation/specscanner.l
index d9fa6a5b54a..697db975479 100644
--- a/src/test/isolation/specscanner.l
+++ b/src/test/isolation/specscanner.l
@@ -67,6 +67,8 @@ session			{ return SESSION; }
 setup			{ return SETUP; }
 step			{ return STEP; }
 teardown		{ return TEARDOWN; }
+initialize		 { return INITIALIZE; }
+destroy			 { return DESTROY; }
 
  /* Whitespace and comments */
 [\n]			{ yyline++; }
diff --git a/src/test/regress/expected/global_temporary_table.out b/src/test/regress/expected/global_temporary_table.out
new file mode 100644
index 00000000000..def015dcbd6
--- /dev/null
+++ b/src/test/regress/expected/global_temporary_table.out
@@ -0,0 +1,506 @@
+--
+-- GLobal emparary table test case 
+--
+CREATE SCHEMA IF NOT EXISTS global_temporary_table;
+set search_path=global_temporary_table,sys;
+--
+--  test create global temp table basic syntax
+--
+create global temp table gtt_on_commit_default(a int primary key, b text);
+create global temp table gtt_on_commit_delete(a int primary key, b text) on commit delete rows;
+create global temp table gtt_on_commit_delete2(n int) with (on_commit_delete_rows='true');
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit PRESERVE rows;
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt_test_createindex(c0 tsvector,c1 varchar(100), c2 int);
+--
+-- test DML of temp table
+--
+-- update empty temp table
+update gtt_on_commit_delete set b ='test';
+begin;
+insert into gtt_on_commit_delete values (1);
+update gtt_on_commit_delete set b ='test';
+-- should 1 row
+select * from gtt_on_commit_delete;
+ a |  b   
+---+------
+ 1 | test
+(1 row)
+
+commit;
+-- data delete after transaction commit
+-- should 0 row
+select * from gtt_on_commit_delete;
+ a | b 
+---+---
+(0 rows)
+
+-- update empty temp table
+update gtt_on_commit_preserve set b ='test';
+insert into gtt_on_commit_preserve values (2);
+begin;
+insert into gtt_on_commit_preserve values (3);
+update gtt_on_commit_preserve set b ='test';
+-- should 2 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 2 | test
+ 3 | test
+(2 rows)
+
+delete from gtt_on_commit_preserve where a=2;
+commit;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 3 | test
+(1 row)
+
+begin;
+insert into gtt_on_commit_preserve values (4);
+-- temp table support truncate;
+truncate gtt_on_commit_preserve;
+select * from gtt_on_commit_preserve order by a;
+ a | b 
+---+---
+(0 rows)
+
+rollback;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 3 | test
+(1 row)
+
+--
+-- test unsupported global temp partition table
+--
+-- should fail
+CREATE global temp TABLE global_temp_partition_01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+on commit delete rows;
+ERROR:  Only support global temporary regular table.
+CREATE TABLE regular_partition_01_2019 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+);
+CREATE TABLE regular_partition01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time);
+-- should fail
+CREATE global temp TABLE temp_partition01_2018
+PARTITION OF regular_partition01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+ERROR:  Not support global temporary partition table or inherit table.
+CREATE global temp TABLE global_temp_partition_01_2021 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+)on commit delete rows;
+-- should fail
+ALTER TABLE regular_partition01 ATTACH PARTITION global_temp_partition_01_2021 FOR VALUES FROM ('2021-01-01 00:00:00') TO ('2022-01-01 00:00:00');
+ERROR:  cannot attach a global temporary relation as partition of permanent relation "regular_partition01"
+--
+-- test unsupported inherit table
+--
+create table 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 inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent);
+ERROR:  Not support global temporary partition table or inherit table.
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent_global_temp) on commit delete rows;
+ERROR:  Not support global temporary partition table or inherit table.
+--
+-- test DDL on global temp table
+--
+create index gtt_idx_1 on gtt_test_createindex using gin (c0);
+create index gtt_idx_2 on gtt_test_createindex using gist (c0);
+create index gtt_idx_3 on gtt_test_createindex using hash (c2);
+alter table gtt_test_rename rename to gtt_test_new;
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+create index CONCURRENTLY idx_b on gtt_on_commit_default (b);
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+insert into gtt_on_commit_default values(1,'test');
+create global temp table gtt_test_alter1 (a int primary key,b text);
+alter table gtt_test_alter1 alter a type varchar;
+-- should fail
+alter table gtt_on_commit_default alter a type varchar;
+ERROR:  Only support alter global temporary table in an empty context.
+HINT:  Please create a new connection and execute ALTER TABLE on the new connection.
+-- should fail
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+ERROR:  not support cluster global temporary table yet
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- should fail
+alter table gtt_on_commit_default SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temporary table
+-- should fail
+alter table gtt_on_commit_default set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table
+-- should fail
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- should fail
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- should fail
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit drop;
+ERROR:  global temporary table not support on commit drop clause
+-- should fail
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  could not create global temporary table with on commit and with clause at same time
+-- should fail
+CREATE MATERIALIZED VIEW mv_gtt_on_commit_default as select * from gtt_on_commit_default;
+ERROR:  materialized views must not use global temporary tables or views
+--
+-- test foreign key dependencies for global temp table
+--
+CREATE global temp TABLE temp_products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+CREATE global temp TABLE temp_orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+)on commit delete rows;
+-- should fail
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+CREATE global temp TABLE temp_orders_2 (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products(product_no),
+    quantity integer
+);
+--should fail
+insert into temp_orders values(1,1,1);
+ERROR:  insert or update on table "temp_orders" violates foreign key constraint "temp_orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "temp_products".
+insert into temp_products values(1,'test',1.0);
+begin;
+insert into temp_orders values(1,1,1);
+commit;
+-- should 1 row
+select count(*) from temp_products;
+ count 
+-------
+     1
+(1 row)
+
+-- should 0 row
+select count(*) from temp_orders;
+ count 
+-------
+     0
+(1 row)
+
+--
+-- test sequence on global temp table
+--
+create global temp table global_temp_with_serial (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+-- support insert data to temp table in read only transaction
+insert into global_temp_with_serial (b) values(1);
+select * from global_temp_with_serial;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_delete(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_preserve(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table temp_table_with_sequence_oncommit_delete add c2 int default nextval('seq_1');
+alter table temp_table_with_sequence_oncommit_preserve add c2 int default nextval('seq_1');
+begin;
+insert into temp_table_with_sequence_oncommit_delete (c1)values(1);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(2);
+insert into temp_table_with_sequence_oncommit_delete (c1)values(3);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(4);
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  3 |  3
+(2 rows)
+
+commit;
+-- should 0 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_preserve order by c1;
+ c1 | c2 
+----+----
+  2 |  2
+  4 |  4
+(2 rows)
+
+--
+-- test statistics on temp table
+--
+create global temp table temp_table_test_statistics(a int);
+insert into temp_table_test_statistics values(generate_series(1,100000));
+create index idx_test_1 on temp_table_test_statistics (a);
+create index idx_test_2 on temp_table_test_statistics((a*10));
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+                   QUERY PLAN                   
+------------------------------------------------
+ Bitmap Heap Scan on temp_table_test_statistics
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_test_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+                   QUERY PLAN                   
+------------------------------------------------
+ Bitmap Heap Scan on temp_table_test_statistics
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_test_2
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze temp_table_test_statistics;
+-- indexscan by idx_test_1
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Index Only Scan using idx_test_1 on temp_table_test_statistics
+   Index Cond: (a = 200000)
+(2 rows)
+
+-- indexscan by idx_test_2
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+                        QUERY PLAN                         
+-----------------------------------------------------------
+ Index Scan using idx_test_2 on temp_table_test_statistics
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+--
+-- test temp table with toast table
+--
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |                0 |        483328 |           98304 |                 581632
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            32768 |                  |         32768 |               0 |                  32768
+(3 rows)
+
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |             8192 |        499712 |          106496 |                 606208
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            40960 |                  |         40960 |               0 |                  40960
+(3 rows)
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,10),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',10);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |             8192 |                0 |         16384 |           32768 |                  49152
+ idx_gtt_t_kenyon_1 |            16384 |                  |         16384 |               0 |                  16384
+ idx_gtt_t_kenyon_2 |            16384 |                  |         16384 |               0 |                  16384
+(3 rows)
+
+rollback;
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |             8192 |        499712 |          106496 |                 606208
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            40960 |                  |         40960 |               0 |                  40960
+(3 rows)
+
+--
+-- test global temp table system view
+--
+create global temp table temp_table_test_systemview(a int primary key, b text) on commit PRESERVE rows;
+-- should empty, storage not initialized
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+ tablename 
+-----------
+(0 rows)
+
+-- should empty, storage not initialized
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into temp_table_test_systemview values(generate_series(1,10000),'test');
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+         tablename          
+----------------------------
+ temp_table_test_systemview
+(1 row)
+
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+       schemaname       |            tablename            | relpages | reltuples | relallvisible 
+------------------------+---------------------------------+----------+-----------+---------------
+ global_temporary_table | temp_table_test_systemview      |        0 |         0 |             0
+ global_temporary_table | temp_table_test_systemview_pkey |        1 |         0 |             0
+(2 rows)
+
+-- should empty
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+analyze temp_table_test_systemview;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+       schemaname       |            tablename            | relpages | reltuples | relallvisible 
+------------------------+---------------------------------+----------+-----------+---------------
+ global_temporary_table | temp_table_test_systemview      |       55 |     10000 |             0
+ global_temporary_table | temp_table_test_systemview_pkey |       30 |     10000 |             0
+(2 rows)
+
+-- get data after analyze;
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+       schemaname       |         tablename          | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------------------+----------------------------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ global_temporary_table | temp_table_test_systemview | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ global_temporary_table | temp_table_test_systemview | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+-- get all object info in current schema
+select relname ,relkind, relpersistence, reloptions from pg_class c, pg_namespace n where c.relnamespace = n.oid and n.nspname = 'global_temporary_table' order by relkind,relpersistence,relname;
+                     relname                     | relkind | relpersistence |          reloptions           
+-------------------------------------------------+---------+----------------+-------------------------------
+ global_temp_partition_01_2021_id_seq            | S       | g              | {on_commit_delete_rows=false}
+ global_temp_with_serial_a_seq                   | S       | g              | {on_commit_delete_rows=false}
+ regular_partition01_id_seq                      | S       | p              | 
+ regular_partition_01_2019_id_seq                | S       | p              | 
+ seq_1                                           | S       | p              | 
+ gtt_idx_1                                       | i       | g              | 
+ gtt_idx_2                                       | i       | g              | 
+ gtt_idx_3                                       | i       | g              | 
+ gtt_on_commit_default_pkey                      | i       | g              | 
+ gtt_on_commit_delete_pkey                       | i       | g              | 
+ gtt_on_commit_preserve_pkey                     | i       | g              | 
+ gtt_test_alter1_pkey                            | i       | g              | 
+ gtt_test_rename_pkey                            | i       | g              | 
+ idx_b                                           | i       | g              | 
+ idx_gtt_t_kenyon_1                              | i       | g              | 
+ idx_gtt_t_kenyon_2                              | i       | g              | 
+ idx_test_1                                      | i       | g              | 
+ idx_test_2                                      | i       | g              | 
+ products_pkey                                   | i       | g              | 
+ temp_orders_2_pkey                              | i       | g              | 
+ temp_orders_pkey                                | i       | g              | 
+ temp_products_pkey                              | i       | g              | 
+ temp_table_test_systemview_pkey                 | i       | g              | 
+ temp_table_with_sequence_oncommit_delete_pkey   | i       | g              | 
+ temp_table_with_sequence_oncommit_preserve_pkey | i       | g              | 
+ regular_partition01                             | p       | p              | 
+ global_temp_partition_01_2021                   | r       | g              | {on_commit_delete_rows=true}
+ global_temp_with_serial                         | r       | g              | {on_commit_delete_rows=false}
+ gtt_on_commit_default                           | r       | g              | {on_commit_delete_rows=false}
+ gtt_on_commit_delete                            | r       | g              | {on_commit_delete_rows=true}
+ gtt_on_commit_delete2                           | r       | g              | {on_commit_delete_rows=true}
+ gtt_on_commit_preserve                          | r       | g              | {on_commit_delete_rows=false}
+ gtt_t_kenyon                                    | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_alter1                                 | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_createindex                            | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_new                                    | r       | g              | {on_commit_delete_rows=false}
+ inherits_parent_global_temp                     | r       | g              | {on_commit_delete_rows=true}
+ products                                        | r       | g              | {on_commit_delete_rows=false}
+ temp_orders                                     | r       | g              | {on_commit_delete_rows=true}
+ temp_orders_2                                   | r       | g              | {on_commit_delete_rows=false}
+ temp_products                                   | r       | g              | {on_commit_delete_rows=false}
+ temp_table_test_statistics                      | r       | g              | {on_commit_delete_rows=false}
+ temp_table_test_systemview                      | r       | g              | {on_commit_delete_rows=false}
+ temp_table_with_sequence_oncommit_delete        | r       | g              | {on_commit_delete_rows=true}
+ temp_table_with_sequence_oncommit_preserve      | r       | g              | {on_commit_delete_rows=false}
+ foo                                             | r       | p              | 
+ inherits_parent                                 | r       | p              | 
+ regular_partition_01_2019                       | r       | p              | 
+(48 rows)
+
+reset search_path;
+drop schema global_temporary_table cascade;
+NOTICE:  drop cascades to 24 other objects
+DETAIL:  drop cascades to table global_temporary_table.gtt_on_commit_default
+drop cascades to table global_temporary_table.gtt_on_commit_delete
+drop cascades to table global_temporary_table.gtt_on_commit_delete2
+drop cascades to table global_temporary_table.gtt_on_commit_preserve
+drop cascades to table global_temporary_table.gtt_test_new
+drop cascades to table global_temporary_table.gtt_test_createindex
+drop cascades to table global_temporary_table.regular_partition_01_2019
+drop cascades to table global_temporary_table.regular_partition01
+drop cascades to table global_temporary_table.global_temp_partition_01_2021
+drop cascades to table global_temporary_table.inherits_parent
+drop cascades to table global_temporary_table.inherits_parent_global_temp
+drop cascades to table global_temporary_table.gtt_test_alter1
+drop cascades to table global_temporary_table.foo
+drop cascades to table global_temporary_table.temp_products
+drop cascades to table global_temporary_table.products
+drop cascades to table global_temporary_table.temp_orders
+drop cascades to table global_temporary_table.temp_orders_2
+drop cascades to table global_temporary_table.global_temp_with_serial
+drop cascades to sequence global_temporary_table.seq_1
+drop cascades to table global_temporary_table.temp_table_with_sequence_oncommit_delete
+drop cascades to table global_temporary_table.temp_table_with_sequence_oncommit_preserve
+drop cascades to table global_temporary_table.temp_table_test_statistics
+drop cascades to table global_temporary_table.gtt_t_kenyon
+drop cascades to table global_temporary_table.temp_table_test_systemview
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2fa00a3c29a..e0001bc3448 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1359,6 +1359,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 7be89178f0f..db8095d30bf 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -130,3 +130,6 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: global_temporary_table
diff --git a/src/test/regress/sql/global_temporary_table.sql b/src/test/regress/sql/global_temporary_table.sql
new file mode 100644
index 00000000000..cb49dc1af57
--- /dev/null
+++ b/src/test/regress/sql/global_temporary_table.sql
@@ -0,0 +1,284 @@
+--
+-- GLobal emparary table test case 
+--
+
+CREATE SCHEMA IF NOT EXISTS global_temporary_table;
+set search_path=global_temporary_table,sys;
+
+--
+--  test create global temp table basic syntax
+--
+create global temp table gtt_on_commit_default(a int primary key, b text);
+create global temp table gtt_on_commit_delete(a int primary key, b text) on commit delete rows;
+create global temp table gtt_on_commit_delete2(n int) with (on_commit_delete_rows='true');
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit PRESERVE rows;
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt_test_createindex(c0 tsvector,c1 varchar(100), c2 int);
+
+--
+-- test DML of temp table
+--
+
+-- update empty temp table
+update gtt_on_commit_delete set b ='test';
+begin;
+insert into gtt_on_commit_delete values (1);
+update gtt_on_commit_delete set b ='test';
+-- should 1 row
+select * from gtt_on_commit_delete;
+commit;
+-- data delete after transaction commit
+-- should 0 row
+select * from gtt_on_commit_delete;
+
+-- update empty temp table
+update gtt_on_commit_preserve set b ='test';
+insert into gtt_on_commit_preserve values (2);
+begin;
+insert into gtt_on_commit_preserve values (3);
+update gtt_on_commit_preserve set b ='test';
+-- should 2 row
+select * from gtt_on_commit_preserve order by a;
+delete from gtt_on_commit_preserve where a=2;
+commit;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+
+begin;
+insert into gtt_on_commit_preserve values (4);
+-- temp table support truncate;
+truncate gtt_on_commit_preserve;
+select * from gtt_on_commit_preserve order by a;
+rollback;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+
+--
+-- test unsupported global temp partition table
+--
+
+-- should fail
+CREATE global temp TABLE global_temp_partition_01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+on commit delete rows;
+
+CREATE TABLE regular_partition_01_2019 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+);
+
+CREATE TABLE regular_partition01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time);
+
+-- should fail
+CREATE global temp TABLE temp_partition01_2018
+PARTITION OF regular_partition01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE global_temp_partition_01_2021 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+)on commit delete rows;
+
+-- should fail
+ALTER TABLE regular_partition01 ATTACH PARTITION global_temp_partition_01_2021 FOR VALUES FROM ('2021-01-01 00:00:00') TO ('2022-01-01 00:00:00');
+
+--
+-- test unsupported inherit table
+--
+create table 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 inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent);
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent_global_temp) on commit delete rows;
+
+--
+-- test DDL on global temp table
+--
+create index gtt_idx_1 on gtt_test_createindex using gin (c0);
+create index gtt_idx_2 on gtt_test_createindex using gist (c0);
+create index gtt_idx_3 on gtt_test_createindex using hash (c2);
+alter table gtt_test_rename rename to gtt_test_new;
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+create index CONCURRENTLY idx_b on gtt_on_commit_default (b);
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+insert into gtt_on_commit_default values(1,'test');
+create global temp table gtt_test_alter1 (a int primary key,b text);
+alter table gtt_test_alter1 alter a type varchar;
+-- should fail
+alter table gtt_on_commit_default alter a type varchar;
+-- should fail
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) on commit delete rows;
+-- should fail
+alter table gtt_on_commit_default SET TABLESPACE pg_default;
+-- should fail
+alter table gtt_on_commit_default set ( on_commit_delete_rows='true');
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) with(on_commit_delete_rows=true);
+-- should fail
+create or replace global temp view gtt_v as select 5;
+create table foo();
+-- should fail
+alter table foo set (on_commit_delete_rows='true');
+-- should fail
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit drop;
+-- should fail
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+-- should fail
+CREATE MATERIALIZED VIEW mv_gtt_on_commit_default as select * from gtt_on_commit_default;
+
+--
+-- test foreign key dependencies for global temp table
+--
+CREATE global temp TABLE temp_products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+CREATE global temp TABLE temp_orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+)on commit delete rows;
+
+-- should fail
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+);
+
+CREATE global temp TABLE temp_orders_2 (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products(product_no),
+    quantity integer
+);
+
+--should fail
+insert into temp_orders values(1,1,1);
+
+insert into temp_products values(1,'test',1.0);
+begin;
+insert into temp_orders values(1,1,1);
+commit;
+-- should 1 row
+select count(*) from temp_products;
+-- should 0 row
+select count(*) from temp_orders;
+
+--
+-- test sequence on global temp table
+--
+create global temp table global_temp_with_serial (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+-- support insert data to temp table in read only transaction
+insert into global_temp_with_serial (b) values(1);
+select * from global_temp_with_serial;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_delete(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_preserve(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table temp_table_with_sequence_oncommit_delete add c2 int default nextval('seq_1');
+alter table temp_table_with_sequence_oncommit_preserve add c2 int default nextval('seq_1');
+begin;
+insert into temp_table_with_sequence_oncommit_delete (c1)values(1);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(2);
+insert into temp_table_with_sequence_oncommit_delete (c1)values(3);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(4);
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+commit;
+-- should 0 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_preserve order by c1;
+
+--
+-- test statistics on temp table
+--
+create global temp table temp_table_test_statistics(a int);
+insert into temp_table_test_statistics values(generate_series(1,100000));
+create index idx_test_1 on temp_table_test_statistics (a);
+create index idx_test_2 on temp_table_test_statistics((a*10));
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+analyze temp_table_test_statistics;
+-- indexscan by idx_test_1
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+-- indexscan by idx_test_2
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+
+--
+-- test temp table with toast table
+--
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,10),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',10);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+rollback;
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+
+--
+-- test global temp table system view
+--
+create global temp table temp_table_test_systemview(a int primary key, b text) on commit PRESERVE rows;
+-- should empty, storage not initialized
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+-- should empty, storage not initialized
+select count(*) from pg_list_gtt_relfrozenxids();
+insert into temp_table_test_systemview values(generate_series(1,10000),'test');
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+select count(*) from pg_list_gtt_relfrozenxids();
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+-- should empty
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+analyze temp_table_test_systemview;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+-- get data after analyze;
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+
+-- get all object info in current schema
+select relname ,relkind, relpersistence, reloptions from pg_class c, pg_namespace n where c.relnamespace = n.oid and n.nspname = 'global_temporary_table' order by relkind,relpersistence,relname;
+
+reset search_path;
+drop schema global_temporary_table cascade;
+
-- 
2.30.1 (Apple Git-130)

0002-gtt-v56-doc.patchapplication/octet-stream; name=0002-gtt-v56-doc.patchDownload
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 473a0a4aeb..e510bde8ac 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -169,32 +169,67 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     <listitem>
      <para>
       If specified, the table is created as a temporary table.
-      Temporary tables are automatically dropped at the end of a
-      session, or optionally at the end of the current transaction
-      (see <literal>ON COMMIT</literal> below).  The default
-      search_path includes the temporary schema first and so identically
-      named existing permanent tables are not chosen for new plans
+      Optionally, <literal>GLOBAL</literal> or <literal>LOCAL</literal>
+      can be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
+      They represent two types of temporary tables supported by <productname>PostgreSQL</productname>:
+      global temporary table and local temporary table. Without specified
+      GLOBAL or LOCAL, a local temporary table is created by default.
+     </para>
+
+    <para>
+     Both types of temporary tables’ data are truncated at the
+     end of a session or optionally at the end of the current transaction.
+     (see <literal>ON COMMIT</literal> below). For global temporary table,
+     its schema is reserved and reused by future sessions or transactions.
+     For local temporary table, both its data and its schema are dropped.
+    </para>
+
+    <variablelist>
+     <varlistentry>
+      <term><literal>Global Temporary Table</literal></term>
+      <listitem>
+       <para>
+        Global temporary table are defined just once and automatically exist
+        (starting with empty contents) in every session that needs them.
+        The schema definition of temporary tables is persistent and shared among sessions.
+        However, the data in temporary tables are kept private to sessions themselves,
+        even though they use same name and same schema.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>Local Temporary Table</literal></term>
+     <listitem>
+     <para>
+      Local temporary table are automatically dropped at the end of a
+      session (include schema and data). Future sessions need to create
+      their own temporary tables when they are used.
+     </para>
+     <para>
+      The default search_path includes the temporary schema first and so
+      identically named existing permanent tables are not chosen for new plans
       while the temporary table exists, unless they are referenced
       with schema-qualified names. Any indexes created on a temporary
       table are automatically temporary as well.
      </para>
+     </listitem>
+     </varlistentry>
+    </variablelist>
 
-     <para>
-      The <link linkend="autovacuum">autovacuum daemon</link> cannot
-      access and therefore cannot vacuum or analyze temporary tables.
-      For this reason, appropriate vacuum and analyze operations should be
-      performed via session SQL commands.  For example, if a temporary
-      table is going to be used in complex queries, it is wise to run
-      <command>ANALYZE</command> on the temporary table after it is populated.
-     </para>
+    <para>
+     The <link linkend="autovacuum">autovacuum daemon</link> cannot
+     access and therefore cannot vacuum or analyze temporary tables.
+     For this reason, appropriate vacuum and analyze operations should be
+     performed via session SQL commands.  For example, if a temporary
+     table is going to be used in complex queries, it is wise to run
+     <command>ANALYZE</command> on the temporary table after it is populated.
+    </para>
+    <para>
+     The Temporary table resembles the SQL standard, but has some differences.
+     see <xref linkend="sql-createtable-compatibility"/> below.
+    </para>
 
-     <para>
-      Optionally, <literal>GLOBAL</literal> or <literal>LOCAL</literal>
-      can be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
-      This presently makes no difference in <productname>PostgreSQL</productname>
-      and is deprecated; see
-      <xref linkend="sql-createtable-compatibility"/> below.
-     </para>
     </listitem>
    </varlistentry>
 
@@ -2133,13 +2168,17 @@ CREATE TABLE cities_partdef
    <title>Temporary Tables</title>
 
    <para>
-    Although the syntax of <literal>CREATE TEMPORARY TABLE</literal>
-    resembles that of the SQL standard, the effect is not the same.  In the
-    standard,
-    temporary tables are defined just once and automatically exist (starting
-    with empty contents) in every session that needs them.
-    <productname>PostgreSQL</productname> instead
-    requires each session to issue its own <literal>CREATE TEMPORARY
+    Although the syntax of <literal>CREATE GLOBAL/LOCAL TEMPORARY TABLE</literal>
+    resembles that of the SQL standard, the effect is not the same.
+    The global temporary table follows the SQL standards while local temporary
+    table does not.
+   </para>
+
+   <para>
+    First, in the standard, both global and local temporary tables are defined just
+    once and automatically exist (starting with empty contents) in every session
+    that needs them. For local temporary tables, <productname>PostgreSQL</productname>
+    instead requires each session to issue its own <literal>CREATE LOCAL TEMPORARY
     TABLE</literal> command for each temporary table to be used.  This allows
     different sessions to use the same temporary table name for different
     purposes, whereas the standard's approach constrains all instances of a
@@ -2147,29 +2186,14 @@ CREATE TABLE cities_partdef
    </para>
 
    <para>
-    The standard's definition of the behavior of temporary tables is
-    widely ignored.  <productname>PostgreSQL</productname>'s behavior
-    on this point is similar to that of several other SQL databases.
-   </para>
-
-   <para>
-    The SQL standard also distinguishes between global and local temporary
+    Second, the SQL standard distinguishes between global and local temporary
     tables, where a local temporary table has a separate set of contents for
     each SQL module within each session, though its definition is still shared
-    across sessions.  Since <productname>PostgreSQL</productname> does not
+    across sessions. Since <productname>PostgreSQL</productname> does not
     support SQL modules, this distinction is not relevant in
     <productname>PostgreSQL</productname>.
    </para>
 
-   <para>
-    For compatibility's sake, <productname>PostgreSQL</productname> will
-    accept the <literal>GLOBAL</literal> and <literal>LOCAL</literal> keywords
-    in a temporary table declaration, but they currently have no effect.
-    Use of these keywords is discouraged, since future versions of
-    <productname>PostgreSQL</productname> might adopt a more
-    standard-compliant interpretation of their meaning.
-   </para>
-
    <para>
     The <literal>ON COMMIT</literal> clause for temporary tables
     also resembles the SQL standard, but has some differences.
@@ -2177,7 +2201,8 @@ CREATE TABLE cities_partdef
     default behavior is <literal>ON COMMIT DELETE ROWS</literal>.  However, the
     default behavior in <productname>PostgreSQL</productname> is
     <literal>ON COMMIT PRESERVE ROWS</literal>.  The <literal>ON COMMIT
-    DROP</literal> option does not exist in SQL.
+    DROP</literal> option does not exist in SQL and is not supported by
+    global temporary table.
    </para>
   </refsect2>
 
-- 
2.30.1 (Apple Git-130)

0003-gtt-v56-implementation.patchapplication/octet-stream; name=0003-gtt-v56-implementation.patchDownload
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index b5602f53233..21b2d2a9527 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -159,6 +159,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * In order to avoid consistency problems, the global temporary table
+	 * uses ShareUpdateExclusiveLock.
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temporary table on commit options",
+			RELOPT_KIND_HEAP,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},
 	/* list terminator */
 	{{NULL}}
 };
@@ -1834,6 +1847,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 43ba03b6eb9..49f1052fdb1 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1023,7 +1023,7 @@ gistproperty(Oid index_oid, int attno,
 XLogRecPtr
 gistGetFakeLSN(Relation rel)
 {
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	if (RELATION_IS_TEMP(rel))
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index eb3810494f2..cbd22909582 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -151,7 +151,7 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	if (!RELATION_IS_TEMP(index))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 9befe012a9e..26fce0c4b83 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -593,7 +593,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
@@ -645,7 +645,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(RelationGetSmgr(rel), dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 05221cc1d6d..c4a0dc1ac0e 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -63,6 +63,7 @@
 #include "access/xlog.h"
 #include "catalog/index.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -508,6 +509,14 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 	TransactionId FreezeLimit;
 	MultiXactId MultiXactCutoff;
 
+	/*
+	 * not every AM requires these to be valid, but regular heap does.
+	 * Transaction information for the global temp table will be stored
+	 * in the local hash table, not the catalog.
+	 */
+	Assert(RELATION_IS_GLOBAL_TEMP(rel) ^ TransactionIdIsNormal(rel->rd_rel->relfrozenxid));
+	Assert(RELATION_IS_GLOBAL_TEMP(rel) ^ MultiXactIdIsValid(rel->rd_rel->relminmxid));
+
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
 	{
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 5bc7c3616a9..0b261f723d7 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -677,6 +678,14 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		/*
+		 * If a global temporary table storage file is not initialized in the
+		 * current backend, its index does not have a root page, just returns 0.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e773612..8c21979625f 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -44,6 +44,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index aa7d4d5456b..595cb03eb4a 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -504,6 +504,7 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 5898203972b..c785d61db20 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -62,6 +62,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,
@@ -366,13 +368,24 @@ heap_create(const char *relname,
 			break;
 	}
 
+	/* For global temporary table, even if the storage is not initialized,
+	 * the relfilenode needs to be generated and put into the catalog.
+	 */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		create_storage = false;
+		if (!OidIsValid(relfilenode))
+			relfilenode = relid;
+	}
 	/*
 	 * Decide whether to create storage. If caller passed a valid relfilenode,
 	 * storage is already created, so don't do it here.  Also don't create it
 	 * for relkinds without physical storage.
 	 */
-	if (!RELKIND_HAS_STORAGE(relkind) || OidIsValid(relfilenode))
+	else if (!RELKIND_HAS_STORAGE(relkind) || OidIsValid(relfilenode))
+	{
 		create_storage = false;
+	}
 	else
 	{
 		create_storage = true;
@@ -427,7 +440,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:
@@ -445,7 +458,8 @@ heap_create(const char *relname,
 	 * protected by the existence of a physical file; but for relations with
 	 * no files, add a pg_shdepend entry to account for that.
 	 */
-	if (!create_storage && reltablespace != InvalidOid)
+	if (!create_storage && reltablespace != InvalidOid &&
+		relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
 		recordDependencyOnTablespace(RelationRelationId, relid,
 									 reltablespace);
 
@@ -998,6 +1012,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -1036,8 +1051,21 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/*
+	 * The transaction information of the global temporary table is stored
+	 * in the local hash table, not in catalog.
+	 */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1410,6 +1438,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1495,8 +1524,9 @@ heap_create_with_catalog(const char *relname,
 	/*
 	 * If there's a special on-commit action, remember it
 	 */
-	if (oncommit != ONCOMMIT_NOOP)
-		register_on_commit_action(relid, oncommit);
+	if (oncommit != ONCOMMIT_NOOP &&
+		relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+		register_on_commit_action(relid, oncommit, false);
 
 	/*
 	 * ok, the relation has been cataloged, so close our relations and return
@@ -1993,6 +2023,19 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/*
+	 * Only when other sessions are not using this Global temporary table,
+	 * is it allowed to DROP it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot drop global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3277,7 +3320,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3289,7 +3332,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3325,7 +3368,7 @@ RelationTruncateIndexes(Relation heapRelation)
  * ON COMMIT truncation of temporary tables, where it doesn't matter.
  */
 void
-heap_truncate(List *relids)
+heap_truncate(List *relids, bool is_global_temp)
 {
 	List	   *relations = NIL;
 	ListCell   *cell;
@@ -3335,8 +3378,23 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode;
+
+		/*
+		 * Truncate global temporary table only clears local data,
+		 * so only low-level locks need to be held.
+		 */
+		if (is_global_temp)
+		{
+			if (!gtt_storage_attached(rid))
+				continue;
+
+			lockmode = RowExclusiveLock;
+		}
+		else
+			lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3369,6 +3427,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3377,23 +3436,39 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	/*
+	 * Truncate GTT only clears local data, so only low-level locks
+	 * need to be held.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		lockmode = AccessShareLock;
+	else
+		lockmode = AccessExclusiveLock;
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	/*
+	 * After the data is cleaned up on the GTT, the transaction information
+	 * for the data(stored in local hash table) is also need reset.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(RelationGetRelid(rel), 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 26bfa74ce75..c32b45f9673 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -54,6 +54,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"
@@ -733,6 +734,25 @@ index_create(Relation heapRelation,
 	TransactionId relfrozenxid;
 	MultiXactId relminmxid;
 
+	/* For global temporary table only */
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temporary table with concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot reindex global temporary tables concurrently")));
+
+		/*
+		 * For the case that some backend is applied relcache message to create
+		 * an index on a global temporary table, if this table in the current
+		 * backend are not initialized, the creation of index storage on the
+		 * table are also skipped.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/* constraint flags can only be set when a constraint is requested */
 	Assert((constr_flags == 0) ||
 		   ((flags & INDEX_CREATE_ADD_CONSTRAINT) != 0));
@@ -2107,7 +2127,7 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 * lock (see comments in RemoveRelations), and a non-concurrent DROP is
 	 * more efficient.
 	 */
-	Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+	Assert(!RelpersistenceTsTemp(get_rel_persistence(indexId)) ||
 		   (!concurrent && !concurrent_lock_mode));
 
 	/*
@@ -2139,6 +2159,21 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/*
+	 * Allow to drop index on global temporary table when only current
+	 * backend use it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation) &&
+		is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+	{
+		ereport(ERROR,
+			(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+			 errmsg("cannot drop index %s on global temporary table %s",
+					RelationGetRelationName(userIndexRelation), RelationGetRelationName(userHeapRelation)),
+					errdetail("Because the index is created on the global temporary table and other backend attached it."),
+					errhint("Please try detach all sessions using this temporary table and try again.")));
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2747,6 +2782,7 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = RELATION_IS_GLOBAL_TEMP(rel);
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2841,20 +2877,37 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
-		{
-			rd_rel->relpages = (int32) relpages;
-			dirty = true;
-		}
-		if (rd_rel->reltuples != (float4) reltuples)
+		/* For global temporary table */
+		if (is_gtt)
 		{
-			rd_rel->reltuples = (float4) reltuples;
-			dirty = true;
+			/* Update GTT'statistics into local relcache */
+			rel->rd_rel->relpages = (int32) relpages;
+			rel->rd_rel->reltuples = (float4) reltuples;
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+
+			/* Update GTT'statistics into local hashtable */
+			up_gtt_relstats(RelationGetRelid(rel), relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+		else
 		{
-			rd_rel->relallvisible = (int32) relallvisible;
-			dirty = true;
+			if (rd_rel->relpages != (int32) relpages)
+			{
+				rd_rel->relpages = (int32) relpages;
+				dirty = true;
+			}
+
+			if (rd_rel->reltuples != (float4) reltuples)
+			{
+				rd_rel->reltuples = (float4) reltuples;
+				dirty = true;
+			}
+
+			if (rd_rel->relallvisible != (int32) relallvisible)
+			{
+				rd_rel->relallvisible = (int32) relallvisible;
+				dirty = true;
+			}
 		}
 	}
 
@@ -2967,6 +3020,26 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, progress_index, progress_vals);
 	}
 
+	/* For build index on global temporary table */
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		/*
+		 * If the storage for the index in this session is not initialized,
+		 * it needs to be created.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			/* Before create init storage, fix the local Relcache first */
+			force_enable_gtt_index(indexRelation);
+
+			Assert(gtt_storage_attached(RelationGetRelid(heapRelation)));
+
+			/* Init storage for index */
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3508,6 +3581,8 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	PGRUsage	ru0;
 	bool		progress = ((params->options & REINDEXOPT_REPORT_PROGRESS) != 0);
 	bool		set_tablespace = false;
+	LOCKMODE	lockmode_on_heap = ShareLock;
+	LOCKMODE	lockmode_on_index = AccessExclusiveLock;
 
 	pg_rusage_init(&ru0);
 
@@ -3521,10 +3596,29 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	if (!OidIsValid(heapId))
 		return;
 
+	/*
+	 * For reindex on global temporary table, If the storage for the index
+	 * in current backend is not initialized, nothing is done.
+	 */
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		if (!gtt_storage_attached(indexId))
+		{
+			/* Suppress use of the target index while rebuilding it */
+			SetReindexProcessing(heapId, indexId);
+			/* Re-allow use of target index */
+			ResetReindexProcessing();
+			return;
+		}
+
+		lockmode_on_heap = AccessShareLock;
+		lockmode_on_index = AccessShareLock;
+	}
+
 	if ((params->options & REINDEXOPT_MISSING_OK) != 0)
-		heapRelation = try_table_open(heapId, ShareLock);
+		heapRelation = try_table_open(heapId, lockmode_on_heap);
 	else
-		heapRelation = table_open(heapId, ShareLock);
+		heapRelation = table_open(heapId, lockmode_on_heap);
 
 	/* if relation is gone, leave */
 	if (!heapRelation)
@@ -3550,7 +3644,7 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	 * Open the target index relation and get an exclusive lock on it, to
 	 * ensure that no one else is touching this particular index.
 	 */
-	iRel = index_open(indexId, AccessExclusiveLock);
+	iRel = index_open(indexId, lockmode_on_index);
 
 	if (progress)
 		pgstat_progress_update_param(PROGRESS_CREATEIDX_ACCESS_METHOD_OID,
@@ -3801,6 +3895,12 @@ reindex_relation(Oid relid, int flags, ReindexParams *params)
 	bool		result;
 	ListCell   *indexId;
 	int			i;
+	LOCKMODE	lockmode;
+
+	if (flags & REINDEX_REL_PROCESS_GLOBAL_TEMP)
+		lockmode = AccessShareLock;
+	else
+		lockmode = ShareLock;
 
 	/*
 	 * Open and lock the relation.  ShareLock is sufficient since we only need
@@ -3808,9 +3908,9 @@ reindex_relation(Oid relid, int flags, ReindexParams *params)
 	 * should match ReindexTable().
 	 */
 	if ((params->options & REINDEXOPT_MISSING_OK) != 0)
-		rel = try_table_open(relid, ShareLock);
+		rel = try_table_open(relid, lockmode);
 	else
-		rel = table_open(relid, ShareLock);
+		rel = table_open(relid, lockmode);
 
 	/* if relation is gone, leave */
 	if (!rel)
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 4de8400fd0f..fe3fcc712cb 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -656,6 +656,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/* Do not allow create global temporary table in temporary schemas */
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp table in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index c5ad28d71fe..707068a6fd8 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,6 +27,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -61,6 +62,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			temprelOid;			/* InvalidOid if not a global temporary rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -115,7 +117,7 @@ AddPendingSync(const RelFileNode *rnode)
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -126,7 +128,12 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/*
+		 * Global temporary table and local temporary table use same
+		 * design on storage module.
+		 */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -154,6 +161,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
@@ -165,6 +173,21 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		AddPendingSync(&rnode);
 	}
 
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		Assert(rel && RELATION_IS_GLOBAL_TEMP(rel));
+
+		/*
+		 * Remember the reloid of global temporary table, which is used for
+		 * transaction commit or rollback.
+		 * see smgrDoPendingDeletes.
+		 */
+		pending->temprelOid = RelationGetRelid(rel);
+
+		/* Remember global temporary table storage info to localhash */
+		remember_gtt_storage_info(rnode, rel);
+	}
+
 	return srel;
 }
 
@@ -201,11 +224,20 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/*
+	 * Remember the reloid of global temporary table, which is used for
+	 * transaction commit or rollback.
+	 * see smgrDoPendingDeletes.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->temprelOid = RelationGetRelid(rel);
+
 	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
@@ -618,6 +650,7 @@ smgrDoPendingDeletes(bool isCommit)
 	int			nrels = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -647,14 +680,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->temprelOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -664,12 +701,21 @@ smgrDoPendingDeletes(bool isCommit)
 
 	if (nrels > 0)
 	{
+		int	i;
+
 		smgrdounlinkall(srels, nrels, false);
 
-		for (int i = 0; i < nrels; i++)
+		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* Delete global temporary table info in localhash */
+			if (gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 00000000000..e79a90cca56
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1526 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  The body implementation of Global Temparary table.
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *	  See src/backend/catalog/GTT_README for Global temparary table's
+ *	  requirements and design.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_info_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+int		vacuum_gtt_defer_check_age = 0;
+
+/*
+ * The Global temporary table's shared hash table data structure
+ */
+typedef struct gtt_ctl_data
+{
+	LWLock		lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+/* record this global temporary table in which backends are being used */
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+/*
+ * The Global temporary table's local hash table data structure
+ */
+/* Record the storage information and statistical information of the global temporary table */
+typedef struct
+{
+	Oid			relfilenode;
+	Oid			spcnode;
+
+	/* pg_class relstat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+
+	/* pg_statistic column stat */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_free_statistics(gtt_relfilenode *rnode);
+static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok);
+static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok);
+static Bitmapset *copy_active_gtt_bitmap(Oid relid);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+/*
+ * Calculate shared hash table entry size for GTT.
+ */
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	/* hash entry header size */
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	/*
+	 * hash entry data size
+	 * this is a bitmap in shared memory, each backend have a bit.
+	 */
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+/*
+ * Calculate shared hash table max size for GTT.
+ */
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	/* shared hash header size */
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	/* hash entry size */
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	/* max size */
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+/*
+ * Initialization shared hash table for GTT.
+ */
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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();
+	}
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(gtt_fnode);
+	info.entrysize = action_gtt_shared_hash_entry_size();
+	active_gtt_shared_hash =
+		ShmemInitHash("active gtt shared hash",
+						gtt_shared_ctl->max_entry,
+						gtt_shared_ctl->max_entry,
+						&info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+/*
+ * Record GTT relid to shared hash table, which means that current backend is using this GTT.
+ */
+static void
+gtt_storage_checkin(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			found;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *)&(fnode), 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_global_temporary_table.")));
+	}
+
+	if (!found)
+	{
+		int			wordnum;
+
+		/* init bitmap */
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	/* record itself in bitmap */
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+/*
+ * Remove the GTT relid record from the shared hash table which means that current backend is
+ * not use this GTT.
+ */
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	if (!skiplock)
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		if (!skiplock)
+			LWLockRelease(&gtt_shared_ctl->lock);
+
+		if (isCommit)
+			elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+	/* remove itself from bitmap */
+	bms_del_member(entry->map, MyBackendId);
+
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+
+	if (!skiplock)
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+	return;
+}
+
+/*
+ * Gets usage information for a GTT from shared hash table.
+ * The information is in the form of bitmap.
+ * Quickly copy the entire bitmap from shared memory and return it.
+ * that to avoid holding locks for a long time.
+ */
+static Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	Bitmapset		*map_copy = NULL;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		return NULL;
+	}
+
+	Assert(entry->map);
+
+	/* copy the entire bitmap */
+	if (!bms_is_empty(entry->map))
+		map_copy = bms_copy(entry->map);
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return map_copy;
+}
+
+/*
+ * Check if there are other backends using this GTT besides the current backend.
+ */
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			in_use = false;
+	int			num_use = 0;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		return false;
+	}
+
+	Assert(entry->map);
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+	/* how many backend are using this GTT */
+	num_use = bms_num_members(entry->map);
+	if (num_use == 0)
+		in_use = false;
+	else if (num_use == 1)
+	{
+		/* check if this is itself */
+		if(bms_is_member(MyBackendId, entry->map))
+			in_use = false;
+		else
+			in_use = true;
+	}
+	else
+		in_use = true;
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return in_use;
+}
+
+/*
+ * Record GTT information to local hash.
+ * They include GTT storage info, transaction info and statistical info.
+ */
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+	gtt_local_hash_entry		*entry;
+	MemoryContext			oldcontext;
+	gtt_relfilenode			*new_node = NULL;
+	Oid				relid = RelationGetRelid(rel);
+	int				natts = 0;
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	/* First time through: initialize the hash table */
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		HASHCTL		ctl;
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_info_context =
+			AllocSetContextCreate(CacheMemoryContext,
+								"gtt info context",
+								ALLOCSET_DEFAULT_SIZES);
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		ctl.hcxt = gtt_info_context;
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+	}
+
+	Assert(CacheMemoryContext);
+	Assert(gtt_info_context);
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	entry = gtt_search_by_relid(relid, true);
+	if (!entry)
+	{
+		bool		found = false;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash,
+						(void *) &relid, HASH_ENTER, &found);
+
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			/* record the on commit clause */
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS, true);
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			gtt_storage_checkin(relid);
+		}
+	}
+
+	/* record storage info relstat columnstats and transaction info to relfilenode list */
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	new_node->natts = 0;
+	new_node->attnum = NULL;
+	new_node->att_stat_tups = NULL;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* init column stats structure */
+	natts = RelationGetNumberOfAttributes(rel);
+	new_node->attnum = palloc0(sizeof(int) * natts);
+	new_node->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+	new_node->natts = natts;
+
+	/* only heap have transaction info */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+
+		/**/
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+
+	/* Registration callbacks are used to trigger cleanup during process exit */
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+/*
+ * Remove GTT information from local hash when transaction commit/rollback.
+ */
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	gtt_relfilenode		*d_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+		return;
+	}
+
+	d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+		else
+		{
+			/* rollback transaction */
+			if (entry->relfilenode_list == NIL)
+			{
+				if (entry->relkind == RELKIND_RELATION ||
+					entry->relkind == RELKIND_SEQUENCE)
+					gtt_storage_checkout(relid, false, isCommit);
+
+				hash_search(gtt_storage_local_hash,
+						(void *) &(relid), HASH_REMOVE, NULL);
+			}
+
+			return;
+		}
+	}
+
+	/* Clean up transaction info from Local order list and MyProc */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+
+		/* this is valid relfrozenxid */
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	/* delete relfilenode from rel entry */
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	gtt_free_statistics(d_rnode);
+
+	if (entry->relfilenode_list == NIL)
+	{
+		/* this means we truncate this GTT at current backend */
+
+		/* tell shared hash that current backend will no longer use this GTT */
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(relid, false, isCommit);
+
+		hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_REMOVE, NULL);
+	}
+
+	return;
+}
+
+/*
+ * Check if current backend is using this GTT.
+ */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool			found = false;
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (!OidIsValid(relid))
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry)
+		found = true;
+
+	return found;
+}
+
+/*
+ * When backend exit, bulk cleaning all GTT storage and local buffer of this backend.
+ */
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS			status;
+	gtt_local_hash_entry	*entry;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	/* Need to ensure we have a usable transaction. */
+	AbortOutOfAnyTransaction();
+
+	/* Search all relfilenode for GTT in current backend */
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel[1];
+			RelFileNode		rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel[0] = smgropen(rnode, MyBackendId);
+			smgrdounlinkall(srel, 1, false);
+			smgrclose(srel[0]);
+		}
+
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(entry->relid, true, false);
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+		hash_search(gtt_storage_local_hash, (void *) &(entry->relid), HASH_REMOVE, NULL);
+	}
+
+	/* set to global area */
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;
+
+	return;
+}
+
+/*
+ * Update GTT relstats(relpage/reltuple/relallvisible)
+ * to local hash.
+ */
+void
+up_gtt_relstats(Oid relid,
+					BlockNumber num_pages,
+					double num_tuples,
+					BlockNumber num_all_visible_pages,
+					TransactionId relfrozenxid,
+					TransactionId relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (!OidIsValid(relid))
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (num_pages > 0 &&
+		gtt_rnode->relpages != (int32)num_pages)
+		gtt_rnode->relpages = (int32)num_pages;
+
+	if (num_tuples > 0 &&
+		gtt_rnode->reltuples != (float4)num_tuples)
+		gtt_rnode->reltuples = (float4)num_tuples;
+
+	/* only heap contain transaction information and relallvisible */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (num_all_visible_pages > 0 &&
+			gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+		{
+			gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+		}
+
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNextTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			/* set to local order list */
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			/* set to global area */
+			set_gtt_session_relfrozenxid();
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search GTT relstats(relpage/reltuple/relallvisible)
+ * from local has.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update GTT info(definition is same as pg_statistic)
+ * to local hash.
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	MemoryContext		oldcontext;
+	int			i = 0;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return;
+
+	Assert(entry->relid == reloid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (gtt_rnode->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	/* switch context to gtt_info_context for store tuple at heap_form_tuple */
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == 0)
+		{
+			gtt_rnode->attnum[i] = attnum;
+			break;
+		}
+		else if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			heap_freetuple(gtt_rnode->att_stat_tups[i]);
+			gtt_rnode->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < gtt_rnode->natts);
+	Assert(gtt_rnode->att_stat_tups[i] == NULL);
+	gtt_rnode->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search GTT statistic info(definition is same as pg_statistic)
+ * from local hash.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	int			i = 0;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return NULL;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return NULL;
+
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			return gtt_rnode->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Insert a RelfrozenXID into the list and keep the list in order.
+ */
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int		i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Remove a RelfrozenXID from order list gtt_session_relfrozenxid_list.
+ */
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+/*
+ * Update of backend Level oldest relfrozenxid to MyProc.
+ * This makes each backend's oldest RelFrozenxID globally visible.
+ */
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->backend_gtt_frozenxid != gtt_frozenxid)
+		MyProc->backend_gtt_frozenxid = gtt_frozenxid;
+}
+
+/*
+ * Get GTT column level data statistics.
+ */
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate		*tupstore;
+	HeapTuple		tuple;
+	Relation		rel = NULL;
+	Oid			reloid = PG_GETARG_OID(0);
+	int			attnum = PG_GETARG_INT32(1);
+	char			rel_persistence;
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	/* get data from local hash */
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum		values[31];
+		bool		isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, NoLock);
+	relation_close(pg_tatistic, AccessShareLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get GTT table level data statistics.
+ */
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate	*tupstore;
+	TupleDesc	tupdesc;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid			reloid = PG_GETARG_OID(0);
+	Oid			relnode = 0;
+	char			rel_persistence;
+	BlockNumber		relpages = 0;
+	BlockNumber		relallvisible = 0;
+	uint32			relfrozenxid = 0;
+	uint32			relminmxid = 0;
+	double			reltuples = 0;
+	Relation		rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[6];
+		bool	isnull[6];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get a list of backend pids that are currently using this GTT.
+ */
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	PGPROC			*proc = NULL;
+	Bitmapset		*map = NULL;
+	Tuplestorestate		*tupstore;
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	HeapTuple		tuple;
+	Oid			reloid = PG_GETARG_OID(0);
+	char			rel_persistence;
+	Relation		rel = NULL;
+	pid_t			pid = 0;
+	int				backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	/* get data from share hash */
+	map = copy_active_gtt_bitmap(reloid);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			/* backendid map to process pid */
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get backend level oldest relfrozenxid of each backend using GTT in current database.
+ */
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate		*tupstore;
+	int			*pids = NULL;
+	uint32			*xids = NULL;
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	HeapTuple		tuple;
+	int			num_xid = MaxBackends + 1;
+	int			i = 0;
+	int			j = 0;
+	uint32			oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+
+	/* Get backend level oldest relfrozenxid in all backend that in MyDatabaseId use GTT */
+	oldest = list_all_backend_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+/*
+ * In order to build the GTT index, force enable GTT'index.
+ */
+void
+force_enable_gtt_index(Relation index)
+{
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(RelationGetRelid(index)));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+/*
+ * Fix the local state of the GTT's index.
+ */
+void
+gtt_fix_index_backend_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid heapOid = index->rd_index->indrelid;
+
+	/* Must be GTT */
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	/*
+	 * If this GTT is not initialized in the current backend,
+	 * its index status is temporarily set to invalid(local relcache).
+	 */
+	if (gtt_storage_attached(heapOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+/*
+ * During the SQL initialization of the executor (InitPlan)
+ * Initialize storage of GTT GTT'indexes and build empty index.
+ */
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int		i;
+	Oid		toastrelid;
+
+	if (!(operation == CMD_UTILITY || operation == CMD_INSERT))
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	/* Each GTT is initialized once in each backend */
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	/* init heap storage */
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	/* rebuild all local index for global temp table */
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		index_build(relation, index, info, true, false);
+
+		/* after build index, index re-enabled */
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+		Assert(info->ii_ReadyForInserts);
+	}
+
+	/* rebuild index for global temp toast table */
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+
+		/* init index storage */
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid			indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+			/* build empty index */
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			Assert(currentIndex->rd_index->indisvalid);
+			Assert(currentIndex->rd_index->indislive);
+			Assert(currentIndex->rd_index->indisready);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+/*
+ * Release the data structure memory used to store GTT storage info.
+ */
+static void
+gtt_free_statistics(gtt_relfilenode *rnode)
+{
+	int i;
+
+	Assert(rnode);
+
+	for (i = 0; i < rnode->natts; i++)
+	{
+		if (rnode->att_stat_tups[i])
+		{
+			heap_freetuple(rnode->att_stat_tups[i]);
+			rnode->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (rnode->attnum)
+		pfree(rnode->attnum);
+
+	if (rnode->att_stat_tups)
+		pfree(rnode->att_stat_tups);
+
+	pfree(rnode);
+
+	return;
+}
+
+/*
+ * Get the current relfilenode of this GTT.
+ */
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
+/*
+ * Get a relfilenode used by this GTT during the transaction life cycle.
+ */
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok)
+{
+	gtt_relfilenode		*rnode = NULL;
+	ListCell		*lc;
+
+	Assert(entry);
+
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == relfilenode)
+		{
+			rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (!missing_ok && rnode == NULL)
+		elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+	return rnode;
+}
+
+/*
+ * Get one GTT info from local hash.
+ */
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry == NULL && !missing_ok)
+		elog(ERROR, "relid %u not found in local hash", relid);
+
+	return entry;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 55f6e3711d8..fc75533263b 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 8bfb2ad9584..500305e8ae8 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -104,7 +105,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -185,6 +186,17 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -601,14 +613,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]));
 		}
 
 		/*
@@ -1633,7 +1646,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;
@@ -1735,31 +1748,48 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/*
+		 * For global temporary table,
+		 * Update column statistic to localhash, not catalog.
+		 */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 9d22f648a84..a44eefa1f6b 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/toasting.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
@@ -390,6 +391,22 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+	{
+		if (gtt_storage_attached(RelationGetRelid(OldHeap)))
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("not support cluster global temporary table yet")));
+
+		relation_close(OldHeap, AccessExclusiveLock);
+		pgstat_progress_end_command();
+		return;
+	}
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
@@ -585,6 +602,8 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
 	TransactionId frozenXid;
 	MultiXactId cutoffMulti;
 
+	Assert(!RELATION_IS_GLOBAL_TEMP(OldHeap));
+
 	/* Mark the correct index as clustered */
 	if (OidIsValid(indexOid))
 		mark_index_clustered(OldHeap, indexOid, true);
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 53f48531419..c03191cce94 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -289,7 +289,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP_ON_CURRENT_SESSION(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, whereClause,
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 40a54ad0bd7..7895e7d99b9 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -30,6 +30,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/copyfrom_internal.h"
 #include "commands/progress.h"
@@ -659,6 +660,9 @@ CopyFrom(CopyFromState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	/* Check and init global temporary table storage in current backend */
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	/*
 	 * Set up a ModifyTableState so we can let FDW(s) init themselves for
 	 * foreign-table result relation(s).
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index c14ca27c5ed..d65ae895356 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -111,6 +111,7 @@ struct ReindexIndexCallbackState
 {
 	ReindexParams params;		/* options from statement */
 	Oid			locked_table_oid;	/* tracks previously locked table */
+	LOCKMODE	lockmode;
 };
 
 /*
@@ -570,7 +571,7 @@ DefineIndex(Oid relationId,
 	 * is more efficient.  Do this before any use of the concurrent option is
 	 * done.
 	 */
-	if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+	if (stmt->concurrent && !RelpersistenceTsTemp(get_rel_persistence(relationId)))
 		concurrent = true;
 	else
 		concurrent = false;
@@ -2581,9 +2582,9 @@ ReindexIndex(RangeVar *indexRelation, ReindexParams *params, bool isTopLevel)
 	 */
 	state.params = *params;
 	state.locked_table_oid = InvalidOid;
+	state.lockmode = AccessShareLock;
 	indOid = RangeVarGetRelidExtended(indexRelation,
-									  (params->options & REINDEXOPT_CONCURRENTLY) != 0 ?
-									  ShareUpdateExclusiveLock : AccessExclusiveLock,
+									  AccessShareLock,
 									  0,
 									  RangeVarCallbackForReindexIndex,
 									  &state);
@@ -2594,11 +2595,25 @@ ReindexIndex(RangeVar *indexRelation, ReindexParams *params, bool isTopLevel)
 	 */
 	persistence = get_rel_persistence(indOid);
 	relkind = get_rel_relkind(indOid);
+	if (persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		LOCKMODE	lockmode;
+
+		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0)
+			lockmode = ShareUpdateExclusiveLock;
+		else
+			lockmode = AccessExclusiveLock;
+
+		/* lock heap first */
+		Assert(OidIsValid(state.locked_table_oid));
+		LockRelationOid(state.locked_table_oid, lockmode);
+		LockRelationOid(indOid, lockmode);
+	}
 
 	if (relkind == RELKIND_PARTITIONED_INDEX)
 		ReindexPartitions(indOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 persistence != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(persistence))
 		ReindexRelationConcurrently(indOid, params);
 	else
 	{
@@ -2620,15 +2635,7 @@ RangeVarCallbackForReindexIndex(const RangeVar *relation,
 {
 	char		relkind;
 	struct ReindexIndexCallbackState *state = arg;
-	LOCKMODE	table_lockmode;
-
-	/*
-	 * Lock level here should match table lock in reindex_index() for
-	 * non-concurrent case and table locks used by index_concurrently_*() for
-	 * concurrent case.
-	 */
-	table_lockmode = (state->params.options & REINDEXOPT_CONCURRENTLY) != 0 ?
-		ShareUpdateExclusiveLock : ShareLock;
+	LOCKMODE	table_lockmode = state->lockmode;
 
 	/*
 	 * If we previously locked some other index's heap, and the name we're
@@ -2689,6 +2696,8 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 {
 	Oid			heapOid;
 	bool		result;
+	char		relpersistence;
+	int 		reindex_flags = 0;
 
 	/*
 	 * The lock level used here should match reindex_relation().
@@ -2699,15 +2708,27 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 	 * locks on our temporary table.
 	 */
 	heapOid = RangeVarGetRelidExtended(relation,
-									   (params->options & REINDEXOPT_CONCURRENTLY) != 0 ?
-									   ShareUpdateExclusiveLock : ShareLock,
+									   AccessShareLock,
 									   0,
 									   RangeVarCallbackOwnsTable, NULL);
 
+	relpersistence = get_rel_persistence(heapOid);
+	if (relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		LOCKMODE	lockmode;
+
+		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0)
+			lockmode = ShareUpdateExclusiveLock;
+		else
+			lockmode = ShareLock;
+
+		LockRelationOid(heapOid, lockmode);
+	}
+
 	if (get_rel_relkind(heapOid) == RELKIND_PARTITIONED_TABLE)
 		ReindexPartitions(heapOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(relpersistence))
 	{
 		result = ReindexRelationConcurrently(heapOid, params);
 
@@ -2721,9 +2742,14 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 		ReindexParams newparams = *params;
 
 		newparams.options |= REINDEXOPT_REPORT_PROGRESS;
+
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			reindex_flags |= REINDEX_REL_PROCESS_GLOBAL_TEMP;
+
+		reindex_flags |= REINDEX_REL_PROCESS_TOAST;
+		reindex_flags |= REINDEX_REL_CHECK_CONSTRAINTS;
 		result = reindex_relation(heapOid,
-								  REINDEX_REL_PROCESS_TOAST |
-								  REINDEX_REL_CHECK_CONSTRAINTS,
+								  reindex_flags,
 								  &newparams);
 		if (!result)
 			ereport(NOTICE,
@@ -3122,7 +3148,7 @@ ReindexMultipleInternal(List *relids, ReindexParams *params)
 			   relkind != RELKIND_PARTITIONED_TABLE);
 
 		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			relpersistence != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(relpersistence))
 		{
 			ReindexParams newparams = *params;
 
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 62465bacd81..ef37f79ba68 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -51,12 +51,32 @@ LockTableCommand(LockStmt *lockstmt)
 		RangeVar   *rv = (RangeVar *) lfirst(p);
 		bool		recurse = rv->inh;
 		Oid			reloid;
+		LOCKMODE	lockmode = lockstmt->mode;
+		char		relpersistence;
 
-		reloid = RangeVarGetRelidExtended(rv, lockstmt->mode,
-										  lockstmt->nowait ? RVR_NOWAIT : 0,
+		reloid = RangeVarGetRelidExtended(rv, NoLock, 0,
 										  RangeVarCallbackForLockTable,
 										  (void *) &lockstmt->mode);
 
+		relpersistence = get_rel_persistence(reloid);
+		if (relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			if (!lockstmt->nowait)
+				LockRelationOid(reloid, lockmode);
+			else if (!ConditionalLockRelationOid(reloid, lockmode))
+			{
+				/* try to throw error by name; relation could be deleted... */
+				char	   *relname = get_rel_name(reloid);
+
+				if (!relname)
+					return;		/* child concurrently dropped, just skip it */
+				ereport(ERROR,
+						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						 errmsg("could not obtain lock on relation \"%s\"",
+								relname)));
+			}
+		}
+
 		if (get_rel_relkind(reloid) == RELKIND_VIEW)
 			LockViewRecurse(reloid, lockstmt->mode, lockstmt->nowait, NIL);
 		else if (recurse)
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 72bfdc07a49..e5257f610f6 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -220,9 +223,12 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	rel = table_open(seqoid, AccessExclusiveLock);
 	tupDesc = RelationGetDescr(rel);
 
-	/* now initialize the sequence's data */
-	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	if (!RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		/* now initialize the sequence's data */
+		tuple = heap_form_tuple(tupDesc, value, null);
+		fill_seq_with_data(rel, tuple);
+	}
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -275,8 +281,6 @@ ResetSequence(Oid seq_relid)
 	Buffer		buf;
 	HeapTupleData seqdatatuple;
 	HeapTuple	tuple;
-	HeapTuple	pgstuple;
-	Form_pg_sequence pgsform;
 	int64		startv;
 
 	/*
@@ -287,12 +291,7 @@ ResetSequence(Oid seq_relid)
 	init_sequence(seq_relid, &elm, &seq_rel);
 	(void) read_seq_tuple(seq_rel, &buf, &seqdatatuple);
 
-	pgstuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seq_relid));
-	if (!HeapTupleIsValid(pgstuple))
-		elog(ERROR, "cache lookup failed for sequence %u", seq_relid);
-	pgsform = (Form_pg_sequence) GETSTRUCT(pgstuple);
-	startv = pgsform->seqstart;
-	ReleaseSysCache(pgstuple);
+	startv = get_seqence_start_value(seq_relid);
 
 	/*
 	 * Copy the existing sequence tuple.
@@ -451,6 +450,15 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 
 	init_sequence(relid, &elm, &seqrel);
 
+	if (RELATION_IS_GLOBAL_TEMP(seqrel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(seqrel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary sequence %s when other backend attached it.",
+						RelationGetRelationName(seqrel))));
+	}
+
 	rel = table_open(SequenceRelationId, RowExclusiveLock);
 	seqtuple = SearchSysCacheCopy1(SEQRELID,
 								   ObjectIdGetDatum(relid));
@@ -611,7 +619,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +944,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1153,6 +1161,14 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	/* Initializes the storage for sequence which the global temporary table belongs. */
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1927,3 +1943,58 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+/*
+ * Get the startValue of the sequence from syscache.
+ */
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+/*
+ * Initialize sequence which global temporary table belongs.
+ */
+void
+gtt_init_seq(Relation rel)
+{
+	Datum		value[SEQ_COL_LASTCOL];
+	bool		null[SEQ_COL_LASTCOL];
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL - 1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+	null[SEQ_COL_LASTVAL - 1] = false;
+
+	value[SEQ_COL_LOG - 1] = Int64GetDatum((int64)0);
+	null[SEQ_COL_LOG - 1] = false;
+
+	value[SEQ_COL_CALLED - 1] = BoolGetDatum(false);
+	null[SEQ_COL_CALLED - 1] = false;
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1c2ebe1bf69..178575e6062 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -118,6 +119,7 @@ typedef struct OnCommitItem
 	 */
 	SubTransactionId creating_subid;
 	SubTransactionId deleting_subid;
+	bool			 is_global_temp;
 } OnCommitItem;
 
 static List *on_commits = NIL;
@@ -602,7 +604,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
 static char GetAttributeCompression(Oid atttypid, char *compression);
-
+static OnCommitAction gtt_oncommit_option(List *options);
 
 /* ----------------------------------------------------------------
  *		DefineRelation
@@ -647,6 +649,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -658,7 +661,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * Check consistency of arguments
 	 */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !RelpersistenceTsTemp(stmt->relation->relpersistence))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -688,7 +691,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	if (RelpersistenceTsTemp(stmt->relation->relpersistence)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -789,6 +792,50 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* For global temporary table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		if (!(relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE))
+			elog(ERROR, "Only support global temporary regular table.");
+
+		/* Check parent table */
+		if (inheritOids)
+			elog(ERROR, "Not support global temporary partition table or inherit table.");
+
+		/* Check oncommit clause and save to reloptions */
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temporary table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1415,7 +1462,7 @@ RemoveRelations(DropStmt *drop)
 		 * relation persistence cannot be known without its OID.
 		 */
 		if (drop->concurrent &&
-			get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(get_rel_persistence(relOid)))
 		{
 			Assert(list_length(drop->objects) == 1 &&
 				   drop->removeType == OBJECT_INDEX);
@@ -1624,9 +1671,9 @@ ExecuteTruncate(TruncateStmt *stmt)
 		Relation	rel;
 		bool		recurse = rv->inh;
 		Oid			myrelid;
-		LOCKMODE	lockmode = AccessExclusiveLock;
+		LOCKMODE	lockmode;
 
-		myrelid = RangeVarGetRelidExtended(rv, lockmode,
+		myrelid = RangeVarGetRelidExtended(rv, AccessShareLock,
 										   0, RangeVarCallbackForTruncate,
 										   NULL);
 
@@ -1634,9 +1681,21 @@ ExecuteTruncate(TruncateStmt *stmt)
 		if (list_member_oid(relids, myrelid))
 			continue;
 
-		/* open the relation, we already hold a lock on it */
+		/* open the relation, we need hold a low-level lock first */
 		rel = table_open(myrelid, NoLock);
 
+		/*
+		 * Truncate global temp table only cleans up the data in current backend,
+		 * only low-level locks are required.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel))
+			lockmode = AccessShareLock;
+		else
+		{
+			lockmode = AccessExclusiveLock;
+			LockRelationOid(myrelid, lockmode);
+		}
+
 		/*
 		 * RangeVarGetRelidExtended() has done most checks with its callback,
 		 * but other checks with the now-opened Relation remain.
@@ -1886,6 +1945,7 @@ ExecuteTruncateGuts(List *explicit_rels,
 	foreach(cell, rels)
 	{
 		Relation	rel = (Relation) lfirst(cell);
+		LOCKMODE	lockmode;
 
 		/* Skip partitioned tables as there is nothing to do */
 		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
@@ -1936,6 +1996,19 @@ ExecuteTruncateGuts(List *explicit_rels,
 			continue;
 		}
 
+		/*
+		 * Skip the global temporary table that is not initialized for storage
+		 * in current backend.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel))
+		{
+			lockmode = AccessShareLock;
+			if (!gtt_storage_attached(RelationGetRelid(rel)))
+				continue;
+		}
+		else
+			lockmode = AccessExclusiveLock;
+
 		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
@@ -1954,6 +2027,7 @@ ExecuteTruncateGuts(List *explicit_rels,
 			Oid			heap_relid;
 			Oid			toast_relid;
 			ReindexParams reindex_params = {0};
+			int			reindex_flags = 0;
 
 			/*
 			 * This effectively deletes all rows in the table, and may be done
@@ -1981,17 +2055,21 @@ ExecuteTruncateGuts(List *explicit_rels,
 			if (OidIsValid(toast_relid))
 			{
 				Relation	toastrel = relation_open(toast_relid,
-													 AccessExclusiveLock);
+													 lockmode);
 
 				RelationSetNewRelfilenode(toastrel,
 										  toastrel->rd_rel->relpersistence);
 				table_close(toastrel, NoLock);
 			}
 
+			reindex_flags = REINDEX_REL_PROCESS_TOAST;
+			if (RELATION_IS_GLOBAL_TEMP(rel))
+				reindex_flags |= REINDEX_REL_PROCESS_GLOBAL_TEMP;
+
 			/*
 			 * Reconstruct the indexes to match, and we're done.
 			 */
-			reindex_relation(heap_relid, REINDEX_REL_PROCESS_TOAST,
+			reindex_relation(heap_relid, reindex_flags,
 							 &reindex_params);
 		}
 
@@ -3998,6 +4076,16 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temporary table only current backend use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -5329,6 +5417,24 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 
 			rel = table_open(tab->relid, NoLock);
 			find_composite_type_dependencies(rel->rd_rel->reltype, rel, NULL);
+
+			if (RELATION_IS_GLOBAL_TEMP(rel) && tab->rewrite > 0)
+			{
+				if (tab->chgPersistence)
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot change global temporary table persistence setting")));
+
+				if(gtt_storage_attached(tab->relid))
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("Only support alter global temporary table in an empty context."),
+						 errhint("Please create a new connection and execute ALTER TABLE on the new connection.")));
+
+				/* There is no need to override the whole temp table */
+				tab->rewrite = 0;
+			}
+
 			table_close(rel, NoLock);
 		}
 
@@ -5380,6 +5486,8 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot rewrite temporary tables of other sessions")));
 
+			Assert(!RELATION_IS_GLOBAL_TEMP(OldHeap));
+
 			/*
 			 * Select destination tablespace (same as original unless user
 			 * requested a change)
@@ -9011,6 +9119,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -13709,6 +13823,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -13908,6 +14025,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temporary table");
+
 	/* Check first if relation can be moved to new tablespace */
 	if (!CheckRelationTableSpaceMove(rel, newTableSpace))
 	{
@@ -14211,7 +14331,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(RelationGetSmgr(rel), dstrel, MAIN_FORKNUM,
@@ -15809,6 +15929,7 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
 	switch (rel->rd_rel->relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -16223,7 +16344,7 @@ AlterSeqNamespaces(Relation classRel, Relation rel,
  * Register a newly-created relation's ON COMMIT action.
  */
 void
-register_on_commit_action(Oid relid, OnCommitAction action)
+register_on_commit_action(Oid relid, OnCommitAction action, bool is_gloal_temp)
 {
 	OnCommitItem *oc;
 	MemoryContext oldcxt;
@@ -16242,6 +16363,7 @@ register_on_commit_action(Oid relid, OnCommitAction action)
 	oc->oncommit = action;
 	oc->creating_subid = GetCurrentSubTransactionId();
 	oc->deleting_subid = InvalidSubTransactionId;
+	oc->is_global_temp = is_gloal_temp;
 
 	/*
 	 * We use lcons() here so that ON COMMIT actions are processed in reverse
@@ -16287,6 +16409,7 @@ PreCommit_on_commit_actions(void)
 	ListCell   *l;
 	List	   *oids_to_truncate = NIL;
 	List	   *oids_to_drop = NIL;
+	List	   *oids_to_truncate_gtt = NIL;
 
 	foreach(l, on_commits)
 	{
@@ -16310,7 +16433,12 @@ PreCommit_on_commit_actions(void)
 				 * tables, as they must still be empty.
 				 */
 				if ((MyXactFlags & XACT_FLAGS_ACCESSEDTEMPNAMESPACE))
-					oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid);
+				{
+					if (oc->is_global_temp)
+						oids_to_truncate_gtt = lappend_oid(oids_to_truncate_gtt, oc->relid);
+					else
+						oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid);
+				}
 				break;
 			case ONCOMMIT_DROP:
 				oids_to_drop = lappend_oid(oids_to_drop, oc->relid);
@@ -16327,7 +16455,10 @@ PreCommit_on_commit_actions(void)
 	 * exists at truncation time.
 	 */
 	if (oids_to_truncate != NIL)
-		heap_truncate(oids_to_truncate);
+		heap_truncate(oids_to_truncate, false);
+
+	if (oids_to_truncate_gtt != NIL)
+		heap_truncate(oids_to_truncate_gtt, true);
 
 	if (oids_to_drop != NIL)
 	{
@@ -17326,6 +17457,13 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot attach temporary relation of another session as partition")));
 
+	/* If the parent is permanent, so must be all of its partitions. */
+	if (attachrel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach a global temporary relation as partition of permanent relation \"%s\"",
+						RelationGetRelationName(rel))));
+
 	/* Check if there are any columns in attachrel that aren't in the parent */
 	tupleDesc = RelationGetDescr(attachrel);
 	natts = tupleDesc->natts;
@@ -18761,3 +18899,40 @@ GetAttributeCompression(Oid atttypid, char *compression)
 
 	return cmethod;
 }
+
+/*
+ * Parse the on commit clause for the temporary table
+ */
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			bool	res = false;
+			char	*sval = defGetString(def);
+
+			/* It has to be a Boolean value */
+			if (!parse_bool(sval, &res))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
+
+			if (res)
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 5c4bc15b441..77c0f384ed1 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"
@@ -1315,6 +1316,22 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = RELATION_IS_GLOBAL_TEMP(relation);
+
+	 /* For global temporary table */
+	if (is_gtt)
+	{
+		/* Store relation statistics and transaction information to the localhash */
+		up_gtt_relstats(RelationGetRelid(relation),
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+
+		/* Update relation statistics to local relcache */
+		relation->rd_rel->relpages = (int32) num_pages;
+		relation->rd_rel->reltuples = (float4) num_tuples;
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1328,17 +1345,23 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (!is_gtt &&
+		pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (!is_gtt &&
+		pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (!is_gtt &&
+		pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1351,7 +1374,15 @@ vac_update_relstats(Relation relation,
 		/*
 		 * If we didn't find any indexes, reset relhasindex.
 		 */
-		if (pgcform->relhasindex && !hasindex)
+		if (is_gtt &&
+			RelationGetIndexList(relation) != NIL)
+		{
+			/*
+			 * Global temporary tables may contain indexes that are not valid locally.
+			 * The catalog should not be updated based on local invalid index.
+			 */
+		}
+		else if (pgcform->relhasindex && !hasindex)
 		{
 			pgcform->relhasindex = false;
 			dirty = true;
@@ -1383,7 +1414,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNextTransactionId(),
@@ -1394,7 +1426,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1502,6 +1535,13 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/*
+		 * The relfrozenxid for a global temporary talble is stored in localhash,
+		 * not pg_class, See list_all_session_gtt_frozenxids()
+		 */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1559,6 +1599,43 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/* If enable global temporary table */
+	if (max_active_gtt > 0)
+	{
+		TransactionId	safe_age;
+		/*  */
+		TransactionId	oldest_gtt_frozenxid =
+			list_all_backend_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid))
+		{
+			safe_age = oldest_gtt_frozenxid + vacuum_gtt_defer_check_age;
+			if (safe_age < FirstNormalTransactionId)
+				safe_age += FirstNormalTransactionId;
+
+			/*
+			 * We tolerate that the minimum age of gtt is less than
+			 * the minimum age of conventional tables, otherwise it will
+			 * throw warning message.
+			 */
+			if (TransactionIdIsNormal(safe_age) &&
+				TransactionIdPrecedes(safe_age, newFrozenXid))
+			{
+				ereport(WARNING,
+					(errmsg("global temp table oldest relfrozenxid %u is the oldest in the entire db",
+							oldest_gtt_frozenxid),
+					 errdetail("The oldest relfrozenxid in pg_class is %u", newFrozenXid),
+					 errhint("If they differ greatly, please consider cleaning up the data in global temp table.")));
+			}
+
+			/*
+			 * We need to ensure that the clog required by gtt is not cleand.
+			 */
+			if (TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+				newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1910,6 +1987,19 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	/*
+	 * Skip those global temporary table that are not initialized in
+	 * current backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel) &&
+		!gtt_storage_attached(RelationGetRelid(rel)))
+	{
+		relation_close(rel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 4df05a0b33d..4c181e2e14e 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -527,6 +527,12 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	/* Global temporary table are not sensible. */
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b3ce4bae530..611e3f18a70 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -784,6 +784,10 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		/* This is one kind of temp table */
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 5c723bc54e1..191e0f6fd21 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d328856ae5b..4e2bbb224cc 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -38,6 +38,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -633,6 +634,9 @@ ExecInsert(ModifyTableState *mtstate,
 		resultRelInfo->ri_IndexRelationDescs == NULL)
 		ExecOpenIndices(resultRelInfo, onconflict != ONCONFLICT_NONE);
 
+	/* Init storage for global temporary table in current backend */
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	/*
 	 * BEFORE ROW INSERT Triggers.
 	 *
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 296dd75c1b6..d971aea2546 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -48,7 +48,7 @@
 #include "partitioning/partprune.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/lsyscache.h"
-
+#include "utils/rel.h"
 
 /* results of subquery_is_pushdown_safe */
 typedef struct pushdown_safety_info
@@ -619,7 +619,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			if (RelpersistenceTsTemp(get_rel_persistence(rte->relid)))
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index bd01ec0526f..ff4e81ca2cf 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6071,7 +6071,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
-	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+	if (RELATION_IS_TEMP(heap) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index c5194fdbbf2..38d7c658541 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -30,6 +30,7 @@
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temporary table in current backend */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 146ee8dd1ea..2d4e9393f00 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2907,6 +2907,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 08f1bf1031c..d6048f2ee57 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3410,17 +3410,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; }
@@ -11683,19 +11677,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 c5c3f26ecf1..2a2b2789077 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -82,6 +82,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);
 
 
 /*
@@ -3665,3 +3666,53 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * Like function isQueryUsingTempRelation_walker
+ * return true if any relation underlying
+ * the query is a global temporary table.
+ */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* Check if the query uses global temporary table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 313d7b6ff02..38e0e162e82 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -447,6 +447,13 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	/*
+	 * If a sequence is bound to a global temporary table, then the sequence
+	 * must been "global temporary"
+	 */
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
@@ -3326,6 +3333,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		cxt.isforeign = false;
 	}
 	cxt.relation = stmt->relation;
+	/* Sets the table persistence to the context */
+	cxt.relation->relpersistence = RelationGetRelPersistence(rel);
 	cxt.rel = rel;
 	cxt.inhRelations = NIL;
 	cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 96332320a73..75ed4d0ae9e 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2115,6 +2115,14 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/*
+			 * Aotuvacuum cannot vacuum the private data stored in each backend
+			 * that belongs to global temporary table, so skip them.
+			 */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2181,7 +2189,7 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		if (RelpersistenceTsTemp(classForm->relpersistence))
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 08ebabfe96a..c346c59c7f4 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlogutils.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -2934,6 +2935,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * Returns 0 if this global temporary table is not initialized in current
+	 * backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 9fa3e0631e6..cc3eb928bc6 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -23,6 +23,7 @@
 #include "access/syncscan.h"
 #include "access/twophase.h"
 #include "commands/async.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
@@ -143,6 +144,7 @@ CalculateShmemSize(int *num_semaphores)
 	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
@@ -246,6 +248,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* For global temporary table shared hashtable */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index bd3c7a47fe2..22481374778 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -65,6 +65,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -5131,3 +5132,78 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temporary table.
+ */
+int
+list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct		*arrayP = procArray;
+	TransactionId		result = InvalidTransactionId;
+	int			index;
+	uint8			flags = 0;
+	int			i = 0;
+
+	/* return 0 if feature is disabled */
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+		*n = 0;
+	}
+
+	/* Disable in standby node */
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		uint8           statusFlags = ProcGlobal->statusFlags[index];
+
+		if (statusFlags & flags)
+			continue;
+
+		/* Fetch all backend that is belonging to MyDatabaseId */
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->backend_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->backend_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->backend_gtt_frozenxid, result))
+				result = proc->backend_gtt_frozenxid;
+
+			/* save backend pid and backend level oldest relfrozenxid */
+			if (max_size > 0)
+			{
+				pids[i] = proc->pid;
+				xids[i] = proc->backend_gtt_frozenxid;
+				i++;
+			}
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (max_size > 0)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 862097352bb..4edd3b31f7a 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -176,7 +176,9 @@ static const char *const BuiltinTrancheNames[] = {
 	/* LWTRANCHE_PARALLEL_APPEND: */
 	"ParallelAppend",
 	/* LWTRANCHE_PER_XACT_PREDICATE_LIST: */
-	"PerXactPredicateList"
+	"PerXactPredicateList",
+	/* LWTRANCHE_GTT_CTL */
+	"GlobalTempTableControl"
 };
 
 StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index b7d9da0aa9f..8051f2053f9 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -393,6 +393,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;	/* init backend level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
@@ -578,6 +579,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;	/* init backend level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index d5a7fb13f3c..8225cf6219f 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -982,6 +982,13 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/*
+			 * For global temporary table ,each backend has its own storage,
+			 * also only sees its own storage. Use Backendid to identify them.
+			 */
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 10895fb2876..66255eb7604 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -108,6 +108,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -5115,12 +5116,26 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								/* For global temporary table, get statistic data from localhash */
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -5368,15 +5383,28 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6820,6 +6848,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;
@@ -6837,6 +6866,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6848,6 +6885,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;
@@ -6863,6 +6902,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7781,6 +7828,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))
@@ -7793,6 +7842,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7805,6 +7863,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.
@@ -7824,6 +7884,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 4ebaa552a27..78c33d2ac87 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -3113,6 +3114,19 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		/* For global temporary table, get statistic data from localhash */
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3e..94fdb998aae 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -65,6 +65,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1116,6 +1117,28 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+
+				/* For global temporary table, get relstat data from localhash */
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								NULL, NULL);
+
+				/* And put them to local relcache */
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1173,6 +1196,8 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			/* The state of the global temporary table's index may need to be set */
+			gtt_fix_index_backend_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -1300,7 +1325,22 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+			/*
+			 * For global temporary table, get the latest relfilenode
+			 * from localhash and put it in relcache.
+			 */
+			if (OidIsValid(newrelnode) &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2251,6 +2291,9 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		/* The state of the global temporary table's index may need to be set */
+		gtt_fix_index_backend_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3489,6 +3532,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3598,28 +3645,39 @@ void
 RelationSetNewRelfilenode(Relation relation, char persistence)
 {
 	Oid			newrelfilenode;
-	Relation	pg_class;
-	HeapTuple	tuple;
+	Relation	pg_class = NULL;
+	HeapTuple	tuple = NULL;
 	Form_pg_class classform;
 	MultiXactId minmulti = InvalidMultiXactId;
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
 
+	/*
+	 * For global temporary table, storage information for the table is
+	 * maintained locally, not in catalog.
+	 */
+	bool		update_catalog = !RELATION_IS_GLOBAL_TEMP(relation);
+
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
 
-	/*
-	 * Get a writable copy of the pg_class tuple for the given relation.
-	 */
-	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+	memset(&classform, 0, sizeof(classform));
 
-	tuple = SearchSysCacheCopy1(RELOID,
-								ObjectIdGetDatum(RelationGetRelid(relation)));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "could not find tuple for relation %u",
-			 RelationGetRelid(relation));
-	classform = (Form_pg_class) GETSTRUCT(tuple);
+	if (update_catalog)
+	{
+		/*
+		 * Get a writable copy of the pg_class tuple for the given relation.
+		 */
+		pg_class = table_open(RelationRelationId, RowExclusiveLock);
+
+		tuple = SearchSysCacheCopy1(RELOID,
+									ObjectIdGetDatum(RelationGetRelid(relation)));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u",
+				 RelationGetRelid(relation));
+		classform = (Form_pg_class) GETSTRUCT(tuple);
+	}
 
 	/*
 	 * Schedule unlinking of the old storage at transaction commit.
@@ -3645,7 +3703,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;
@@ -3665,6 +3723,18 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 			break;
 	}
 
+	/* For global temporary table */
+	if (!update_catalog)
+	{
+		Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+		Assert(RELATION_IS_GLOBAL_TEMP(relation));
+		Assert(!RelationIsMapped(relation));
+
+		/* Make cache invalid and set new relnode to local cache. */
+		CacheInvalidateRelcache(relation);
+		relation->rd_node.relNode = relnode;
+	}
 	/*
 	 * If we're dealing with a mapped index, pg_class.relfilenode doesn't
 	 * change; instead we have to send the update to the relation mapper.
@@ -3674,7 +3744,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	 * possibly-inaccurate values of relpages etc, but those will be fixed up
 	 * later.
 	 */
-	if (RelationIsMapped(relation))
+	else if (RelationIsMapped(relation))
 	{
 		/* This case is only supported for indexes */
 		Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3720,9 +3790,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 		CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 	}
 
-	heap_freetuple(tuple);
+	if (update_catalog)
+	{
+		heap_freetuple(tuple);
 
-	table_close(pg_class, RowExclusiveLock);
+		table_close(pg_class, RowExclusiveLock);
+	}
 
 	/*
 	 * Make the pg_class row change or relation map change visible.  This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index d2ce4a84506..7717ee2ca13 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -44,6 +44,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "commands/prepare.h"
 #include "commands/trigger.h"
@@ -152,6 +153,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temporary table feature.
+ * table schema are still saved in catalog.
+ *
+ * num > 0 means allows the database to manage multiple active tables at the same time.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2127,6 +2140,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 "
@@ -2697,6 +2719,16 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"vacuum_gtt_defer_check_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("The defer check age of GTT, used to check expired data after vacuum."),
+			NULL
+		},
+		&vacuum_gtt_defer_check_age,
+		10000, 0, 1000000,
+		NULL, NULL, NULL
+	},
+
 	/*
 	 * See also CheckRequiredParameterValues() if this parameter changes
 	 */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a485fb2d070..80658c4f3ba 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2503,6 +2503,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
 		dopt->no_unlogged_table_data)
 		return;
 
+	/* Don't dump data in global temporary table/sequence */
+	if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		return;
+
 	/* Check that the data is not explicitly excluded */
 	if (simple_oid_list_member(&tabledata_exclude_oids,
 							   tbinfo->dobj.catId.oid))
@@ -15961,6 +15965,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
 		char	   *foreign = "";
+		char	   *table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -16014,9 +16019,15 @@ dumpTableSchema(Archive *fout, const 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);
 
@@ -16380,6 +16391,15 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 			}
 		}
 
+		/*
+		 * Transaction information for the global temporary table is not stored
+		 * in the pg_class.
+		 */
+		if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			Assert(tbinfo->frozenxid == 0);
+			Assert(tbinfo->minmxid == 0);
+		}
 		/*
 		 * In binary_upgrade mode, arrange to restore the old relfrozenxid and
 		 * relminmxid of all vacuumable relations.  (While vacuum.c processes
@@ -16387,7 +16407,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		 * of other relations; so this "if" lacks RELKIND_TOASTVALUE, and the
 		 * child toast table is handled below.)
 		 */
-		if (dopt->binary_upgrade &&
+		else if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
 			 tbinfo->relkind == RELKIND_MATVIEW))
 		{
@@ -17390,6 +17410,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	PQExpBuffer query = createPQExpBuffer();
 	PQExpBuffer delqry = createPQExpBuffer();
 	char	   *qseqname;
+	bool	   global_temp_seq = false;
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
@@ -17399,9 +17420,12 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
-						  "FROM pg_catalog.pg_sequence "
-						  "WHERE seqrelid = '%u'::oid",
+						  "seqcache, seqcycle, "
+						  "c.relpersistence "
+						  "FROM pg_catalog.pg_sequence s, "
+						  "pg_catalog.pg_class c "
+						  "WHERE seqrelid = '%u'::oid "
+						  "and s.seqrelid = c.oid",
 						  tbinfo->dobj.catId.oid);
 	}
 	else if (fout->remoteVersion >= 80400)
@@ -17446,6 +17470,9 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
 
+	if (fout->remoteVersion >= 140000)
+		global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
 	if (strcmp(seqtype, "smallint") == 0)
@@ -17523,9 +17550,13 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	}
 	else
 	{
-		appendPQExpBuffer(query,
-						  "CREATE SEQUENCE %s\n",
-						  fmtQualifiedDumpable(tbinfo));
+		appendPQExpBuffer(query, "CREATE ");
+
+		if (global_temp_seq)
+			appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+		appendPQExpBuffer(query, "SEQUENCE %s\n",
+								  fmtQualifiedDumpable(tbinfo));
 
 		if (strcmp(seqtype, "bigint") != 0)
 			appendPQExpBuffer(query, "    AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index ad5f3919956..f3819860096 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -88,7 +88,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -178,7 +178,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 5d9a26cf822..2de11d5d707 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -447,8 +449,17 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
 			 "         ON c.relnamespace = n.oid "
 			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
+			 CppAsString2(RELKIND_MATVIEW) ") AND ");
+
+	if (skip_gtt)
+	{
+		/* exclude global temp tables */
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+			"    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND ");
+	}
+
 	/* exclude possible orphaned temp tables */
+	snprintf(query + strlen(query), sizeof(query) - strlen(query),
 			 "    ((n.nspname !~ '^pg_temp_' AND "
 			 "      n.nspname !~ '^pg_toast_temp_' AND "
 			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 3628bd74a7b..bbb9b5ea13d 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -407,7 +407,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -645,7 +645,10 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+			/* exclude global temp tables */
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -656,7 +659,10 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+		/* exclude global temp tables */
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index ca0795f68ff..018a2effd4b 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -389,7 +389,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index ea4ca5c05c2..68d04e554ee 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -4066,7 +4066,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ecae9df8eda..d226b2dfef9 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1059,6 +1059,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2542,6 +2544,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2755,6 +2760,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE", "SEQUENCE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 6ce480b49c1..dda3f3c5a60 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -85,7 +85,7 @@ extern Oid	heap_create_with_catalog(const char *relname,
 
 extern void heap_drop_with_catalog(Oid relid);
 
-extern void heap_truncate(List *relids);
+extern void heap_truncate(List *relids, bool is_global_temp);
 
 extern void heap_truncate_one_rel(Relation rel);
 
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 008f723e104..875b1003899 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -157,6 +157,7 @@ extern void reindex_index(Oid indexId, bool skip_constraint_checks,
 #define REINDEX_REL_CHECK_CONSTRAINTS		0x04
 #define REINDEX_REL_FORCE_INDEXES_UNLOGGED	0x08
 #define REINDEX_REL_FORCE_INDEXES_PERMANENT 0x10
+#define REINDEX_REL_PROCESS_GLOBAL_TEMP		0x20
 
 extern bool reindex_relation(Oid relid, int flags, ReindexParams *params);
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index fef9945ed8f..9176b7dcc07 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -172,6 +172,7 @@ DECLARE_INDEX(pg_class_tblspc_relfilenode_index, 3455, ClassTblspcRelfilenodeInd
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index d068d6532ec..fd5089388f2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5735,6 +5735,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '9874',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '9875',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '9876',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '9877',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 0ab32b44e91..92e9f8ba485 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,7 +22,7 @@
 /* GUC variables */
 extern int	wal_skip_threshold;
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationPreTruncate(Relation rel);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 00000000000..8a3d9558712
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,45 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern int		vacuum_gtt_defer_check_age;
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Oid relid,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void force_enable_gtt_index(Relation index);
+extern void gtt_fix_index_backend_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index 40544dd4c70..7b66d808fc5 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 336549cc5f0..3e8167134b7 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -86,7 +86,7 @@ extern void find_composite_type_dependencies(Oid typeOid,
 
 extern void check_of_type(HeapTuple typetuple);
 
-extern void register_on_commit_action(Oid relid, OnCommitAction action);
+extern void register_on_commit_action(Oid relid, OnCommitAction action, bool is_gloal_temp);
 extern void remove_on_commit_action(Oid relid);
 
 extern void PreCommit_on_commit_actions(void);
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 8336c2c5a29..bddcfe7256d 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index c86ccdaf608..6b395551c1d 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -399,6 +399,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index a8f052e4845..4b4ed1a13aa 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -189,6 +189,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_SHARED_TIDBITMAP,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_PER_XACT_PREDICATE_LIST,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index be67d8a8616..e2f8bb5162d 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -157,6 +157,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId backend_gtt_frozenxid;	/* backend level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index b01fa52139a..8efffa55ac5 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -94,4 +94,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index aa18d304ac0..524c9d7de3f 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -288,6 +288,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 b4faa1c1238..a74558a8383 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	SMgrRelation rd_smgr;		/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -326,6 +326,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -608,11 +609,13 @@ RelationGetSmgr(Relation rel)
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -620,6 +623,7 @@ RelationGetSmgr(Relation rel)
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -632,6 +636,30 @@ RelationGetSmgr(Relation rel)
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP_ON_CURRENT_SESSION
+ *		Test a rel is either local temp relation of this session
+ *		or global temp relation.
+ */
+#define RELATION_IS_TEMP_ON_CURRENT_SESSION(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is local temp relation or global temporary relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RelpersistenceTsTemp
+ *		Test a relpersistence is local temp relation or global temporary relation.
+ */
+#define RelpersistenceTsTemp(relpersistence) \
+	(relpersistence == RELPERSISTENCE_TEMP || \
+	 relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -677,6 +705,19 @@ RelationGetSmgr(Relation rel)
 	 (relation)->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&	\
 	 !IsCatalogRelation(relation))
 
+/* For global temporary table */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/* Get on commit clause value only for global temporary table */
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+/* Get relpersistence for relation */
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
-- 
2.30.1 (Apple Git-130)

0001-gtt-v56-reademe.patchapplication/octet-stream; name=0001-gtt-v56-reademe.patchDownload
diff --git a/README.gtt.txt b/README.gtt.txt
new file mode 100644
index 00000000000..d181df9acd7
--- /dev/null
+++ b/README.gtt.txt
@@ -0,0 +1,172 @@
+Global Temporary Table(GTT)
+=========================================
+
+Feature description
+-----------------------------------------
+
+Previously, temporary tables are defined once and automatically
+exist (starting with empty contents) in every session before using them.
+
+The temporary table implementation in PostgreSQL, known as Local temp tables(LTT),
+did not fully comply with the SQL standard. This version added the support of
+Global Temporary Table .
+
+The metadata of Global Temporary Table is persistent and shared among sessions.
+The data stored in the Global temporary table is independent of sessions. This
+means, when a session creates a Global Temporary Table and writes some data.
+Other sessions cannot see those data, but they have an empty Global Temporary
+Table with same schema.
+
+Like local temporary table, Global Temporary Table supports ON COMMIT PRESERVE ROWS
+or ON COMMIT DELETE ROWS clause, so that data in the temporary table can be
+cleaned up or preserved automatically when a session exits or a transaction COMMITs.
+
+Unlike Local Temporary Table, Global Temporary Table does not support
+ON COMMIT DROP clauses.
+
+In following paragraphs, we use GTT for Global Temporary Table and LTT for
+local temporary table.
+
+Main design ideas
+-----------------------------------------
+In general, GTT and LTT use the same storage and buffer design and
+implementation. The storage files for both types of temporary tables are named
+as t_backendid_relfilenode, and the local buffer is used to cache the data.
+
+The schema of GTTs is shared among sessions while their data are not. We build
+a new mechanisms to manage those non-shared data and their statistics.
+Here is the summary of changes:
+
+1) CATALOG
+GTTs store session-specific data. The storage information of GTTs'data, their
+transaction information, and their statistics are not stored in the catalog.
+
+2) STORAGE INFO & STATISTICS INFO & TRANSACTION INFO
+In order to maintain durability and availability of GTTs'session-specific data,
+their storage information, statistics, and transaction information is managed
+in a local hash table tt_storage_local_hash.
+
+3) DDL
+Currently, GTT supports almost all table'DDL except CLUSTER/VACUUM FULL.
+Part of the DDL behavior is limited by shared definitions and multiple copies of
+local data, and we added some structures to handle this.
+
+A shared hash table active_gtt_shared_hash is added to track the state of the
+GTT in a different session. This information is recorded in the hash table
+during the DDL execution of the GTT.
+
+The data stored in a GTT can only be modified or accessed by owning session.
+The statements that only modify data in a GTT do not need a high level of
+table locking. The operations making those changes include truncate GTT,
+reindex GTT, and lock GTT.
+
+4) MVCC commit log(clog) cleanup
+Each GTT in a session has its own piece of data, and they have their own
+transaction information. We set up data structures to track and maintain
+this information. The cleaning of CLOGs also needs to consider the transaction
+information of GTT.
+
+Detailed design
+-----------------------------------------
+
+1. CATALOG
+1.1 relpersistence
+define RELPERSISTENCEGLOBALTEMP 'g'
+Mark Global Temporary Table in pg_class relpersistence to 'g'. The relpersistence
+of indexes created on the GTT, sequences on GTT and toast tables on GTT are
+also set to 'g'
+
+1.2 on commit clause
+LTT's status associated with on commit DELETE ROWS and on commit PRESERVE ROWS
+is not stored in catalog. Instead, GTTs need a bool value on_commit_delete_rows
+in reloptions which is shared among sessions.
+
+1.3 gram.y
+GTT is already supported in syntax tree. We remove the warning message
+"GLOBAL is deprecated in temporary table creation" and mark
+relpersistence = RELPERSISTENCEGLOBALTEMP.
+
+2. STORAGE INFO & STATISTICS INFO & TRANSACTION INFO
+2.1. gtt_storage_local_hash
+Each backend creates a local hashtable gtt_storage_local_hash to track a GTT's
+storage file information, statistics, and transaction information.
+
+2.2 GTT storage file info track
+1) When one session inserts data into a GTT for the first time, record the
+storage info to gtt_storage_local_hash.
+2) Use beforeshmemexit to ensure that all files of session GTT are deleted when
+the session exits.
+
+2.3 statistics info
+1) relpages reltuples relallvisible relfilenode
+2) The statistics of each column from pg_statistic
+All the above information is stored in gtt_storage_local_hash.
+When doing vacuum or analyze, GTT's statistic is updated, which is used by
+the SQL planner.
+The statistics summarizes only data in the current session.
+
+2.3 transaction info track
+frozenxid minmulti from pg_class is stored to gtt_storage_local_hash.
+
+3 DDL
+3.1. active_gtt_shared_hash
+This is the hash table created in shared memory to trace the GTT files initialized
+in each session. Each hash entry contains a bitmap that records the backendid of
+the initialized GTT file. With this hash table, we know which backend/session
+is using this GTT. Such information is used during GTT's DDL operations.
+
+3.2 DROP GTT
+One GTT is allowed to be deleted when there is only one session using the table
+and the session is the current session. After holding the lock on GTT,
+active_gtt_shared_hash is checked to ensure that.
+
+3.3 ALTER GTT/DROP INDEX ON GTT
+Same as drop GTT.
+
+3.4 CREATE INDEX ON GTT
+1) create index on GTT statements build index based on local data in a session.
+2) After the index is created, record the index metadata to the catalog.
+3) Other sessions can enable or disable the local GTT index.
+
+3.5 TRUNCATE/REINDEX GTT
+The SQL truncate/reindex command open the GTT using AccessShareLock lock,
+not AccessExclusiveLock, because this command only cleans up local data and
+local buffers in current session. This allows these operations to be executed
+concurrently between sessions, unlike normal tables.
+
+3.6 LOCK GTT
+A lock GTT statement does not hold any relation lock.
+
+3.7 CLUSTER GTT/VACUUM FULL GTT
+The current version does not support.
+
+4 MVCC commit log(clog) cleanup
+
+The GTT storage file contains transaction information. Queries for GTT data rely
+on transaction information such as clog. The transaction information required by
+each session may be completely different. We need to ensure that the transaction
+information of the GTT data is not cleaned up during its lifetime and that
+transaction resources are recycled at the instance level.
+
+4.1 The session level GTT oldest frozenxid
+1) To manage all GTT transaction information, add session level oldest frozenxid
+in each session. When one GTT is created or removed, record the session level
+oldest frozenxid and store it in MyProc.
+2) When vacuum advances the database's frozenxid, session level oldest frozenxid
+should be considered. This is acquired by searching all of MyProc. This way,
+we can avoid the clog required by GTTs to be cleaned.
+
+4.2 vacuum GTT
+Users can perform vacuum over a GTT to clean up local data in the GTT.
+
+4.3 autovacuum GTT
+Autovacuum skips all GTTs, because the data in GTTs is only visible in current session.
+
+5 OTHERS
+5.1 Parallel query
+Planner does not produce parallel query plans for SQL related to GTT. Because
+GTT private data cannot be accessed across processes.
+
+5.2 WAL and Logical replication
+Like LTT, the DML on GTT does not record WAL and is not parsed or replay by
+the logical replication.
\ No newline at end of file
-- 
2.30.1 (Apple Git-130)

#334Andrew Bille
andrewbille@gmail.com
In reply to: wenjing (#333)
Re: [Proposal] Global temporary tables

Another thanks for the fix. It works for me.

But I found another crash!

On master with the v56 patches applied:

initdb -D data
pg_ctl -w -t 5 -D data -l server.log start
echo "create global temp table t(i int4); insert into t values (1); vacuum
t;" > tmp.sql
psql < tmp.sql

CREATE TABLE
INSERT 0 1
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
connection to server was lost

with following stack:
[New LWP 2192409]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Core was generated by `postgres: andrew regression [local] VACUUM
'.
Program terminated with signal SIGABRT, Aborted.
#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
50 ../sysdeps/unix/sysv/linux/raise.c: No such file or directory.
(gdb) bt
#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
#1 0x00007fb26b558859 in __GI_abort () at abort.c:79
#2 0x00005627ddd8466c in ExceptionalCondition
(conditionName=conditionName@entry=0x5627dde153d0
"TransactionIdIsNormal(relfrozenxid)", errorType=errorType@entry=0x5627ddde100b
"FailedAssertion", fileName=fileName@entry=0x5627dddfa697 "vacuum.c",
lineNumber=lineNumber@entry=1170) at assert.c:69
#3 0x00005627dda70808 in vacuum_xid_failsafe_check
(relfrozenxid=<optimized out>, relminmxid=<optimized out>) at vacuum.c:1170
#4 0x00005627dd8db7ee in lazy_check_wraparound_failsafe
(vacrel=vacrel@entry=0x5627df5c9680) at vacuumlazy.c:2607
#5 0x00005627dd8ded18 in lazy_scan_heap (vacrel=vacrel@entry=0x5627df5c9680,
params=params@entry=0x7fffb3d36100, aggressive=aggressive@entry=true) at
vacuumlazy.c:978
#6 0x00005627dd8e019a in heap_vacuum_rel (rel=0x7fb26218af70,
params=0x7fffb3d36100, bstrategy=<optimized out>) at vacuumlazy.c:644
#7 0x00005627dda70033 in table_relation_vacuum (bstrategy=<optimized out>,
params=0x7fffb3d36100, rel=0x7fb26218af70) at
../../../src/include/access/tableam.h:1678
#8 vacuum_rel (relid=16385, relation=<optimized out>,
params=params@entry=0x7fffb3d36100)
at vacuum.c:2124
#9 0x00005627dda71624 in vacuum (relations=0x5627df610598,
params=params@entry=0x7fffb3d36100, bstrategy=<optimized out>,
bstrategy@entry=0x0, isTopLevel=isTopLevel@entry=true) at vacuum.c:476
#10 0x00005627dda71eb1 in ExecVacuum (pstate=pstate@entry=0x5627df567440,
vacstmt=vacstmt@entry=0x5627df545e70, isTopLevel=isTopLevel@entry=true) at
vacuum.c:269
#11 0x00005627ddc4a8cc in standard_ProcessUtility (pstmt=0x5627df5461c0,
queryString=0x5627df545380 "vacuum t;", readOnlyTree=<optimized out>,
context=PROCESS_UTILITY_TOPLEVEL, params=0x0, queryEnv=0x0,
dest=0x5627df5462b0, qc=0x7fffb3d36470) at utility.c:858
#12 0x00005627ddc4ada1 in ProcessUtility (pstmt=pstmt@entry=0x5627df5461c0,
queryString=<optimized out>, readOnlyTree=<optimized out>,
context=context@entry=PROCESS_UTILITY_TOPLEVEL, params=<optimized out>,
queryEnv=<optimized out>, dest=0x5627df5462b0, qc=0x7fffb3d36470) at
utility.c:527
#13 0x00005627ddc4822d in PortalRunUtility (portal=portal@entry=0x5627df5a67e0,
pstmt=pstmt@entry=0x5627df5461c0, isTopLevel=isTopLevel@entry=true,
setHoldSnapshot=setHoldSnapshot@entry=false, dest=dest@entry=0x5627df5462b0,
qc=qc@entry=0x7fffb3d36470) at pquery.c:1155
#14 0x00005627ddc48551 in PortalRunMulti (portal=portal@entry=0x5627df5a67e0,
isTopLevel=isTopLevel@entry=true, setHoldSnapshot=setHoldSnapshot@entry=false,
dest=dest@entry=0x5627df5462b0, altdest=altdest@entry=0x5627df5462b0,
qc=qc@entry=0x7fffb3d36470) at pquery.c:1312
#15 0x00005627ddc4896c in PortalRun (portal=portal@entry=0x5627df5a67e0,
count=count@entry=9223372036854775807, isTopLevel=isTopLevel@entry=true,
run_once=run_once@entry=true, dest=dest@entry=0x5627df5462b0,
altdest=altdest@entry=0x5627df5462b0, qc=0x7fffb3d36470) at pquery.c:788
#16 0x00005627ddc44afb in exec_simple_query
(query_string=query_string@entry=0x5627df545380
"vacuum t;") at postgres.c:1214
#17 0x00005627ddc469df in PostgresMain (dbname=<optimized out>,
username=<optimized out>) at postgres.c:4497
#18 0x00005627ddb9fe7d in BackendRun (port=port@entry=0x5627df566580) at
postmaster.c:4560
#19 0x00005627ddba3001 in BackendStartup (port=port@entry=0x5627df566580)
at postmaster.c:4288
#20 0x00005627ddba3248 in ServerLoop () at postmaster.c:1801
#21 0x00005627ddba482a in PostmasterMain (argc=3, argv=<optimized out>) at
postmaster.c:1473
#22 0x00005627ddae4d1d in main (argc=3, argv=0x5627df53f750) at main.c:198

On Mon, Oct 18, 2021 at 7:00 PM wenjing <wjzeng2012@gmail.com> wrote:

Show quoted text

Hi Andrew
I fixed the problem, please confirm again.
Thanks

Wenjing

#335wenjing
wjzeng2012@gmail.com
In reply to: Andrew Bille (#334)
4 attachment(s)
Re: [Proposal] Global temporary tables

Andrew Bille <andrewbille@gmail.com> 于2021年10月20日周三 上午2:59写道:

Another thanks for the fix. It works for me.

But I found another crash!

This is a check code that was added this year, but it did find a problem
and I fixed it.
Please review the new code(v57) again.

Wenjing

Show quoted text

On master with the v56 patches applied:

initdb -D data
pg_ctl -w -t 5 -D data -l server.log start
echo "create global temp table t(i int4); insert into t values (1); vacuum
t;" > tmp.sql
psql < tmp.sql

CREATE TABLE
INSERT 0 1
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
connection to server was lost

with following stack:
[New LWP 2192409]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Core was generated by `postgres: andrew regression [local] VACUUM
'.
Program terminated with signal SIGABRT, Aborted.
#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
50 ../sysdeps/unix/sysv/linux/raise.c: No such file or directory.
(gdb) bt
#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
#1 0x00007fb26b558859 in __GI_abort () at abort.c:79
#2 0x00005627ddd8466c in ExceptionalCondition
(conditionName=conditionName@entry=0x5627dde153d0
"TransactionIdIsNormal(relfrozenxid)", errorType=errorType@entry=0x5627ddde100b
"FailedAssertion", fileName=fileName@entry=0x5627dddfa697 "vacuum.c",
lineNumber=lineNumber@entry=1170) at assert.c:69
#3 0x00005627dda70808 in vacuum_xid_failsafe_check
(relfrozenxid=<optimized out>, relminmxid=<optimized out>) at vacuum.c:1170
#4 0x00005627dd8db7ee in lazy_check_wraparound_failsafe
(vacrel=vacrel@entry=0x5627df5c9680) at vacuumlazy.c:2607
#5 0x00005627dd8ded18 in lazy_scan_heap (vacrel=vacrel@entry=0x5627df5c9680,
params=params@entry=0x7fffb3d36100, aggressive=aggressive@entry=true) at
vacuumlazy.c:978
#6 0x00005627dd8e019a in heap_vacuum_rel (rel=0x7fb26218af70,
params=0x7fffb3d36100, bstrategy=<optimized out>) at vacuumlazy.c:644
#7 0x00005627dda70033 in table_relation_vacuum (bstrategy=<optimized
out>, params=0x7fffb3d36100, rel=0x7fb26218af70) at
../../../src/include/access/tableam.h:1678
#8 vacuum_rel (relid=16385, relation=<optimized out>, params=params@entry=0x7fffb3d36100)
at vacuum.c:2124
#9 0x00005627dda71624 in vacuum (relations=0x5627df610598,
params=params@entry=0x7fffb3d36100, bstrategy=<optimized out>,
bstrategy@entry=0x0, isTopLevel=isTopLevel@entry=true) at vacuum.c:476
#10 0x00005627dda71eb1 in ExecVacuum (pstate=pstate@entry=0x5627df567440,
vacstmt=vacstmt@entry=0x5627df545e70, isTopLevel=isTopLevel@entry=true)
at vacuum.c:269
#11 0x00005627ddc4a8cc in standard_ProcessUtility (pstmt=0x5627df5461c0,
queryString=0x5627df545380 "vacuum t;", readOnlyTree=<optimized out>,
context=PROCESS_UTILITY_TOPLEVEL, params=0x0, queryEnv=0x0,
dest=0x5627df5462b0, qc=0x7fffb3d36470) at utility.c:858
#12 0x00005627ddc4ada1 in ProcessUtility (pstmt=pstmt@entry=0x5627df5461c0,
queryString=<optimized out>, readOnlyTree=<optimized out>,
context=context@entry=PROCESS_UTILITY_TOPLEVEL, params=<optimized out>,
queryEnv=<optimized out>, dest=0x5627df5462b0, qc=0x7fffb3d36470) at
utility.c:527
#13 0x00005627ddc4822d in PortalRunUtility (portal=portal@entry=0x5627df5a67e0,
pstmt=pstmt@entry=0x5627df5461c0, isTopLevel=isTopLevel@entry=true,
setHoldSnapshot=setHoldSnapshot@entry=false, dest=dest@entry=0x5627df5462b0,
qc=qc@entry=0x7fffb3d36470) at pquery.c:1155
#14 0x00005627ddc48551 in PortalRunMulti (portal=portal@entry=0x5627df5a67e0,
isTopLevel=isTopLevel@entry=true, setHoldSnapshot=setHoldSnapshot@entry=false,
dest=dest@entry=0x5627df5462b0, altdest=altdest@entry=0x5627df5462b0,
qc=qc@entry=0x7fffb3d36470) at pquery.c:1312
#15 0x00005627ddc4896c in PortalRun (portal=portal@entry=0x5627df5a67e0,
count=count@entry=9223372036854775807, isTopLevel=isTopLevel@entry=true,
run_once=run_once@entry=true, dest=dest@entry=0x5627df5462b0,
altdest=altdest@entry=0x5627df5462b0, qc=0x7fffb3d36470) at pquery.c:788
#16 0x00005627ddc44afb in exec_simple_query
(query_string=query_string@entry=0x5627df545380 "vacuum t;") at
postgres.c:1214
#17 0x00005627ddc469df in PostgresMain (dbname=<optimized out>,
username=<optimized out>) at postgres.c:4497
#18 0x00005627ddb9fe7d in BackendRun (port=port@entry=0x5627df566580) at
postmaster.c:4560
#19 0x00005627ddba3001 in BackendStartup (port=port@entry=0x5627df566580)
at postmaster.c:4288
#20 0x00005627ddba3248 in ServerLoop () at postmaster.c:1801
#21 0x00005627ddba482a in PostmasterMain (argc=3, argv=<optimized out>) at
postmaster.c:1473
#22 0x00005627ddae4d1d in main (argc=3, argv=0x5627df53f750) at main.c:198

On Mon, Oct 18, 2021 at 7:00 PM wenjing <wjzeng2012@gmail.com> wrote:

Hi Andrew
I fixed the problem, please confirm again.
Thanks

Wenjing

Attachments:

0002-gtt-v57-doc.patchapplication/octet-stream; name=0002-gtt-v57-doc.patchDownload
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 473a0a4aeb..e510bde8ac 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -169,32 +169,67 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     <listitem>
      <para>
       If specified, the table is created as a temporary table.
-      Temporary tables are automatically dropped at the end of a
-      session, or optionally at the end of the current transaction
-      (see <literal>ON COMMIT</literal> below).  The default
-      search_path includes the temporary schema first and so identically
-      named existing permanent tables are not chosen for new plans
+      Optionally, <literal>GLOBAL</literal> or <literal>LOCAL</literal>
+      can be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
+      They represent two types of temporary tables supported by <productname>PostgreSQL</productname>:
+      global temporary table and local temporary table. Without specified
+      GLOBAL or LOCAL, a local temporary table is created by default.
+     </para>
+
+    <para>
+     Both types of temporary tables’ data are truncated at the
+     end of a session or optionally at the end of the current transaction.
+     (see <literal>ON COMMIT</literal> below). For global temporary table,
+     its schema is reserved and reused by future sessions or transactions.
+     For local temporary table, both its data and its schema are dropped.
+    </para>
+
+    <variablelist>
+     <varlistentry>
+      <term><literal>Global Temporary Table</literal></term>
+      <listitem>
+       <para>
+        Global temporary table are defined just once and automatically exist
+        (starting with empty contents) in every session that needs them.
+        The schema definition of temporary tables is persistent and shared among sessions.
+        However, the data in temporary tables are kept private to sessions themselves,
+        even though they use same name and same schema.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>Local Temporary Table</literal></term>
+     <listitem>
+     <para>
+      Local temporary table are automatically dropped at the end of a
+      session (include schema and data). Future sessions need to create
+      their own temporary tables when they are used.
+     </para>
+     <para>
+      The default search_path includes the temporary schema first and so
+      identically named existing permanent tables are not chosen for new plans
       while the temporary table exists, unless they are referenced
       with schema-qualified names. Any indexes created on a temporary
       table are automatically temporary as well.
      </para>
+     </listitem>
+     </varlistentry>
+    </variablelist>
 
-     <para>
-      The <link linkend="autovacuum">autovacuum daemon</link> cannot
-      access and therefore cannot vacuum or analyze temporary tables.
-      For this reason, appropriate vacuum and analyze operations should be
-      performed via session SQL commands.  For example, if a temporary
-      table is going to be used in complex queries, it is wise to run
-      <command>ANALYZE</command> on the temporary table after it is populated.
-     </para>
+    <para>
+     The <link linkend="autovacuum">autovacuum daemon</link> cannot
+     access and therefore cannot vacuum or analyze temporary tables.
+     For this reason, appropriate vacuum and analyze operations should be
+     performed via session SQL commands.  For example, if a temporary
+     table is going to be used in complex queries, it is wise to run
+     <command>ANALYZE</command> on the temporary table after it is populated.
+    </para>
+    <para>
+     The Temporary table resembles the SQL standard, but has some differences.
+     see <xref linkend="sql-createtable-compatibility"/> below.
+    </para>
 
-     <para>
-      Optionally, <literal>GLOBAL</literal> or <literal>LOCAL</literal>
-      can be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
-      This presently makes no difference in <productname>PostgreSQL</productname>
-      and is deprecated; see
-      <xref linkend="sql-createtable-compatibility"/> below.
-     </para>
     </listitem>
    </varlistentry>
 
@@ -2133,13 +2168,17 @@ CREATE TABLE cities_partdef
    <title>Temporary Tables</title>
 
    <para>
-    Although the syntax of <literal>CREATE TEMPORARY TABLE</literal>
-    resembles that of the SQL standard, the effect is not the same.  In the
-    standard,
-    temporary tables are defined just once and automatically exist (starting
-    with empty contents) in every session that needs them.
-    <productname>PostgreSQL</productname> instead
-    requires each session to issue its own <literal>CREATE TEMPORARY
+    Although the syntax of <literal>CREATE GLOBAL/LOCAL TEMPORARY TABLE</literal>
+    resembles that of the SQL standard, the effect is not the same.
+    The global temporary table follows the SQL standards while local temporary
+    table does not.
+   </para>
+
+   <para>
+    First, in the standard, both global and local temporary tables are defined just
+    once and automatically exist (starting with empty contents) in every session
+    that needs them. For local temporary tables, <productname>PostgreSQL</productname>
+    instead requires each session to issue its own <literal>CREATE LOCAL TEMPORARY
     TABLE</literal> command for each temporary table to be used.  This allows
     different sessions to use the same temporary table name for different
     purposes, whereas the standard's approach constrains all instances of a
@@ -2147,29 +2186,14 @@ CREATE TABLE cities_partdef
    </para>
 
    <para>
-    The standard's definition of the behavior of temporary tables is
-    widely ignored.  <productname>PostgreSQL</productname>'s behavior
-    on this point is similar to that of several other SQL databases.
-   </para>
-
-   <para>
-    The SQL standard also distinguishes between global and local temporary
+    Second, the SQL standard distinguishes between global and local temporary
     tables, where a local temporary table has a separate set of contents for
     each SQL module within each session, though its definition is still shared
-    across sessions.  Since <productname>PostgreSQL</productname> does not
+    across sessions. Since <productname>PostgreSQL</productname> does not
     support SQL modules, this distinction is not relevant in
     <productname>PostgreSQL</productname>.
    </para>
 
-   <para>
-    For compatibility's sake, <productname>PostgreSQL</productname> will
-    accept the <literal>GLOBAL</literal> and <literal>LOCAL</literal> keywords
-    in a temporary table declaration, but they currently have no effect.
-    Use of these keywords is discouraged, since future versions of
-    <productname>PostgreSQL</productname> might adopt a more
-    standard-compliant interpretation of their meaning.
-   </para>
-
    <para>
     The <literal>ON COMMIT</literal> clause for temporary tables
     also resembles the SQL standard, but has some differences.
@@ -2177,7 +2201,8 @@ CREATE TABLE cities_partdef
     default behavior is <literal>ON COMMIT DELETE ROWS</literal>.  However, the
     default behavior in <productname>PostgreSQL</productname> is
     <literal>ON COMMIT PRESERVE ROWS</literal>.  The <literal>ON COMMIT
-    DROP</literal> option does not exist in SQL.
+    DROP</literal> option does not exist in SQL and is not supported by
+    global temporary table.
    </para>
   </refsect2>
 
-- 
2.30.1 (Apple Git-130)

0001-gtt-v57-reademe.patchapplication/octet-stream; name=0001-gtt-v57-reademe.patchDownload
diff --git a/README.gtt.txt b/README.gtt.txt
new file mode 100644
index 00000000000..d181df9acd7
--- /dev/null
+++ b/README.gtt.txt
@@ -0,0 +1,172 @@
+Global Temporary Table(GTT)
+=========================================
+
+Feature description
+-----------------------------------------
+
+Previously, temporary tables are defined once and automatically
+exist (starting with empty contents) in every session before using them.
+
+The temporary table implementation in PostgreSQL, known as Local temp tables(LTT),
+did not fully comply with the SQL standard. This version added the support of
+Global Temporary Table .
+
+The metadata of Global Temporary Table is persistent and shared among sessions.
+The data stored in the Global temporary table is independent of sessions. This
+means, when a session creates a Global Temporary Table and writes some data.
+Other sessions cannot see those data, but they have an empty Global Temporary
+Table with same schema.
+
+Like local temporary table, Global Temporary Table supports ON COMMIT PRESERVE ROWS
+or ON COMMIT DELETE ROWS clause, so that data in the temporary table can be
+cleaned up or preserved automatically when a session exits or a transaction COMMITs.
+
+Unlike Local Temporary Table, Global Temporary Table does not support
+ON COMMIT DROP clauses.
+
+In following paragraphs, we use GTT for Global Temporary Table and LTT for
+local temporary table.
+
+Main design ideas
+-----------------------------------------
+In general, GTT and LTT use the same storage and buffer design and
+implementation. The storage files for both types of temporary tables are named
+as t_backendid_relfilenode, and the local buffer is used to cache the data.
+
+The schema of GTTs is shared among sessions while their data are not. We build
+a new mechanisms to manage those non-shared data and their statistics.
+Here is the summary of changes:
+
+1) CATALOG
+GTTs store session-specific data. The storage information of GTTs'data, their
+transaction information, and their statistics are not stored in the catalog.
+
+2) STORAGE INFO & STATISTICS INFO & TRANSACTION INFO
+In order to maintain durability and availability of GTTs'session-specific data,
+their storage information, statistics, and transaction information is managed
+in a local hash table tt_storage_local_hash.
+
+3) DDL
+Currently, GTT supports almost all table'DDL except CLUSTER/VACUUM FULL.
+Part of the DDL behavior is limited by shared definitions and multiple copies of
+local data, and we added some structures to handle this.
+
+A shared hash table active_gtt_shared_hash is added to track the state of the
+GTT in a different session. This information is recorded in the hash table
+during the DDL execution of the GTT.
+
+The data stored in a GTT can only be modified or accessed by owning session.
+The statements that only modify data in a GTT do not need a high level of
+table locking. The operations making those changes include truncate GTT,
+reindex GTT, and lock GTT.
+
+4) MVCC commit log(clog) cleanup
+Each GTT in a session has its own piece of data, and they have their own
+transaction information. We set up data structures to track and maintain
+this information. The cleaning of CLOGs also needs to consider the transaction
+information of GTT.
+
+Detailed design
+-----------------------------------------
+
+1. CATALOG
+1.1 relpersistence
+define RELPERSISTENCEGLOBALTEMP 'g'
+Mark Global Temporary Table in pg_class relpersistence to 'g'. The relpersistence
+of indexes created on the GTT, sequences on GTT and toast tables on GTT are
+also set to 'g'
+
+1.2 on commit clause
+LTT's status associated with on commit DELETE ROWS and on commit PRESERVE ROWS
+is not stored in catalog. Instead, GTTs need a bool value on_commit_delete_rows
+in reloptions which is shared among sessions.
+
+1.3 gram.y
+GTT is already supported in syntax tree. We remove the warning message
+"GLOBAL is deprecated in temporary table creation" and mark
+relpersistence = RELPERSISTENCEGLOBALTEMP.
+
+2. STORAGE INFO & STATISTICS INFO & TRANSACTION INFO
+2.1. gtt_storage_local_hash
+Each backend creates a local hashtable gtt_storage_local_hash to track a GTT's
+storage file information, statistics, and transaction information.
+
+2.2 GTT storage file info track
+1) When one session inserts data into a GTT for the first time, record the
+storage info to gtt_storage_local_hash.
+2) Use beforeshmemexit to ensure that all files of session GTT are deleted when
+the session exits.
+
+2.3 statistics info
+1) relpages reltuples relallvisible relfilenode
+2) The statistics of each column from pg_statistic
+All the above information is stored in gtt_storage_local_hash.
+When doing vacuum or analyze, GTT's statistic is updated, which is used by
+the SQL planner.
+The statistics summarizes only data in the current session.
+
+2.3 transaction info track
+frozenxid minmulti from pg_class is stored to gtt_storage_local_hash.
+
+3 DDL
+3.1. active_gtt_shared_hash
+This is the hash table created in shared memory to trace the GTT files initialized
+in each session. Each hash entry contains a bitmap that records the backendid of
+the initialized GTT file. With this hash table, we know which backend/session
+is using this GTT. Such information is used during GTT's DDL operations.
+
+3.2 DROP GTT
+One GTT is allowed to be deleted when there is only one session using the table
+and the session is the current session. After holding the lock on GTT,
+active_gtt_shared_hash is checked to ensure that.
+
+3.3 ALTER GTT/DROP INDEX ON GTT
+Same as drop GTT.
+
+3.4 CREATE INDEX ON GTT
+1) create index on GTT statements build index based on local data in a session.
+2) After the index is created, record the index metadata to the catalog.
+3) Other sessions can enable or disable the local GTT index.
+
+3.5 TRUNCATE/REINDEX GTT
+The SQL truncate/reindex command open the GTT using AccessShareLock lock,
+not AccessExclusiveLock, because this command only cleans up local data and
+local buffers in current session. This allows these operations to be executed
+concurrently between sessions, unlike normal tables.
+
+3.6 LOCK GTT
+A lock GTT statement does not hold any relation lock.
+
+3.7 CLUSTER GTT/VACUUM FULL GTT
+The current version does not support.
+
+4 MVCC commit log(clog) cleanup
+
+The GTT storage file contains transaction information. Queries for GTT data rely
+on transaction information such as clog. The transaction information required by
+each session may be completely different. We need to ensure that the transaction
+information of the GTT data is not cleaned up during its lifetime and that
+transaction resources are recycled at the instance level.
+
+4.1 The session level GTT oldest frozenxid
+1) To manage all GTT transaction information, add session level oldest frozenxid
+in each session. When one GTT is created or removed, record the session level
+oldest frozenxid and store it in MyProc.
+2) When vacuum advances the database's frozenxid, session level oldest frozenxid
+should be considered. This is acquired by searching all of MyProc. This way,
+we can avoid the clog required by GTTs to be cleaned.
+
+4.2 vacuum GTT
+Users can perform vacuum over a GTT to clean up local data in the GTT.
+
+4.3 autovacuum GTT
+Autovacuum skips all GTTs, because the data in GTTs is only visible in current session.
+
+5 OTHERS
+5.1 Parallel query
+Planner does not produce parallel query plans for SQL related to GTT. Because
+GTT private data cannot be accessed across processes.
+
+5.2 WAL and Logical replication
+Like LTT, the DML on GTT does not record WAL and is not parsed or replay by
+the logical replication.
\ No newline at end of file
-- 
2.30.1 (Apple Git-130)

0004-gtt-v57-regress.patchapplication/octet-stream; name=0004-gtt-v57-regress.patchDownload
diff --git a/src/test/isolation/expected/gtt-sequence.out b/src/test/isolation/expected/gtt-sequence.out
new file mode 100644
index 00000000000..31db2ebd423
--- /dev/null
+++ b/src/test/isolation/expected/gtt-sequence.out
@@ -0,0 +1,48 @@
+unused step name: s1_seq_restart
+Parsed test spec with 2 sessions
+
+starting permutation: s1_seq_next s2_seq_next s1_seq_next
+step s1_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      1
+(1 row)
+
+step s2_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      1
+(1 row)
+
+step s1_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      2
+(1 row)
+
+
+starting permutation: s1_select s2_select s1_insert s2_insert s1_select s2_select
+step s1_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+(0 rows)
+
+step s2_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+(0 rows)
+
+step s1_insert: insert into gtt_with_seq values(1);
+step s2_insert: insert into gtt_with_seq values(10);
+step s1_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+ 1| 3
+(1 row)
+
+step s2_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+10| 1
+(1 row)
+
diff --git a/src/test/isolation/expected/gtt-table.out b/src/test/isolation/expected/gtt-table.out
new file mode 100644
index 00000000000..5825773aa12
--- /dev/null
+++ b/src/test/isolation/expected/gtt-table.out
@@ -0,0 +1,675 @@
+Parsed test spec with 3 sessions
+
+starting permutation: s1_update_d
+step s1_update_d: update gtt_on_commit_delete_row set b = 'update'
+
+starting permutation: s1_select_d s1_insert_d s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_begin s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+1|test1
+(1 row)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_begin s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+1|test1
+(1 row)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_select_d s1_truncate_d s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_begin s1_insert_d s1_select_d s1_truncate_d s1_select_d s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_begin s1_insert_d s1_select_d s1_truncate_d s1_select_d s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_insert_d s1_select_d s1_begin s1_insert_d s1_select_d s1_save_1 s1_truncate_d s1_insert_d s1_select_d s1_save_2 s1_truncate_d s1_insert_d s1_select_d s1_save_3 s1_rollback_to_save_2 s1_select_d s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_save_1: SAVEPOINT save1
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_save_2: SAVEPOINT save2
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+4|test1
+(1 row)
+
+step s1_save_3: SAVEPOINT save3
+step s1_rollback_to_save_2: rollback to savepoint save2
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+5|test1
+(2 rows)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_insert_d s1_select_d s1_begin s1_insert_d s1_select_d s1_save_1 s1_truncate_d s1_insert_d s1_select_d s1_save_2 s1_truncate_d s1_insert_d s1_select_d s1_save_3 s1_rollback_to_save_2 s1_select_d s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_save_1: SAVEPOINT save1
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_save_2: SAVEPOINT save2
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+4|test1
+(1 row)
+
+step s1_save_3: SAVEPOINT save3
+step s1_rollback_to_save_2: rollback to savepoint save2
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+5|test1
+(2 rows)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_update_p
+step s2_update_p: update gtt_on_commit_preserve_row set b = 'update'
+
+starting permutation: s2_select_p s2_insert_p s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_begin s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_begin s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_select_p s2_insert_p s2_select_p s2_truncate_p s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_select_p s2_insert_p s2_begin s2_insert_p s2_select_p s2_truncate_p s2_select_p s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_insert_p s2_begin s2_insert_p s2_select_p s2_truncate_p s2_select_p s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_insert_p s2_select_p s2_begin s2_insert_p s2_select_p s2_save_1 s2_truncate_p s2_insert_p s2_select_p s2_save_2 s2_truncate_p s2_insert_p s2_select_p s2_save_3 s2_rollback_to_save_2 s2_select_p s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_save_1: SAVEPOINT save1
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_save_2: SAVEPOINT save2
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+4|test10
+(1 row)
+
+step s2_save_3: SAVEPOINT save3
+step s2_rollback_to_save_2: rollback to savepoint save2
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+
+starting permutation: s2_insert_p s2_select_p s2_begin s2_insert_p s2_select_p s2_save_1 s2_truncate_p s2_insert_p s2_select_p s2_save_2 s2_truncate_p s2_insert_p s2_select_p s2_save_3 s2_rollback_to_save_2 s2_select_p s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_save_1: SAVEPOINT save1
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_save_2: SAVEPOINT save2
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+4|test10
+(1 row)
+
+step s2_save_3: SAVEPOINT save3
+step s2_rollback_to_save_2: rollback to savepoint save2
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_insert_p s2_insert_p s1_select_p s2_select_p
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_truncate_p s2_truncate_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_begin s1_insert_d s2_insert_d s1_truncate_d s2_insert_d s1_commit
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s2_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s2_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_commit: commit
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_reindex_p s2_reindex_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_reindex_p: reindex table gtt_on_commit_preserve_row
+step s2_reindex_p: reindex table gtt_on_commit_preserve_row
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_reindex_i_p s2_reindex_i_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_reindex_i_p: reindex index gtt_on_commit_preserve_row_pkey
+step s2_reindex_i_p: reindex index gtt_on_commit_preserve_row_pkey
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_insert_c s3_create_c s3_insert_c s1_insert_c s1_analyze_c s2_analyze_c s3_analyze_c s1_select_c s2_select_c s3_select_c
+step s2_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s3_create_c: create unique index idx_temp_table_a on gtt_test_createindex(a)
+step s3_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s1_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s1_analyze_c: analyze gtt_test_createindex
+step s2_analyze_c: analyze gtt_test_createindex
+step s3_analyze_c: analyze gtt_test_createindex
+step s1_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                                               
+---------------------------------------------------------
+Index Scan using idx_temp_table_a on gtt_test_createindex
+  Index Cond: (a = 1)                                    
+(2 rows)
+
+step s2_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                      
+--------------------------------
+Seq Scan on gtt_test_createindex
+  Filter: (a = 1)               
+(2 rows)
+
+step s3_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                                               
+---------------------------------------------------------
+Index Scan using idx_temp_table_a on gtt_test_createindex
+  Index Cond: (a = 1)                                    
+(2 rows)
+
+
+starting permutation: s1_begin s2_begin s1_lock_p s2_lock_p s1_truncate_p s2_truncate_p s1_insert_p s2_insert_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_lock_p: LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE
+step s2_lock_p: LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE
+step s1_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index f4c01006fc1..746a17f824c 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -96,3 +96,5 @@ test: plpgsql-toast
 test: truncate-conflict
 test: serializable-parallel
 test: serializable-parallel-2
+test: gtt-sequence
+test: gtt-table
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 88594a3cb5d..ec643aadb5f 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -80,9 +80,30 @@ disconnect_atexit(void)
 {
 	int			i;
 
-	for (i = 0; i < nconns; i++)
+	for (i = 1; i < nconns; i++)
 		if (conns[i].conn)
 			PQfinish(conns[i].conn);
+
+	if (parseresult.destroy)
+	{
+		PGresult   *res;
+
+		res = PQexec(conns[0].conn, parseresult.destroy);
+		if (PQresultStatus(res) == PGRES_TUPLES_OK)
+		{
+			printResultSet(res);
+		}
+		else if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		{
+			fprintf(stderr, "destroy failed: %s",
+					PQerrorMessage(conns[0].conn));
+			/* don't exit on teardown failure */
+		}
+		PQclear(res);
+	}
+
+	if (conns[0].conn)
+		PQfinish(conns[0].conn);
 }
 
 int
@@ -214,6 +235,24 @@ main(int argc, char **argv)
 	PQclear(res);
 	termPQExpBuffer(&wait_query);
 
+	if (parseresult.initialize)
+	{
+		PGresult   *res;
+
+		res = PQexec(conns[0].conn, parseresult.initialize);
+		if (PQresultStatus(res) == PGRES_TUPLES_OK)
+		{
+			printResultSet(res);
+		}
+		else if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		{
+			fprintf(stderr, "initialize failed: %s",
+					PQerrorMessage(conns[0].conn));
+			/* don't exit on teardown failure */
+		}
+		PQclear(res);
+	}
+
 	/*
 	 * Run the permutations specified in the spec, or all if none were
 	 * explicitly specified.
diff --git a/src/test/isolation/isolationtester.h b/src/test/isolation/isolationtester.h
index 5f300219c20..b5a29893da9 100644
--- a/src/test/isolation/isolationtester.h
+++ b/src/test/isolation/isolationtester.h
@@ -81,6 +81,8 @@ typedef struct
 	int			nsessions;
 	Permutation **permutations;
 	int			npermutations;
+	char	   *initialize;
+	char	   *destroy;
 } TestSpec;
 
 extern TestSpec parseresult;
diff --git a/src/test/isolation/specparse.y b/src/test/isolation/specparse.y
index c25aa1a73fa..2784f758ed9 100644
--- a/src/test/isolation/specparse.y
+++ b/src/test/isolation/specparse.y
@@ -39,7 +39,7 @@ TestSpec		parseresult;			/* result of parsing is left here */
 }
 
 %type <ptr_list> setup_list
-%type <str>  opt_setup opt_teardown
+%type <str>  opt_setup opt_teardown opt_initialize opt_destroy
 %type <str> setup
 %type <ptr_list> step_list session_list permutation_list opt_permutation_list
 %type <ptr_list> permutation_step_list blocker_list
@@ -51,23 +51,27 @@ TestSpec		parseresult;			/* result of parsing is left here */
 
 %token <str> sqlblock identifier
 %token <integer> INTEGER
-%token NOTICES PERMUTATION SESSION SETUP STEP TEARDOWN TEST
+%token NOTICES PERMUTATION SESSION SETUP STEP TEARDOWN TEST INITIALIZE DESTROY
 
 %%
 
 TestSpec:
+			opt_initialize
 			setup_list
 			opt_teardown
+			opt_destroy
 			session_list
 			opt_permutation_list
 			{
-				parseresult.setupsqls = (char **) $1.elements;
-				parseresult.nsetupsqls = $1.nelements;
-				parseresult.teardownsql = $2;
-				parseresult.sessions = (Session **) $3.elements;
-				parseresult.nsessions = $3.nelements;
-				parseresult.permutations = (Permutation **) $4.elements;
-				parseresult.npermutations = $4.nelements;
+				parseresult.setupsqls = (char **) $2.elements;
+				parseresult.nsetupsqls = $2.nelements;
+				parseresult.teardownsql = $3;
+				parseresult.sessions = (Session **) $5.elements;
+				parseresult.nsessions = $5.nelements;
+				parseresult.permutations = (Permutation **) $6.elements;
+				parseresult.npermutations = $6.nelements;
+				parseresult.initialize = $1;
+				parseresult.destroy = $4;
 			}
 		;
 
@@ -100,6 +104,16 @@ opt_teardown:
 			| TEARDOWN sqlblock	{ $$ = $2; }
 		;
 
+opt_initialize:
+			/* EMPTY */			{ $$ = NULL; }
+			| INITIALIZE sqlblock	{ $$ = $2; }
+		;
+
+opt_destroy:
+			/* EMPTY */			{ $$ = NULL; }
+			| DESTROY sqlblock	{ $$ = $2; }
+		;
+
 session_list:
 			session_list session
 			{
diff --git a/src/test/isolation/specs/gtt-sequence.spec b/src/test/isolation/specs/gtt-sequence.spec
new file mode 100644
index 00000000000..88eece45e29
--- /dev/null
+++ b/src/test/isolation/specs/gtt-sequence.spec
@@ -0,0 +1,39 @@
+# Tests for global temporary relations
+
+initialize
+{
+  CREATE GLOBAL TEMPORARY TABLE if not exists gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+}
+
+destroy
+{
+  /* wait other backend exit */
+  select pg_sleep(1);
+
+  DROP TABLE gtt_with_seq;
+}
+
+# Session 1
+session "s1"
+step "s1_seq_next" { select nextval('gtt_with_seq_c2_seq'); }
+step "s1_seq_restart" { alter sequence gtt_with_seq_c2_seq RESTART; }
+step "s1_insert" { insert into gtt_with_seq values(1); }
+step "s1_select" { select * from gtt_with_seq order by c1,c2; }
+teardown
+{
+  TRUNCATE gtt_with_seq;
+}
+
+# Session 2
+session "s2"
+step "s2_seq_next" { select nextval('gtt_with_seq_c2_seq'); }
+step "s2_insert" { insert into gtt_with_seq values(10); }
+step "s2_select" { select * from gtt_with_seq order by c1,c2; }
+teardown
+{
+  TRUNCATE gtt_with_seq RESTART IDENTITY;
+}
+
+permutation "s1_seq_next" "s2_seq_next" "s1_seq_next"
+permutation "s1_select" "s2_select" "s1_insert" "s2_insert" "s1_select" "s2_select"
+
diff --git a/src/test/isolation/specs/gtt-table.spec b/src/test/isolation/specs/gtt-table.spec
new file mode 100644
index 00000000000..e0396b21ef0
--- /dev/null
+++ b/src/test/isolation/specs/gtt-table.spec
@@ -0,0 +1,135 @@
+# Tests for global temporary relations
+
+initialize
+{
+  create global temp table gtt_on_commit_delete_row(a bigserial primary key, b text) on commit delete rows;
+  create global temp table gtt_on_commit_preserve_row(a bigserial primary key, b text) on commit preserve rows;
+  create global temp table gtt_test_createindex(a int, b char(1000)) on commit preserve rows;
+}
+
+destroy
+{
+  /* wait other backend exit */
+  select pg_sleep(1);
+
+  DROP TABLE gtt_on_commit_delete_row;
+  DROP TABLE gtt_on_commit_preserve_row;
+  DROP TABLE gtt_test_createindex;
+}
+
+# Session 1
+session "s1"
+step "s1_begin" {begin}
+step "s1_commit" {commit}
+step "s1_rollback" {rollback}
+step "s1_insert_d" {insert into gtt_on_commit_delete_row (b) values('test1')}
+step "s1_select_d" {select a,b from gtt_on_commit_delete_row order by a,b}
+step "s1_insert_p" {insert into gtt_on_commit_preserve_row (b) values('test20')}
+step "s1_select_p" {select a,b from gtt_on_commit_preserve_row order by a,b}
+step "s1_truncate_d" {truncate gtt_on_commit_delete_row}
+step "s1_truncate_p" {truncate gtt_on_commit_preserve_row}
+step "s1_lock_p" {LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE}
+step "s1_update_d" {update gtt_on_commit_delete_row set b = 'update'}
+step "s1_save_1" {SAVEPOINT save1}
+step "s1_save_2" {SAVEPOINT save2}
+step "s1_save_3" {SAVEPOINT save3}
+step "s1_rollback_to_save_2" {rollback to savepoint save2}
+step "s1_reindex_p" {reindex table gtt_on_commit_preserve_row}
+step "s1_reindex_i_p" {reindex index gtt_on_commit_preserve_row_pkey}
+step "s1_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s1_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s1_analyze_c" {analyze gtt_test_createindex}
+teardown
+{
+  TRUNCATE gtt_on_commit_delete_row RESTART IDENTITY;
+  TRUNCATE gtt_on_commit_preserve_row RESTART IDENTITY;
+}
+
+# Session 2
+session "s2"
+step "s2_begin" {begin}
+step "s2_commit" {commit}
+step "s2_rollback" {rollback}
+step "s2_insert_p" {insert into gtt_on_commit_preserve_row (b) values('test10')}
+step "s2_select_p" {select a,b from gtt_on_commit_preserve_row order by a,b}
+step "s2_insert_d" {insert into gtt_on_commit_delete_row (b) values('test1')}
+step "s2_truncate_p" {truncate gtt_on_commit_preserve_row}
+step "s2_update_p" {update gtt_on_commit_preserve_row set b = 'update'}
+step "s2_lock_p" {LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE}
+step "s2_save_1" {SAVEPOINT save1}
+step "s2_save_2" {SAVEPOINT save2}
+step "s2_save_3" {SAVEPOINT save3}
+step "s2_rollback_to_save_2" {rollback to savepoint save2}
+step "s2_reindex_p" {reindex table gtt_on_commit_preserve_row}
+step "s2_reindex_i_p" {reindex index gtt_on_commit_preserve_row_pkey}
+step "s2_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s2_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s2_analyze_c" {analyze gtt_test_createindex}
+teardown
+{
+  TRUNCATE gtt_on_commit_delete_row RESTART IDENTITY;
+  TRUNCATE gtt_on_commit_preserve_row RESTART IDENTITY;
+}
+
+session "s3"
+step "s3_create_c" {create unique index idx_temp_table_a on gtt_test_createindex(a)}
+step "s3_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s3_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s3_analyze_c" {analyze gtt_test_createindex}
+
+
+#
+# test on commit delete temp table
+#
+
+# test update empty temp table
+permutation "s1_update_d"
+# test insert into temp table
+permutation "s1_select_d" "s1_insert_d" "s1_select_d"
+# test temp table in transaction(commit)
+permutation "s1_select_d" "s1_begin"    "s1_insert_d" "s1_select_d"   "s1_commit"   "s1_select_d" 
+# test temp table in transaction(rollback)
+permutation "s1_select_d" "s1_begin"    "s1_insert_d" "s1_select_d"   "s1_rollback" "s1_select_d" 
+# test truncate
+permutation "s1_select_d" "s1_insert_d" "s1_select_d" "s1_truncate_d" "s1_select_d"
+# test truncate in transaction block
+permutation "s1_select_d" "s1_insert_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_truncate_d" "s1_select_d"   "s1_insert_d" "s1_select_d" "s1_commit"   "s1_select_d" 
+permutation "s1_select_d" "s1_insert_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_truncate_d" "s1_select_d"   "s1_insert_d" "s1_select_d" "s1_rollback" "s1_select_d"
+# test temp table with subtransaction or savepoint
+permutation "s1_insert_d" "s1_select_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_save_1"     "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_2"   "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_3" "s1_rollback_to_save_2" "s1_select_d" "s1_insert_d" "s1_select_d" "s1_commit" "s1_select_d"
+permutation "s1_insert_d" "s1_select_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_save_1"     "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_2"   "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_3" "s1_rollback_to_save_2" "s1_select_d" "s1_insert_d" "s1_select_d" "s1_rollback" "s1_select_d"
+
+#
+# test on commit preserve table
+#
+
+# same as test on commit delete temp table
+permutation "s2_update_p"
+permutation "s2_select_p" "s2_insert_p" "s2_select_p"
+permutation "s2_select_p" "s2_begin"    "s2_insert_p" "s2_select_p"   "s2_commit"   "s2_select_p"
+permutation "s2_select_p" "s2_begin"    "s2_insert_p" "s2_select_p"   "s2_rollback" "s2_select_p"
+permutation "s2_select_p" "s2_insert_p" "s2_select_p" "s2_truncate_p" "s2_select_p"
+permutation "s2_select_p" "s2_insert_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_truncate_p" "s2_select_p"   "s2_insert_p" "s2_select_p" "s2_commit"   "s2_select_p" 
+permutation "s2_select_p" "s2_insert_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_truncate_p" "s2_select_p"   "s2_insert_p" "s2_select_p" "s2_rollback" "s2_select_p" 
+permutation "s2_insert_p" "s2_select_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_save_1"     "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_2"   "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_3" "s2_rollback_to_save_2" "s2_select_p" "s2_insert_p" "s2_select_p" "s2_commit" "s2_select_p"
+permutation "s2_insert_p" "s2_select_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_save_1"     "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_2"   "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_3" "s2_rollback_to_save_2" "s2_select_p" "s2_insert_p" "s2_select_p" "s2_rollback" "s2_select_p"
+
+#
+# test concurrent operation on temp table
+#
+
+#  test concurrent read
+permutation "s1_insert_p" "s2_insert_p" "s1_select_p" "s2_select_p" 
+#  test concurrent truncate
+permutation "s1_begin" "s2_begin"    "s1_insert_p" "s2_insert_p"   "s1_truncate_p" "s2_truncate_p"  "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+permutation "s1_begin" "s1_insert_d" "s2_insert_d" "s1_truncate_d" "s2_insert_d"   "s1_commit" 
+#  test concurrent reindex table
+permutation "s1_begin" "s2_begin" "s1_insert_p" "s2_insert_p" "s1_reindex_p"   "s2_reindex_p"   "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+#  test concurrent reindex index
+permutation "s1_begin" "s2_begin" "s1_insert_p" "s2_insert_p" "s1_reindex_i_p" "s2_reindex_i_p" "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+
+# test create index
+permutation "s2_insert_c" "s3_create_c" "s3_insert_c" "s1_insert_c" "s1_analyze_c" "s2_analyze_c" "s3_analyze_c" "s1_select_c" "s2_select_c" "s3_select_c"
+
+# test lock gtt
+permutation "s1_begin" "s2_begin" "s1_lock_p" "s2_lock_p" "s1_truncate_p" "s2_truncate_p" "s1_insert_p" "s2_insert_p" "s1_commit" "s2_commit" "s1_select_p" "s2_select_p"
diff --git a/src/test/isolation/specscanner.l b/src/test/isolation/specscanner.l
index d9fa6a5b54a..697db975479 100644
--- a/src/test/isolation/specscanner.l
+++ b/src/test/isolation/specscanner.l
@@ -67,6 +67,8 @@ session			{ return SESSION; }
 setup			{ return SETUP; }
 step			{ return STEP; }
 teardown		{ return TEARDOWN; }
+initialize		 { return INITIALIZE; }
+destroy			 { return DESTROY; }
 
  /* Whitespace and comments */
 [\n]			{ yyline++; }
diff --git a/src/test/regress/expected/global_temporary_table.out b/src/test/regress/expected/global_temporary_table.out
new file mode 100644
index 00000000000..094ad04ce90
--- /dev/null
+++ b/src/test/regress/expected/global_temporary_table.out
@@ -0,0 +1,515 @@
+--
+-- GLobal emparary table test case 
+--
+CREATE SCHEMA IF NOT EXISTS global_temporary_table;
+set search_path=global_temporary_table,sys;
+--
+--  test create global temp table basic syntax
+--
+create global temp table gtt_on_commit_default(a int primary key, b text);
+create global temp table gtt_on_commit_delete(a int primary key, b text) on commit delete rows;
+create global temp table gtt_on_commit_delete2(n int) with (on_commit_delete_rows='true');
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit PRESERVE rows;
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt_test_createindex(c0 tsvector,c1 varchar(100), c2 int);
+--
+-- test DML on global temp table
+--
+-- update empty temp table
+update gtt_on_commit_delete set b ='test';
+begin;
+insert into gtt_on_commit_delete values (1);
+update gtt_on_commit_delete set b ='test';
+-- should 1 row
+select * from gtt_on_commit_delete;
+ a |  b   
+---+------
+ 1 | test
+(1 row)
+
+commit;
+-- data delete after transaction commit
+-- should 0 row
+select * from gtt_on_commit_delete;
+ a | b 
+---+---
+(0 rows)
+
+-- update empty temp table
+update gtt_on_commit_preserve set b ='test';
+insert into gtt_on_commit_preserve values (2);
+begin;
+insert into gtt_on_commit_preserve values (3);
+update gtt_on_commit_preserve set b ='test';
+-- should 2 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 2 | test
+ 3 | test
+(2 rows)
+
+delete from gtt_on_commit_preserve where a=2;
+commit;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 3 | test
+(1 row)
+
+begin;
+insert into gtt_on_commit_preserve values (4);
+-- temp table support truncate;
+truncate gtt_on_commit_preserve;
+select * from gtt_on_commit_preserve order by a;
+ a | b 
+---+---
+(0 rows)
+
+rollback;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 3 | test
+(1 row)
+
+--
+-- test unsupported global temp partition table
+--
+-- should fail
+CREATE global temp TABLE global_temp_partition_01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+on commit delete rows;
+ERROR:  Only support global temporary regular table.
+CREATE TABLE regular_partition_01_2019 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+);
+CREATE TABLE regular_partition01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time);
+-- should fail
+CREATE global temp TABLE temp_partition01_2018
+PARTITION OF regular_partition01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+ERROR:  Not support global temporary partition table or inherit table.
+CREATE global temp TABLE global_temp_partition_01_2021 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+)on commit delete rows;
+-- should fail
+ALTER TABLE regular_partition01 ATTACH PARTITION global_temp_partition_01_2021 FOR VALUES FROM ('2021-01-01 00:00:00') TO ('2022-01-01 00:00:00');
+ERROR:  cannot attach a global temporary relation as partition of permanent relation "regular_partition01"
+--
+-- test unsupported inherit table
+--
+create table 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 inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent);
+ERROR:  Not support global temporary partition table or inherit table.
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent_global_temp) on commit delete rows;
+ERROR:  Not support global temporary partition table or inherit table.
+--
+-- test DDL on global temp table
+--
+create index gtt_idx_1 on gtt_test_createindex using gin (c0);
+create index gtt_idx_2 on gtt_test_createindex using gist (c0);
+create index gtt_idx_3 on gtt_test_createindex using hash (c2);
+alter table gtt_test_rename rename to gtt_test_new;
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+create index CONCURRENTLY idx_b on gtt_on_commit_default (b);
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+insert into gtt_on_commit_default values(1,'test');
+create global temp table gtt_test_alter1 (a int primary key,b text);
+alter table gtt_test_alter1 alter a type varchar;
+-- should fail
+alter table gtt_on_commit_default alter a type varchar;
+ERROR:  Only support alter global temporary table in an empty context.
+HINT:  Please create a new connection and execute ALTER TABLE on the new connection.
+-- should fail
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+ERROR:  not support cluster global temporary table yet
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- should fail
+alter table gtt_on_commit_default SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temporary table
+-- should fail
+alter table gtt_on_commit_default set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table
+-- should fail
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- should fail
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- should fail
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit drop;
+ERROR:  global temporary table not support on commit drop clause
+-- should fail
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  could not create global temporary table with on commit and with clause at same time
+-- should fail
+CREATE MATERIALIZED VIEW mv_gtt_on_commit_default as select * from gtt_on_commit_default;
+ERROR:  materialized views must not use global temporary tables or views
+--
+-- test foreign key dependencies for global temp table
+--
+CREATE global temp TABLE temp_products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+CREATE global temp TABLE temp_orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+)on commit delete rows;
+-- should fail
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+CREATE global temp TABLE temp_orders_2 (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products(product_no),
+    quantity integer
+);
+--should fail
+insert into temp_orders values(1,1,1);
+ERROR:  insert or update on table "temp_orders" violates foreign key constraint "temp_orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "temp_products".
+insert into temp_products values(1,'test',1.0);
+begin;
+insert into temp_orders values(1,1,1);
+commit;
+-- should 1 row
+select count(*) from temp_products;
+ count 
+-------
+     1
+(1 row)
+
+-- should 0 row
+select count(*) from temp_orders;
+ count 
+-------
+     0
+(1 row)
+
+--
+-- test sequence on global temp table
+--
+create global temp table global_temp_with_serial (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+-- support insert data to temp table in read only transaction
+insert into global_temp_with_serial (b) values(1);
+select * from global_temp_with_serial;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_delete(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_preserve(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table temp_table_with_sequence_oncommit_delete add c2 int default nextval('seq_1');
+alter table temp_table_with_sequence_oncommit_preserve add c2 int default nextval('seq_1');
+begin;
+insert into temp_table_with_sequence_oncommit_delete (c1)values(1);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(2);
+insert into temp_table_with_sequence_oncommit_delete (c1)values(3);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(4);
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  3 |  3
+(2 rows)
+
+commit;
+-- should 0 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_preserve order by c1;
+ c1 | c2 
+----+----
+  2 |  2
+  4 |  4
+(2 rows)
+
+--
+-- test statistics on temp table
+--
+create global temp table temp_table_test_statistics(a int);
+insert into temp_table_test_statistics values(generate_series(1,100000));
+create index idx_test_1 on temp_table_test_statistics (a);
+create index idx_test_2 on temp_table_test_statistics((a*10));
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+                   QUERY PLAN                   
+------------------------------------------------
+ Bitmap Heap Scan on temp_table_test_statistics
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_test_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+                   QUERY PLAN                   
+------------------------------------------------
+ Bitmap Heap Scan on temp_table_test_statistics
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_test_2
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze temp_table_test_statistics;
+-- indexscan by idx_test_1
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Index Only Scan using idx_test_1 on temp_table_test_statistics
+   Index Cond: (a = 200000)
+(2 rows)
+
+-- indexscan by idx_test_2
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+                        QUERY PLAN                         
+-----------------------------------------------------------
+ Index Scan using idx_test_2 on temp_table_test_statistics
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+--
+-- test temp table with toast table
+--
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |                0 |        483328 |           98304 |                 581632
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            32768 |                  |         32768 |               0 |                  32768
+(3 rows)
+
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |             8192 |        499712 |          106496 |                 606208
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            40960 |                  |         40960 |               0 |                  40960
+(3 rows)
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,10),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',10);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |             8192 |                0 |         16384 |           32768 |                  49152
+ idx_gtt_t_kenyon_1 |            16384 |                  |         16384 |               0 |                  16384
+ idx_gtt_t_kenyon_2 |            16384 |                  |         16384 |               0 |                  16384
+(3 rows)
+
+rollback;
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |             8192 |        499712 |          106496 |                 606208
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            40960 |                  |         40960 |               0 |                  40960
+(3 rows)
+
+-- test analyze/vacuum on global temp table
+ANALYZE gtt_t_kenyon;
+VACUUM gtt_t_kenyon;
+--
+-- test global temp table system view
+--
+create global temp table temp_table_test_systemview(a int primary key, b text) on commit PRESERVE rows;
+-- should empty, storage not initialized
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+ tablename 
+-----------
+(0 rows)
+
+-- should empty, storage not initialized
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into temp_table_test_systemview values(generate_series(1,10000),'test');
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+         tablename          
+----------------------------
+ temp_table_test_systemview
+(1 row)
+
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+       schemaname       |            tablename            | relpages | reltuples | relallvisible 
+------------------------+---------------------------------+----------+-----------+---------------
+ global_temporary_table | temp_table_test_systemview      |        0 |         0 |             0
+ global_temporary_table | temp_table_test_systemview_pkey |        1 |         0 |             0
+(2 rows)
+
+-- should empty
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+analyze temp_table_test_systemview;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+       schemaname       |            tablename            | relpages | reltuples | relallvisible 
+------------------------+---------------------------------+----------+-----------+---------------
+ global_temporary_table | temp_table_test_systemview      |       55 |     10000 |             0
+ global_temporary_table | temp_table_test_systemview_pkey |       30 |     10000 |             0
+(2 rows)
+
+-- get data after analyze;
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+       schemaname       |         tablename          | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------------------+----------------------------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ global_temporary_table | temp_table_test_systemview | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ global_temporary_table | temp_table_test_systemview | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+-- get all object info in current schema
+select relname ,relkind, relpersistence, reloptions from pg_class c, pg_namespace n where c.relnamespace = n.oid and n.nspname = 'global_temporary_table' order by relkind,relpersistence,relname;
+                     relname                     | relkind | relpersistence |          reloptions           
+-------------------------------------------------+---------+----------------+-------------------------------
+ global_temp_partition_01_2021_id_seq            | S       | g              | {on_commit_delete_rows=false}
+ global_temp_with_serial_a_seq                   | S       | g              | {on_commit_delete_rows=false}
+ regular_partition01_id_seq                      | S       | p              | 
+ regular_partition_01_2019_id_seq                | S       | p              | 
+ seq_1                                           | S       | p              | 
+ gtt_idx_1                                       | i       | g              | 
+ gtt_idx_2                                       | i       | g              | 
+ gtt_idx_3                                       | i       | g              | 
+ gtt_on_commit_default_pkey                      | i       | g              | 
+ gtt_on_commit_delete_pkey                       | i       | g              | 
+ gtt_on_commit_preserve_pkey                     | i       | g              | 
+ gtt_test_alter1_pkey                            | i       | g              | 
+ gtt_test_rename_pkey                            | i       | g              | 
+ idx_b                                           | i       | g              | 
+ idx_gtt_t_kenyon_1                              | i       | g              | 
+ idx_gtt_t_kenyon_2                              | i       | g              | 
+ idx_test_1                                      | i       | g              | 
+ idx_test_2                                      | i       | g              | 
+ products_pkey                                   | i       | g              | 
+ temp_orders_2_pkey                              | i       | g              | 
+ temp_orders_pkey                                | i       | g              | 
+ temp_products_pkey                              | i       | g              | 
+ temp_table_test_systemview_pkey                 | i       | g              | 
+ temp_table_with_sequence_oncommit_delete_pkey   | i       | g              | 
+ temp_table_with_sequence_oncommit_preserve_pkey | i       | g              | 
+ regular_partition01                             | p       | p              | 
+ global_temp_partition_01_2021                   | r       | g              | {on_commit_delete_rows=true}
+ global_temp_with_serial                         | r       | g              | {on_commit_delete_rows=false}
+ gtt_on_commit_default                           | r       | g              | {on_commit_delete_rows=false}
+ gtt_on_commit_delete                            | r       | g              | {on_commit_delete_rows=true}
+ gtt_on_commit_delete2                           | r       | g              | {on_commit_delete_rows=true}
+ gtt_on_commit_preserve                          | r       | g              | {on_commit_delete_rows=false}
+ gtt_t_kenyon                                    | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_alter1                                 | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_createindex                            | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_new                                    | r       | g              | {on_commit_delete_rows=false}
+ inherits_parent_global_temp                     | r       | g              | {on_commit_delete_rows=true}
+ products                                        | r       | g              | {on_commit_delete_rows=false}
+ temp_orders                                     | r       | g              | {on_commit_delete_rows=true}
+ temp_orders_2                                   | r       | g              | {on_commit_delete_rows=false}
+ temp_products                                   | r       | g              | {on_commit_delete_rows=false}
+ temp_table_test_statistics                      | r       | g              | {on_commit_delete_rows=false}
+ temp_table_test_systemview                      | r       | g              | {on_commit_delete_rows=false}
+ temp_table_with_sequence_oncommit_delete        | r       | g              | {on_commit_delete_rows=true}
+ temp_table_with_sequence_oncommit_preserve      | r       | g              | {on_commit_delete_rows=false}
+ foo                                             | r       | p              | 
+ inherits_parent                                 | r       | p              | 
+ regular_partition_01_2019                       | r       | p              | 
+(48 rows)
+
+reset search_path;
+drop schema global_temporary_table cascade;
+NOTICE:  drop cascades to 24 other objects
+DETAIL:  drop cascades to table global_temporary_table.gtt_on_commit_default
+drop cascades to table global_temporary_table.gtt_on_commit_delete
+drop cascades to table global_temporary_table.gtt_on_commit_delete2
+drop cascades to table global_temporary_table.gtt_on_commit_preserve
+drop cascades to table global_temporary_table.gtt_test_new
+drop cascades to table global_temporary_table.gtt_test_createindex
+drop cascades to table global_temporary_table.regular_partition_01_2019
+drop cascades to table global_temporary_table.regular_partition01
+drop cascades to table global_temporary_table.global_temp_partition_01_2021
+drop cascades to table global_temporary_table.inherits_parent
+drop cascades to table global_temporary_table.inherits_parent_global_temp
+drop cascades to table global_temporary_table.gtt_test_alter1
+drop cascades to table global_temporary_table.foo
+drop cascades to table global_temporary_table.temp_products
+drop cascades to table global_temporary_table.products
+drop cascades to table global_temporary_table.temp_orders
+drop cascades to table global_temporary_table.temp_orders_2
+drop cascades to table global_temporary_table.global_temp_with_serial
+drop cascades to sequence global_temporary_table.seq_1
+drop cascades to table global_temporary_table.temp_table_with_sequence_oncommit_delete
+drop cascades to table global_temporary_table.temp_table_with_sequence_oncommit_preserve
+drop cascades to table global_temporary_table.temp_table_test_statistics
+drop cascades to table global_temporary_table.gtt_t_kenyon
+drop cascades to table global_temporary_table.temp_table_test_systemview
+-- should empty
+select * from pg_list_gtt_relfrozenxids();
+ pid | relfrozenxid 
+-----+--------------
+(0 rows)
+
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2fa00a3c29a..e0001bc3448 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1359,6 +1359,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 7be89178f0f..db8095d30bf 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -130,3 +130,6 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: global_temporary_table
diff --git a/src/test/regress/sql/global_temporary_table.sql b/src/test/regress/sql/global_temporary_table.sql
new file mode 100644
index 00000000000..7dfe4446970
--- /dev/null
+++ b/src/test/regress/sql/global_temporary_table.sql
@@ -0,0 +1,290 @@
+--
+-- GLobal emparary table test case 
+--
+
+CREATE SCHEMA IF NOT EXISTS global_temporary_table;
+set search_path=global_temporary_table,sys;
+
+--
+--  test create global temp table basic syntax
+--
+create global temp table gtt_on_commit_default(a int primary key, b text);
+create global temp table gtt_on_commit_delete(a int primary key, b text) on commit delete rows;
+create global temp table gtt_on_commit_delete2(n int) with (on_commit_delete_rows='true');
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit PRESERVE rows;
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt_test_createindex(c0 tsvector,c1 varchar(100), c2 int);
+
+--
+-- test DML on global temp table
+--
+
+-- update empty temp table
+update gtt_on_commit_delete set b ='test';
+begin;
+insert into gtt_on_commit_delete values (1);
+update gtt_on_commit_delete set b ='test';
+-- should 1 row
+select * from gtt_on_commit_delete;
+commit;
+-- data delete after transaction commit
+-- should 0 row
+select * from gtt_on_commit_delete;
+
+-- update empty temp table
+update gtt_on_commit_preserve set b ='test';
+insert into gtt_on_commit_preserve values (2);
+begin;
+insert into gtt_on_commit_preserve values (3);
+update gtt_on_commit_preserve set b ='test';
+-- should 2 row
+select * from gtt_on_commit_preserve order by a;
+delete from gtt_on_commit_preserve where a=2;
+commit;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+
+begin;
+insert into gtt_on_commit_preserve values (4);
+-- temp table support truncate;
+truncate gtt_on_commit_preserve;
+select * from gtt_on_commit_preserve order by a;
+rollback;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+
+--
+-- test unsupported global temp partition table
+--
+
+-- should fail
+CREATE global temp TABLE global_temp_partition_01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+on commit delete rows;
+
+CREATE TABLE regular_partition_01_2019 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+);
+
+CREATE TABLE regular_partition01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time);
+
+-- should fail
+CREATE global temp TABLE temp_partition01_2018
+PARTITION OF regular_partition01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE global_temp_partition_01_2021 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+)on commit delete rows;
+
+-- should fail
+ALTER TABLE regular_partition01 ATTACH PARTITION global_temp_partition_01_2021 FOR VALUES FROM ('2021-01-01 00:00:00') TO ('2022-01-01 00:00:00');
+
+--
+-- test unsupported inherit table
+--
+create table 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 inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent);
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent_global_temp) on commit delete rows;
+
+--
+-- test DDL on global temp table
+--
+create index gtt_idx_1 on gtt_test_createindex using gin (c0);
+create index gtt_idx_2 on gtt_test_createindex using gist (c0);
+create index gtt_idx_3 on gtt_test_createindex using hash (c2);
+alter table gtt_test_rename rename to gtt_test_new;
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+create index CONCURRENTLY idx_b on gtt_on_commit_default (b);
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+insert into gtt_on_commit_default values(1,'test');
+create global temp table gtt_test_alter1 (a int primary key,b text);
+alter table gtt_test_alter1 alter a type varchar;
+-- should fail
+alter table gtt_on_commit_default alter a type varchar;
+-- should fail
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) on commit delete rows;
+-- should fail
+alter table gtt_on_commit_default SET TABLESPACE pg_default;
+-- should fail
+alter table gtt_on_commit_default set ( on_commit_delete_rows='true');
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) with(on_commit_delete_rows=true);
+-- should fail
+create or replace global temp view gtt_v as select 5;
+create table foo();
+-- should fail
+alter table foo set (on_commit_delete_rows='true');
+-- should fail
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit drop;
+-- should fail
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+-- should fail
+CREATE MATERIALIZED VIEW mv_gtt_on_commit_default as select * from gtt_on_commit_default;
+
+--
+-- test foreign key dependencies for global temp table
+--
+CREATE global temp TABLE temp_products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+CREATE global temp TABLE temp_orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+)on commit delete rows;
+
+-- should fail
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+);
+
+CREATE global temp TABLE temp_orders_2 (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products(product_no),
+    quantity integer
+);
+
+--should fail
+insert into temp_orders values(1,1,1);
+
+insert into temp_products values(1,'test',1.0);
+begin;
+insert into temp_orders values(1,1,1);
+commit;
+-- should 1 row
+select count(*) from temp_products;
+-- should 0 row
+select count(*) from temp_orders;
+
+--
+-- test sequence on global temp table
+--
+create global temp table global_temp_with_serial (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+-- support insert data to temp table in read only transaction
+insert into global_temp_with_serial (b) values(1);
+select * from global_temp_with_serial;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_delete(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_preserve(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table temp_table_with_sequence_oncommit_delete add c2 int default nextval('seq_1');
+alter table temp_table_with_sequence_oncommit_preserve add c2 int default nextval('seq_1');
+begin;
+insert into temp_table_with_sequence_oncommit_delete (c1)values(1);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(2);
+insert into temp_table_with_sequence_oncommit_delete (c1)values(3);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(4);
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+commit;
+-- should 0 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_preserve order by c1;
+
+--
+-- test statistics on temp table
+--
+create global temp table temp_table_test_statistics(a int);
+insert into temp_table_test_statistics values(generate_series(1,100000));
+create index idx_test_1 on temp_table_test_statistics (a);
+create index idx_test_2 on temp_table_test_statistics((a*10));
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+analyze temp_table_test_statistics;
+-- indexscan by idx_test_1
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+-- indexscan by idx_test_2
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+
+--
+-- test temp table with toast table
+--
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,10),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',10);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+rollback;
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+
+-- test analyze/vacuum on global temp table
+ANALYZE gtt_t_kenyon;
+VACUUM gtt_t_kenyon;
+
+--
+-- test global temp table system view
+--
+create global temp table temp_table_test_systemview(a int primary key, b text) on commit PRESERVE rows;
+-- should empty, storage not initialized
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+-- should empty, storage not initialized
+select count(*) from pg_list_gtt_relfrozenxids();
+insert into temp_table_test_systemview values(generate_series(1,10000),'test');
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+select count(*) from pg_list_gtt_relfrozenxids();
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+-- should empty
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+analyze temp_table_test_systemview;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+-- get data after analyze;
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+
+-- get all object info in current schema
+select relname ,relkind, relpersistence, reloptions from pg_class c, pg_namespace n where c.relnamespace = n.oid and n.nspname = 'global_temporary_table' order by relkind,relpersistence,relname;
+
+reset search_path;
+drop schema global_temporary_table cascade;
+-- should empty
+select * from pg_list_gtt_relfrozenxids();
+
-- 
2.30.1 (Apple Git-130)

0003-gtt-v57-implementation.patchapplication/octet-stream; name=0003-gtt-v57-implementation.patchDownload
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index e84ecd1c981..c58218b1508 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -18,6 +18,7 @@
 #include "access/toast_internals.h"
 #include "access/visibilitymap.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "funcapi.h"
 #include "miscadmin.h"
 #include "storage/bufmgr.h"
@@ -223,6 +224,8 @@ verify_heapam(PG_FUNCTION_ARGS)
 	BlockNumber last_block;
 	BlockNumber nblocks;
 	const char *skip;
+	TransactionId	relfrozenxid = InvalidTransactionId;
+	MultiXactId		relminmxid = InvalidMultiXactId;
 
 	/* Check to see if caller supports us returning a tuplestore */
 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -339,6 +342,13 @@ verify_heapam(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(ctx.rel) &&
+		!gtt_storage_attached(RelationGetRelid(ctx.rel)))
+	{
+		relation_close(ctx.rel, AccessShareLock);
+		PG_RETURN_NULL();
+	}
+
 	/* Early exit if the relation is empty */
 	nblocks = RelationGetNumberOfBlocks(ctx.rel);
 	if (!nblocks)
@@ -406,9 +416,25 @@ verify_heapam(PG_FUNCTION_ARGS)
 
 	update_cached_xid_range(&ctx);
 	update_cached_mxid_range(&ctx);
-	ctx.relfrozenxid = ctx.rel->rd_rel->relfrozenxid;
+
+	if (RELATION_IS_GLOBAL_TEMP(ctx.rel))
+	{
+		get_gtt_relstats(RelationGetRelid(ctx.rel),
+						NULL,
+						NULL,
+						NULL,
+						&relfrozenxid,
+						&relminmxid);
+	}
+	else
+	{
+		relfrozenxid = ctx.rel->rd_rel->relfrozenxid;
+		relminmxid = ctx.rel->rd_rel->relminmxid;
+	}
+
+	ctx.relfrozenxid = relfrozenxid;
 	ctx.relfrozenfxid = FullTransactionIdFromXidAndCtx(ctx.relfrozenxid, &ctx);
-	ctx.relminmxid = ctx.rel->rd_rel->relminmxid;
+	ctx.relminmxid = relminmxid;
 
 	if (TransactionIdIsNormal(ctx.relfrozenxid))
 		ctx.oldest_xid = ctx.relfrozenxid;
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index b5602f53233..21b2d2a9527 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -159,6 +159,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * In order to avoid consistency problems, the global temporary table
+	 * uses ShareUpdateExclusiveLock.
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temporary table on commit options",
+			RELOPT_KIND_HEAP,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},
 	/* list terminator */
 	{{NULL}}
 };
@@ -1834,6 +1847,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 43ba03b6eb9..49f1052fdb1 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1023,7 +1023,7 @@ gistproperty(Oid index_oid, int attno,
 XLogRecPtr
 gistGetFakeLSN(Relation rel)
 {
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	if (RELATION_IS_TEMP(rel))
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index eb3810494f2..cbd22909582 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -151,7 +151,7 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	if (!RELATION_IS_TEMP(index))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 2da2be16969..34ecef39de9 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -52,6 +52,7 @@
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/atomics.h"
@@ -5844,6 +5845,19 @@ heap_abort_speculative(Relation relation, ItemPointer tid)
 	BlockNumber block;
 	Buffer		buffer;
 	TransactionId prune_xid;
+	TransactionId relfrozenxid = InvalidTransactionId;
+
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		get_gtt_relstats(RelationGetRelid(relation),
+						NULL,
+						NULL,
+						NULL,
+						&relfrozenxid,
+						NULL);
+	}
+	else
+		relfrozenxid = relation->rd_rel->relfrozenxid;
 
 	Assert(ItemPointerIsValid(tid));
 
@@ -5896,8 +5910,8 @@ heap_abort_speculative(Relation relation, ItemPointer tid)
 	 * TransactionXmin, so there's no race here).
 	 */
 	Assert(TransactionIdIsValid(TransactionXmin));
-	if (TransactionIdPrecedes(TransactionXmin, relation->rd_rel->relfrozenxid))
-		prune_xid = relation->rd_rel->relfrozenxid;
+	if (TransactionIdPrecedes(TransactionXmin, relfrozenxid))
+		prune_xid = relfrozenxid;
 	else
 		prune_xid = TransactionXmin;
 	PageSetPrunable(page, prune_xid);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 9befe012a9e..26fce0c4b83 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -593,7 +593,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
@@ -645,7 +645,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(RelationGetSmgr(rel), dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 05221cc1d6d..984e08b4a78 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -63,6 +63,7 @@
 #include "access/xlog.h"
 #include "catalog/index.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -507,6 +508,25 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 	TransactionId OldestXmin;
 	TransactionId FreezeLimit;
 	MultiXactId MultiXactCutoff;
+	TransactionId	relfrozenxid = InvalidTransactionId;
+	MultiXactId		relminmxid = InvalidMultiXactId;
+	double			reltuples = 0;
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		get_gtt_relstats(RelationGetRelid(rel),
+						NULL,
+						&reltuples,
+						NULL,
+						&relfrozenxid,
+						&relminmxid);
+	}
+	else
+	{
+		relfrozenxid = rel->rd_rel->relfrozenxid;
+		relminmxid = rel->rd_rel->relminmxid;
+		reltuples = rel->rd_rel->reltuples;
+	}
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
@@ -542,9 +562,9 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 	 * table's minimum MultiXactId is older than or equal to the requested
 	 * mxid full-table scan limit; or if DISABLE_PAGE_SKIPPING was specified.
 	 */
-	aggressive = TransactionIdPrecedesOrEquals(rel->rd_rel->relfrozenxid,
+	aggressive = TransactionIdPrecedesOrEquals(relfrozenxid,
 											   xidFullScanLimit);
-	aggressive |= MultiXactIdPrecedesOrEquals(rel->rd_rel->relminmxid,
+	aggressive |= MultiXactIdPrecedesOrEquals(relminmxid,
 											  mxactFullScanLimit);
 	if (params->options & VACOPT_DISABLE_PAGE_SKIPPING)
 		aggressive = true;
@@ -591,9 +611,9 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 	}
 
 	vacrel->bstrategy = bstrategy;
-	vacrel->relfrozenxid = rel->rd_rel->relfrozenxid;
-	vacrel->relminmxid = rel->rd_rel->relminmxid;
-	vacrel->old_live_tuples = rel->rd_rel->reltuples;
+	vacrel->relfrozenxid = relfrozenxid;
+	vacrel->relminmxid = relminmxid;
+	vacrel->old_live_tuples = reltuples;
 
 	/* Set cutoffs for entire VACUUM */
 	vacrel->OldestXmin = OldestXmin;
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 5bc7c3616a9..0b261f723d7 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -677,6 +678,14 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		/*
+		 * If a global temporary table storage file is not initialized in the
+		 * current backend, its index does not have a root page, just returns 0.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e773612..8c21979625f 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -44,6 +44,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index aa7d4d5456b..595cb03eb4a 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -504,6 +504,7 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 5898203972b..159b4e9c812 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -62,6 +62,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,
@@ -366,13 +368,24 @@ heap_create(const char *relname,
 			break;
 	}
 
+	/* For global temporary table, even if the storage is not initialized,
+	 * the relfilenode needs to be generated and put into the catalog.
+	 */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		create_storage = false;
+		if (!OidIsValid(relfilenode))
+			relfilenode = relid;
+	}
 	/*
 	 * Decide whether to create storage. If caller passed a valid relfilenode,
 	 * storage is already created, so don't do it here.  Also don't create it
 	 * for relkinds without physical storage.
 	 */
-	if (!RELKIND_HAS_STORAGE(relkind) || OidIsValid(relfilenode))
+	else if (!RELKIND_HAS_STORAGE(relkind) || OidIsValid(relfilenode))
+	{
 		create_storage = false;
+	}
 	else
 	{
 		create_storage = true;
@@ -427,7 +440,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:
@@ -445,7 +458,8 @@ heap_create(const char *relname,
 	 * protected by the existence of a physical file; but for relations with
 	 * no files, add a pg_shdepend entry to account for that.
 	 */
-	if (!create_storage && reltablespace != InvalidOid)
+	if (!create_storage && reltablespace != InvalidOid &&
+		relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
 		recordDependencyOnTablespace(RelationRelationId, relid,
 									 reltablespace);
 
@@ -998,6 +1012,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -1036,8 +1051,21 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/*
+	 * The transaction information of the global temporary table is stored
+	 * in the local hash table, not in catalog.
+	 */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1410,6 +1438,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1495,8 +1524,9 @@ heap_create_with_catalog(const char *relname,
 	/*
 	 * If there's a special on-commit action, remember it
 	 */
-	if (oncommit != ONCOMMIT_NOOP)
-		register_on_commit_action(relid, oncommit);
+	if (oncommit != ONCOMMIT_NOOP &&
+		relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+		register_on_commit_action(relid, oncommit, false);
 
 	/*
 	 * ok, the relation has been cataloged, so close our relations and return
@@ -1993,6 +2023,19 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/*
+	 * Only when other sessions are not using this Global temporary table,
+	 * is it allowed to DROP it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot drop global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3277,7 +3320,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3289,7 +3332,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3325,7 +3368,7 @@ RelationTruncateIndexes(Relation heapRelation)
  * ON COMMIT truncation of temporary tables, where it doesn't matter.
  */
 void
-heap_truncate(List *relids)
+heap_truncate(List *relids, bool is_global_temp)
 {
 	List	   *relations = NIL;
 	ListCell   *cell;
@@ -3335,8 +3378,23 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode;
+
+		/*
+		 * Truncate global temporary table only clears local data,
+		 * so only low-level locks need to be held.
+		 */
+		if (is_global_temp)
+		{
+			if (!gtt_storage_attached(rid))
+				continue;
+
+			lockmode = RowExclusiveLock;
+		}
+		else
+			lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3369,6 +3427,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3377,23 +3436,39 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	/*
+	 * Truncate GTT only clears local data, so only low-level locks
+	 * need to be held.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		lockmode = AccessShareLock;
+	else
+		lockmode = AccessExclusiveLock;
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	/*
+	 * After the data is cleaned up on the GTT, the transaction information
+	 * for the data(stored in local hash table) is also need reset.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(RelationGetRelid(rel), 0, 0, 0, RecentXmin, GetOldestMultiXactId());
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 26bfa74ce75..c32b45f9673 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -54,6 +54,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"
@@ -733,6 +734,25 @@ index_create(Relation heapRelation,
 	TransactionId relfrozenxid;
 	MultiXactId relminmxid;
 
+	/* For global temporary table only */
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temporary table with concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot reindex global temporary tables concurrently")));
+
+		/*
+		 * For the case that some backend is applied relcache message to create
+		 * an index on a global temporary table, if this table in the current
+		 * backend are not initialized, the creation of index storage on the
+		 * table are also skipped.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/* constraint flags can only be set when a constraint is requested */
 	Assert((constr_flags == 0) ||
 		   ((flags & INDEX_CREATE_ADD_CONSTRAINT) != 0));
@@ -2107,7 +2127,7 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 * lock (see comments in RemoveRelations), and a non-concurrent DROP is
 	 * more efficient.
 	 */
-	Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+	Assert(!RelpersistenceTsTemp(get_rel_persistence(indexId)) ||
 		   (!concurrent && !concurrent_lock_mode));
 
 	/*
@@ -2139,6 +2159,21 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/*
+	 * Allow to drop index on global temporary table when only current
+	 * backend use it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation) &&
+		is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+	{
+		ereport(ERROR,
+			(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+			 errmsg("cannot drop index %s on global temporary table %s",
+					RelationGetRelationName(userIndexRelation), RelationGetRelationName(userHeapRelation)),
+					errdetail("Because the index is created on the global temporary table and other backend attached it."),
+					errhint("Please try detach all sessions using this temporary table and try again.")));
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2747,6 +2782,7 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = RELATION_IS_GLOBAL_TEMP(rel);
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2841,20 +2877,37 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
-		{
-			rd_rel->relpages = (int32) relpages;
-			dirty = true;
-		}
-		if (rd_rel->reltuples != (float4) reltuples)
+		/* For global temporary table */
+		if (is_gtt)
 		{
-			rd_rel->reltuples = (float4) reltuples;
-			dirty = true;
+			/* Update GTT'statistics into local relcache */
+			rel->rd_rel->relpages = (int32) relpages;
+			rel->rd_rel->reltuples = (float4) reltuples;
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+
+			/* Update GTT'statistics into local hashtable */
+			up_gtt_relstats(RelationGetRelid(rel), relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+		else
 		{
-			rd_rel->relallvisible = (int32) relallvisible;
-			dirty = true;
+			if (rd_rel->relpages != (int32) relpages)
+			{
+				rd_rel->relpages = (int32) relpages;
+				dirty = true;
+			}
+
+			if (rd_rel->reltuples != (float4) reltuples)
+			{
+				rd_rel->reltuples = (float4) reltuples;
+				dirty = true;
+			}
+
+			if (rd_rel->relallvisible != (int32) relallvisible)
+			{
+				rd_rel->relallvisible = (int32) relallvisible;
+				dirty = true;
+			}
 		}
 	}
 
@@ -2967,6 +3020,26 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, progress_index, progress_vals);
 	}
 
+	/* For build index on global temporary table */
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		/*
+		 * If the storage for the index in this session is not initialized,
+		 * it needs to be created.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			/* Before create init storage, fix the local Relcache first */
+			force_enable_gtt_index(indexRelation);
+
+			Assert(gtt_storage_attached(RelationGetRelid(heapRelation)));
+
+			/* Init storage for index */
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3508,6 +3581,8 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	PGRUsage	ru0;
 	bool		progress = ((params->options & REINDEXOPT_REPORT_PROGRESS) != 0);
 	bool		set_tablespace = false;
+	LOCKMODE	lockmode_on_heap = ShareLock;
+	LOCKMODE	lockmode_on_index = AccessExclusiveLock;
 
 	pg_rusage_init(&ru0);
 
@@ -3521,10 +3596,29 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	if (!OidIsValid(heapId))
 		return;
 
+	/*
+	 * For reindex on global temporary table, If the storage for the index
+	 * in current backend is not initialized, nothing is done.
+	 */
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		if (!gtt_storage_attached(indexId))
+		{
+			/* Suppress use of the target index while rebuilding it */
+			SetReindexProcessing(heapId, indexId);
+			/* Re-allow use of target index */
+			ResetReindexProcessing();
+			return;
+		}
+
+		lockmode_on_heap = AccessShareLock;
+		lockmode_on_index = AccessShareLock;
+	}
+
 	if ((params->options & REINDEXOPT_MISSING_OK) != 0)
-		heapRelation = try_table_open(heapId, ShareLock);
+		heapRelation = try_table_open(heapId, lockmode_on_heap);
 	else
-		heapRelation = table_open(heapId, ShareLock);
+		heapRelation = table_open(heapId, lockmode_on_heap);
 
 	/* if relation is gone, leave */
 	if (!heapRelation)
@@ -3550,7 +3644,7 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	 * Open the target index relation and get an exclusive lock on it, to
 	 * ensure that no one else is touching this particular index.
 	 */
-	iRel = index_open(indexId, AccessExclusiveLock);
+	iRel = index_open(indexId, lockmode_on_index);
 
 	if (progress)
 		pgstat_progress_update_param(PROGRESS_CREATEIDX_ACCESS_METHOD_OID,
@@ -3801,6 +3895,12 @@ reindex_relation(Oid relid, int flags, ReindexParams *params)
 	bool		result;
 	ListCell   *indexId;
 	int			i;
+	LOCKMODE	lockmode;
+
+	if (flags & REINDEX_REL_PROCESS_GLOBAL_TEMP)
+		lockmode = AccessShareLock;
+	else
+		lockmode = ShareLock;
 
 	/*
 	 * Open and lock the relation.  ShareLock is sufficient since we only need
@@ -3808,9 +3908,9 @@ reindex_relation(Oid relid, int flags, ReindexParams *params)
 	 * should match ReindexTable().
 	 */
 	if ((params->options & REINDEXOPT_MISSING_OK) != 0)
-		rel = try_table_open(relid, ShareLock);
+		rel = try_table_open(relid, lockmode);
 	else
-		rel = table_open(relid, ShareLock);
+		rel = table_open(relid, lockmode);
 
 	/* if relation is gone, leave */
 	if (!rel)
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 4de8400fd0f..fe3fcc712cb 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -656,6 +656,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/* Do not allow create global temporary table in temporary schemas */
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp table in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index c5ad28d71fe..707068a6fd8 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,6 +27,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -61,6 +62,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			temprelOid;			/* InvalidOid if not a global temporary rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -115,7 +117,7 @@ AddPendingSync(const RelFileNode *rnode)
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -126,7 +128,12 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/*
+		 * Global temporary table and local temporary table use same
+		 * design on storage module.
+		 */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -154,6 +161,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
@@ -165,6 +173,21 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		AddPendingSync(&rnode);
 	}
 
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		Assert(rel && RELATION_IS_GLOBAL_TEMP(rel));
+
+		/*
+		 * Remember the reloid of global temporary table, which is used for
+		 * transaction commit or rollback.
+		 * see smgrDoPendingDeletes.
+		 */
+		pending->temprelOid = RelationGetRelid(rel);
+
+		/* Remember global temporary table storage info to localhash */
+		remember_gtt_storage_info(rnode, rel);
+	}
+
 	return srel;
 }
 
@@ -201,11 +224,20 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/*
+	 * Remember the reloid of global temporary table, which is used for
+	 * transaction commit or rollback.
+	 * see smgrDoPendingDeletes.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->temprelOid = RelationGetRelid(rel);
+
 	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
@@ -618,6 +650,7 @@ smgrDoPendingDeletes(bool isCommit)
 	int			nrels = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -647,14 +680,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->temprelOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -664,12 +701,21 @@ smgrDoPendingDeletes(bool isCommit)
 
 	if (nrels > 0)
 	{
+		int	i;
+
 		smgrdounlinkall(srels, nrels, false);
 
-		for (int i = 0; i < nrels; i++)
+		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* Delete global temporary table info in localhash */
+			if (gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 00000000000..64242abe4df
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1526 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  The body implementation of Global Temparary table.
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *	  See src/backend/catalog/GTT_README for Global temparary table's
+ *	  requirements and design.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_info_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+
+int		vacuum_gtt_defer_check_age = 0;
+
+/*
+ * The Global temporary table's shared hash table data structure
+ */
+typedef struct gtt_ctl_data
+{
+	LWLock		lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+/* record this global temporary table in which backends are being used */
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+/*
+ * The Global temporary table's local hash table data structure
+ */
+/* Record the storage information and statistical information of the global temporary table */
+typedef struct
+{
+	Oid			relfilenode;
+	Oid			spcnode;
+
+	/* pg_class relstat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+
+	/* pg_statistic column stat */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_free_statistics(gtt_relfilenode *rnode);
+static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok);
+static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok);
+static Bitmapset *copy_active_gtt_bitmap(Oid relid);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+/*
+ * Calculate shared hash table entry size for GTT.
+ */
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	/* hash entry header size */
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	/*
+	 * hash entry data size
+	 * this is a bitmap in shared memory, each backend have a bit.
+	 */
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+/*
+ * Calculate shared hash table max size for GTT.
+ */
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	/* shared hash header size */
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	/* hash entry size */
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	/* max size */
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+/*
+ * Initialization shared hash table for GTT.
+ */
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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();
+	}
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(gtt_fnode);
+	info.entrysize = action_gtt_shared_hash_entry_size();
+	active_gtt_shared_hash =
+		ShmemInitHash("active gtt shared hash",
+						gtt_shared_ctl->max_entry,
+						gtt_shared_ctl->max_entry,
+						&info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+/*
+ * Record GTT relid to shared hash table, which means that current backend is using this GTT.
+ */
+static void
+gtt_storage_checkin(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			found;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *)&(fnode), 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_global_temporary_table.")));
+	}
+
+	if (!found)
+	{
+		int			wordnum;
+
+		/* init bitmap */
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	/* record itself in bitmap */
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+/*
+ * Remove the GTT relid record from the shared hash table which means that current backend is
+ * not use this GTT.
+ */
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	if (!skiplock)
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		if (!skiplock)
+			LWLockRelease(&gtt_shared_ctl->lock);
+
+		if (isCommit)
+			elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+	/* remove itself from bitmap */
+	bms_del_member(entry->map, MyBackendId);
+
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+
+	if (!skiplock)
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+	return;
+}
+
+/*
+ * Gets usage information for a GTT from shared hash table.
+ * The information is in the form of bitmap.
+ * Quickly copy the entire bitmap from shared memory and return it.
+ * that to avoid holding locks for a long time.
+ */
+static Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	Bitmapset		*map_copy = NULL;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		return NULL;
+	}
+
+	Assert(entry->map);
+
+	/* copy the entire bitmap */
+	if (!bms_is_empty(entry->map))
+		map_copy = bms_copy(entry->map);
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return map_copy;
+}
+
+/*
+ * Check if there are other backends using this GTT besides the current backend.
+ */
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			in_use = false;
+	int			num_use = 0;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		return false;
+	}
+
+	Assert(entry->map);
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+	/* how many backend are using this GTT */
+	num_use = bms_num_members(entry->map);
+	if (num_use == 0)
+		in_use = false;
+	else if (num_use == 1)
+	{
+		/* check if this is itself */
+		if(bms_is_member(MyBackendId, entry->map))
+			in_use = false;
+		else
+			in_use = true;
+	}
+	else
+		in_use = true;
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return in_use;
+}
+
+/*
+ * Record GTT information to local hash.
+ * They include GTT storage info, transaction info and statistical info.
+ */
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+	gtt_local_hash_entry		*entry;
+	MemoryContext			oldcontext;
+	gtt_relfilenode			*new_node = NULL;
+	Oid				relid = RelationGetRelid(rel);
+	int				natts = 0;
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	/* First time through: initialize the hash table */
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		HASHCTL		ctl;
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_info_context =
+			AllocSetContextCreate(CacheMemoryContext,
+								"gtt info context",
+								ALLOCSET_DEFAULT_SIZES);
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		ctl.hcxt = gtt_info_context;
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+	}
+
+	Assert(CacheMemoryContext);
+	Assert(gtt_info_context);
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	entry = gtt_search_by_relid(relid, true);
+	if (!entry)
+	{
+		bool		found = false;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash,
+						(void *) &relid, HASH_ENTER, &found);
+
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			/* record the on commit clause */
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS, true);
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			gtt_storage_checkin(relid);
+		}
+	}
+
+	/* record storage info relstat columnstats and transaction info to relfilenode list */
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	new_node->natts = 0;
+	new_node->attnum = NULL;
+	new_node->att_stat_tups = NULL;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* init column stats structure */
+	natts = RelationGetNumberOfAttributes(rel);
+	new_node->attnum = palloc0(sizeof(int) * natts);
+	new_node->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+	new_node->natts = natts;
+
+	/* only heap rel or toast rel have transaction info */
+	if (entry->relkind == RELKIND_RELATION ||
+		entry->relkind == RELKIND_TOASTVALUE)
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+
+	/* Registration callbacks are used to trigger cleanup during process exit */
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+/*
+ * Remove GTT information from local hash when transaction commit/rollback.
+ */
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	gtt_relfilenode		*d_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+		return;
+	}
+
+	d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+		else
+		{
+			/* rollback transaction */
+			if (entry->relfilenode_list == NIL)
+			{
+				if (entry->relkind == RELKIND_RELATION ||
+					entry->relkind == RELKIND_SEQUENCE)
+					gtt_storage_checkout(relid, false, isCommit);
+
+				hash_search(gtt_storage_local_hash,
+						(void *) &(relid), HASH_REMOVE, NULL);
+			}
+
+			return;
+		}
+	}
+
+	/* Clean up transaction info from Local order list and MyProc */
+	if (entry->relkind == RELKIND_RELATION ||
+		entry->relkind == RELKIND_TOASTVALUE)
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+
+		/* this is valid relfrozenxid */
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	/* delete relfilenode from rel entry */
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	gtt_free_statistics(d_rnode);
+
+	if (entry->relfilenode_list == NIL)
+	{
+		/* this means we truncate this GTT at current backend */
+
+		/* tell shared hash that current backend will no longer use this GTT */
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(relid, false, isCommit);
+
+		hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_REMOVE, NULL);
+	}
+
+	return;
+}
+
+/*
+ * Check if current backend is using this GTT.
+ */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool			found = false;
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (!OidIsValid(relid))
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry)
+		found = true;
+
+	return found;
+}
+
+/*
+ * When backend exit, bulk cleaning all GTT storage and local buffer of this backend.
+ */
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS			status;
+	gtt_local_hash_entry	*entry;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	/* Need to ensure we have a usable transaction. */
+	AbortOutOfAnyTransaction();
+
+	/* Search all relfilenode for GTT in current backend */
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel[1];
+			RelFileNode		rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel[0] = smgropen(rnode, MyBackendId);
+			smgrdounlinkall(srel, 1, false);
+			smgrclose(srel[0]);
+		}
+
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(entry->relid, true, false);
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+		hash_search(gtt_storage_local_hash, (void *) &(entry->relid), HASH_REMOVE, NULL);
+	}
+
+	/* set to global area */
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;
+
+	return;
+}
+
+/*
+ * Update GTT relstats(relpage/reltuple/relallvisible)
+ * to local hash.
+ */
+void
+up_gtt_relstats(Oid relid,
+					BlockNumber num_pages,
+					double num_tuples,
+					BlockNumber num_all_visible_pages,
+					TransactionId relfrozenxid,
+					TransactionId relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (!OidIsValid(relid))
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (num_pages > 0 &&
+		gtt_rnode->relpages != (int32)num_pages)
+		gtt_rnode->relpages = (int32)num_pages;
+
+	if (num_tuples > 0 &&
+		gtt_rnode->reltuples != (float4)num_tuples)
+		gtt_rnode->reltuples = (float4)num_tuples;
+
+	/* only heap contain transaction information and relallvisible */
+	if (entry->relkind == RELKIND_RELATION ||
+		entry->relkind == RELKIND_TOASTVALUE)
+	{
+		if (num_all_visible_pages > 0 &&
+			gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+		{
+			gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+		}
+
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNextTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			/* set to local order list */
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			/* set to global area */
+			set_gtt_session_relfrozenxid();
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search GTT relstats(relpage/reltuple/relallvisible)
+ * from local has.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update GTT info(definition is same as pg_statistic)
+ * to local hash.
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	MemoryContext		oldcontext;
+	int			i = 0;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return;
+
+	Assert(entry->relid == reloid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (gtt_rnode->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	/* switch context to gtt_info_context for store tuple at heap_form_tuple */
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == 0)
+		{
+			gtt_rnode->attnum[i] = attnum;
+			break;
+		}
+		else if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			heap_freetuple(gtt_rnode->att_stat_tups[i]);
+			gtt_rnode->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < gtt_rnode->natts);
+	Assert(gtt_rnode->att_stat_tups[i] == NULL);
+	gtt_rnode->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search GTT statistic info(definition is same as pg_statistic)
+ * from local hash.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	int			i = 0;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return NULL;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return NULL;
+
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			return gtt_rnode->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Insert a RelfrozenXID into the list and keep the list in order.
+ */
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int		i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Remove a RelfrozenXID from order list gtt_session_relfrozenxid_list.
+ */
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+/*
+ * Update of backend Level oldest relfrozenxid to MyProc.
+ * This makes each backend's oldest RelFrozenxID globally visible.
+ */
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list != NIL)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	if (MyProc->backend_gtt_frozenxid != gtt_frozenxid)
+		MyProc->backend_gtt_frozenxid = gtt_frozenxid;
+}
+
+/*
+ * Get GTT column level data statistics.
+ */
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate		*tupstore;
+	HeapTuple		tuple;
+	Relation		rel = NULL;
+	Oid			reloid = PG_GETARG_OID(0);
+	int			attnum = PG_GETARG_INT32(1);
+	char			rel_persistence;
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	/* get data from local hash */
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum		values[31];
+		bool		isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, NoLock);
+	relation_close(pg_tatistic, AccessShareLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get GTT table level data statistics.
+ */
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate	*tupstore;
+	TupleDesc	tupdesc;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid			reloid = PG_GETARG_OID(0);
+	Oid			relnode = 0;
+	char			rel_persistence;
+	BlockNumber		relpages = 0;
+	BlockNumber		relallvisible = 0;
+	uint32			relfrozenxid = 0;
+	uint32			relminmxid = 0;
+	double			reltuples = 0;
+	Relation		rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[6];
+		bool	isnull[6];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get a list of backend pids that are currently using this GTT.
+ */
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	PGPROC			*proc = NULL;
+	Bitmapset		*map = NULL;
+	Tuplestorestate		*tupstore;
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	HeapTuple		tuple;
+	Oid			reloid = PG_GETARG_OID(0);
+	char			rel_persistence;
+	Relation		rel = NULL;
+	pid_t			pid = 0;
+	int				backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	/* get data from share hash */
+	map = copy_active_gtt_bitmap(reloid);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			/* backendid map to process pid */
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get backend level oldest relfrozenxid of each backend using GTT in current database.
+ */
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate		*tupstore;
+	int			*pids = NULL;
+	uint32			*xids = NULL;
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	HeapTuple		tuple;
+	int			num_xid = MaxBackends + 1;
+	int			i = 0;
+	int			j = 0;
+	uint32			oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+
+	/* Get backend level oldest relfrozenxid in all backend that in MyDatabaseId use GTT */
+	oldest = list_all_backend_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+/*
+ * In order to build the GTT index, force enable GTT'index.
+ */
+void
+force_enable_gtt_index(Relation index)
+{
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(RelationGetRelid(index)));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+/*
+ * Fix the local state of the GTT's index.
+ */
+void
+gtt_fix_index_backend_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid heapOid = index->rd_index->indrelid;
+
+	/* Must be GTT */
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	/*
+	 * If this GTT is not initialized in the current backend,
+	 * its index status is temporarily set to invalid(local relcache).
+	 */
+	if (gtt_storage_attached(heapOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+/*
+ * During the SQL initialization of the executor (InitPlan)
+ * Initialize storage of GTT GTT'indexes and build empty index.
+ */
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int		i;
+	Oid		toastrelid;
+
+	if (!(operation == CMD_UTILITY || operation == CMD_INSERT))
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	/* Each GTT is initialized once in each backend */
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	/* init heap storage */
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	/* rebuild all local index for global temp table */
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		index_build(relation, index, info, true, false);
+
+		/* after build index, index re-enabled */
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+		Assert(info->ii_ReadyForInserts);
+	}
+
+	/* rebuild index for global temp toast table */
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+
+		/* init index storage */
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid			indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+			/* build empty index */
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			Assert(currentIndex->rd_index->indisvalid);
+			Assert(currentIndex->rd_index->indislive);
+			Assert(currentIndex->rd_index->indisready);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+/*
+ * Release the data structure memory used to store GTT storage info.
+ */
+static void
+gtt_free_statistics(gtt_relfilenode *rnode)
+{
+	int i;
+
+	Assert(rnode);
+
+	for (i = 0; i < rnode->natts; i++)
+	{
+		if (rnode->att_stat_tups[i])
+		{
+			heap_freetuple(rnode->att_stat_tups[i]);
+			rnode->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (rnode->attnum)
+		pfree(rnode->attnum);
+
+	if (rnode->att_stat_tups)
+		pfree(rnode->att_stat_tups);
+
+	pfree(rnode);
+
+	return;
+}
+
+/*
+ * Get the current relfilenode of this GTT.
+ */
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
+/*
+ * Get a relfilenode used by this GTT during the transaction life cycle.
+ */
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok)
+{
+	gtt_relfilenode		*rnode = NULL;
+	ListCell		*lc;
+
+	Assert(entry);
+
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == relfilenode)
+		{
+			rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (!missing_ok && rnode == NULL)
+		elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+	return rnode;
+}
+
+/*
+ * Get one GTT info from local hash.
+ */
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry == NULL && !missing_ok)
+		elog(ERROR, "relid %u not found in local hash", relid);
+
+	return entry;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 55f6e3711d8..fc75533263b 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 8bfb2ad9584..500305e8ae8 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -104,7 +105,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -185,6 +186,17 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -601,14 +613,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]));
 		}
 
 		/*
@@ -1633,7 +1646,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;
@@ -1735,31 +1748,48 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/*
+		 * For global temporary table,
+		 * Update column statistic to localhash, not catalog.
+		 */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 9d22f648a84..a44eefa1f6b 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/toasting.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
@@ -390,6 +391,22 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+	{
+		if (gtt_storage_attached(RelationGetRelid(OldHeap)))
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("not support cluster global temporary table yet")));
+
+		relation_close(OldHeap, AccessExclusiveLock);
+		pgstat_progress_end_command();
+		return;
+	}
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
@@ -585,6 +602,8 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
 	TransactionId frozenXid;
 	MultiXactId cutoffMulti;
 
+	Assert(!RELATION_IS_GLOBAL_TEMP(OldHeap));
+
 	/* Mark the correct index as clustered */
 	if (OidIsValid(indexOid))
 		mark_index_clustered(OldHeap, indexOid, true);
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 53f48531419..c03191cce94 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -289,7 +289,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP_ON_CURRENT_SESSION(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, whereClause,
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 40a54ad0bd7..7895e7d99b9 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -30,6 +30,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/copyfrom_internal.h"
 #include "commands/progress.h"
@@ -659,6 +660,9 @@ CopyFrom(CopyFromState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	/* Check and init global temporary table storage in current backend */
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	/*
 	 * Set up a ModifyTableState so we can let FDW(s) init themselves for
 	 * foreign-table result relation(s).
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index c14ca27c5ed..d65ae895356 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -111,6 +111,7 @@ struct ReindexIndexCallbackState
 {
 	ReindexParams params;		/* options from statement */
 	Oid			locked_table_oid;	/* tracks previously locked table */
+	LOCKMODE	lockmode;
 };
 
 /*
@@ -570,7 +571,7 @@ DefineIndex(Oid relationId,
 	 * is more efficient.  Do this before any use of the concurrent option is
 	 * done.
 	 */
-	if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+	if (stmt->concurrent && !RelpersistenceTsTemp(get_rel_persistence(relationId)))
 		concurrent = true;
 	else
 		concurrent = false;
@@ -2581,9 +2582,9 @@ ReindexIndex(RangeVar *indexRelation, ReindexParams *params, bool isTopLevel)
 	 */
 	state.params = *params;
 	state.locked_table_oid = InvalidOid;
+	state.lockmode = AccessShareLock;
 	indOid = RangeVarGetRelidExtended(indexRelation,
-									  (params->options & REINDEXOPT_CONCURRENTLY) != 0 ?
-									  ShareUpdateExclusiveLock : AccessExclusiveLock,
+									  AccessShareLock,
 									  0,
 									  RangeVarCallbackForReindexIndex,
 									  &state);
@@ -2594,11 +2595,25 @@ ReindexIndex(RangeVar *indexRelation, ReindexParams *params, bool isTopLevel)
 	 */
 	persistence = get_rel_persistence(indOid);
 	relkind = get_rel_relkind(indOid);
+	if (persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		LOCKMODE	lockmode;
+
+		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0)
+			lockmode = ShareUpdateExclusiveLock;
+		else
+			lockmode = AccessExclusiveLock;
+
+		/* lock heap first */
+		Assert(OidIsValid(state.locked_table_oid));
+		LockRelationOid(state.locked_table_oid, lockmode);
+		LockRelationOid(indOid, lockmode);
+	}
 
 	if (relkind == RELKIND_PARTITIONED_INDEX)
 		ReindexPartitions(indOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 persistence != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(persistence))
 		ReindexRelationConcurrently(indOid, params);
 	else
 	{
@@ -2620,15 +2635,7 @@ RangeVarCallbackForReindexIndex(const RangeVar *relation,
 {
 	char		relkind;
 	struct ReindexIndexCallbackState *state = arg;
-	LOCKMODE	table_lockmode;
-
-	/*
-	 * Lock level here should match table lock in reindex_index() for
-	 * non-concurrent case and table locks used by index_concurrently_*() for
-	 * concurrent case.
-	 */
-	table_lockmode = (state->params.options & REINDEXOPT_CONCURRENTLY) != 0 ?
-		ShareUpdateExclusiveLock : ShareLock;
+	LOCKMODE	table_lockmode = state->lockmode;
 
 	/*
 	 * If we previously locked some other index's heap, and the name we're
@@ -2689,6 +2696,8 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 {
 	Oid			heapOid;
 	bool		result;
+	char		relpersistence;
+	int 		reindex_flags = 0;
 
 	/*
 	 * The lock level used here should match reindex_relation().
@@ -2699,15 +2708,27 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 	 * locks on our temporary table.
 	 */
 	heapOid = RangeVarGetRelidExtended(relation,
-									   (params->options & REINDEXOPT_CONCURRENTLY) != 0 ?
-									   ShareUpdateExclusiveLock : ShareLock,
+									   AccessShareLock,
 									   0,
 									   RangeVarCallbackOwnsTable, NULL);
 
+	relpersistence = get_rel_persistence(heapOid);
+	if (relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		LOCKMODE	lockmode;
+
+		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0)
+			lockmode = ShareUpdateExclusiveLock;
+		else
+			lockmode = ShareLock;
+
+		LockRelationOid(heapOid, lockmode);
+	}
+
 	if (get_rel_relkind(heapOid) == RELKIND_PARTITIONED_TABLE)
 		ReindexPartitions(heapOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(relpersistence))
 	{
 		result = ReindexRelationConcurrently(heapOid, params);
 
@@ -2721,9 +2742,14 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 		ReindexParams newparams = *params;
 
 		newparams.options |= REINDEXOPT_REPORT_PROGRESS;
+
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			reindex_flags |= REINDEX_REL_PROCESS_GLOBAL_TEMP;
+
+		reindex_flags |= REINDEX_REL_PROCESS_TOAST;
+		reindex_flags |= REINDEX_REL_CHECK_CONSTRAINTS;
 		result = reindex_relation(heapOid,
-								  REINDEX_REL_PROCESS_TOAST |
-								  REINDEX_REL_CHECK_CONSTRAINTS,
+								  reindex_flags,
 								  &newparams);
 		if (!result)
 			ereport(NOTICE,
@@ -3122,7 +3148,7 @@ ReindexMultipleInternal(List *relids, ReindexParams *params)
 			   relkind != RELKIND_PARTITIONED_TABLE);
 
 		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			relpersistence != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(relpersistence))
 		{
 			ReindexParams newparams = *params;
 
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 62465bacd81..ef37f79ba68 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -51,12 +51,32 @@ LockTableCommand(LockStmt *lockstmt)
 		RangeVar   *rv = (RangeVar *) lfirst(p);
 		bool		recurse = rv->inh;
 		Oid			reloid;
+		LOCKMODE	lockmode = lockstmt->mode;
+		char		relpersistence;
 
-		reloid = RangeVarGetRelidExtended(rv, lockstmt->mode,
-										  lockstmt->nowait ? RVR_NOWAIT : 0,
+		reloid = RangeVarGetRelidExtended(rv, NoLock, 0,
 										  RangeVarCallbackForLockTable,
 										  (void *) &lockstmt->mode);
 
+		relpersistence = get_rel_persistence(reloid);
+		if (relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			if (!lockstmt->nowait)
+				LockRelationOid(reloid, lockmode);
+			else if (!ConditionalLockRelationOid(reloid, lockmode))
+			{
+				/* try to throw error by name; relation could be deleted... */
+				char	   *relname = get_rel_name(reloid);
+
+				if (!relname)
+					return;		/* child concurrently dropped, just skip it */
+				ereport(ERROR,
+						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						 errmsg("could not obtain lock on relation \"%s\"",
+								relname)));
+			}
+		}
+
 		if (get_rel_relkind(reloid) == RELKIND_VIEW)
 			LockViewRecurse(reloid, lockstmt->mode, lockstmt->nowait, NIL);
 		else if (recurse)
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 72bfdc07a49..e5257f610f6 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -220,9 +223,12 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	rel = table_open(seqoid, AccessExclusiveLock);
 	tupDesc = RelationGetDescr(rel);
 
-	/* now initialize the sequence's data */
-	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	if (!RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		/* now initialize the sequence's data */
+		tuple = heap_form_tuple(tupDesc, value, null);
+		fill_seq_with_data(rel, tuple);
+	}
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -275,8 +281,6 @@ ResetSequence(Oid seq_relid)
 	Buffer		buf;
 	HeapTupleData seqdatatuple;
 	HeapTuple	tuple;
-	HeapTuple	pgstuple;
-	Form_pg_sequence pgsform;
 	int64		startv;
 
 	/*
@@ -287,12 +291,7 @@ ResetSequence(Oid seq_relid)
 	init_sequence(seq_relid, &elm, &seq_rel);
 	(void) read_seq_tuple(seq_rel, &buf, &seqdatatuple);
 
-	pgstuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seq_relid));
-	if (!HeapTupleIsValid(pgstuple))
-		elog(ERROR, "cache lookup failed for sequence %u", seq_relid);
-	pgsform = (Form_pg_sequence) GETSTRUCT(pgstuple);
-	startv = pgsform->seqstart;
-	ReleaseSysCache(pgstuple);
+	startv = get_seqence_start_value(seq_relid);
 
 	/*
 	 * Copy the existing sequence tuple.
@@ -451,6 +450,15 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 
 	init_sequence(relid, &elm, &seqrel);
 
+	if (RELATION_IS_GLOBAL_TEMP(seqrel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(seqrel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary sequence %s when other backend attached it.",
+						RelationGetRelationName(seqrel))));
+	}
+
 	rel = table_open(SequenceRelationId, RowExclusiveLock);
 	seqtuple = SearchSysCacheCopy1(SEQRELID,
 								   ObjectIdGetDatum(relid));
@@ -611,7 +619,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +944,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1153,6 +1161,14 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	/* Initializes the storage for sequence which the global temporary table belongs. */
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1927,3 +1943,58 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+/*
+ * Get the startValue of the sequence from syscache.
+ */
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+/*
+ * Initialize sequence which global temporary table belongs.
+ */
+void
+gtt_init_seq(Relation rel)
+{
+	Datum		value[SEQ_COL_LASTCOL];
+	bool		null[SEQ_COL_LASTCOL];
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL - 1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+	null[SEQ_COL_LASTVAL - 1] = false;
+
+	value[SEQ_COL_LOG - 1] = Int64GetDatum((int64)0);
+	null[SEQ_COL_LOG - 1] = false;
+
+	value[SEQ_COL_CALLED - 1] = BoolGetDatum(false);
+	null[SEQ_COL_CALLED - 1] = false;
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1c2ebe1bf69..178575e6062 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -118,6 +119,7 @@ typedef struct OnCommitItem
 	 */
 	SubTransactionId creating_subid;
 	SubTransactionId deleting_subid;
+	bool			 is_global_temp;
 } OnCommitItem;
 
 static List *on_commits = NIL;
@@ -602,7 +604,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
 static char GetAttributeCompression(Oid atttypid, char *compression);
-
+static OnCommitAction gtt_oncommit_option(List *options);
 
 /* ----------------------------------------------------------------
  *		DefineRelation
@@ -647,6 +649,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -658,7 +661,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * Check consistency of arguments
 	 */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !RelpersistenceTsTemp(stmt->relation->relpersistence))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -688,7 +691,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	if (RelpersistenceTsTemp(stmt->relation->relpersistence)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -789,6 +792,50 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* For global temporary table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		if (!(relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE))
+			elog(ERROR, "Only support global temporary regular table.");
+
+		/* Check parent table */
+		if (inheritOids)
+			elog(ERROR, "Not support global temporary partition table or inherit table.");
+
+		/* Check oncommit clause and save to reloptions */
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temporary table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1415,7 +1462,7 @@ RemoveRelations(DropStmt *drop)
 		 * relation persistence cannot be known without its OID.
 		 */
 		if (drop->concurrent &&
-			get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(get_rel_persistence(relOid)))
 		{
 			Assert(list_length(drop->objects) == 1 &&
 				   drop->removeType == OBJECT_INDEX);
@@ -1624,9 +1671,9 @@ ExecuteTruncate(TruncateStmt *stmt)
 		Relation	rel;
 		bool		recurse = rv->inh;
 		Oid			myrelid;
-		LOCKMODE	lockmode = AccessExclusiveLock;
+		LOCKMODE	lockmode;
 
-		myrelid = RangeVarGetRelidExtended(rv, lockmode,
+		myrelid = RangeVarGetRelidExtended(rv, AccessShareLock,
 										   0, RangeVarCallbackForTruncate,
 										   NULL);
 
@@ -1634,9 +1681,21 @@ ExecuteTruncate(TruncateStmt *stmt)
 		if (list_member_oid(relids, myrelid))
 			continue;
 
-		/* open the relation, we already hold a lock on it */
+		/* open the relation, we need hold a low-level lock first */
 		rel = table_open(myrelid, NoLock);
 
+		/*
+		 * Truncate global temp table only cleans up the data in current backend,
+		 * only low-level locks are required.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel))
+			lockmode = AccessShareLock;
+		else
+		{
+			lockmode = AccessExclusiveLock;
+			LockRelationOid(myrelid, lockmode);
+		}
+
 		/*
 		 * RangeVarGetRelidExtended() has done most checks with its callback,
 		 * but other checks with the now-opened Relation remain.
@@ -1886,6 +1945,7 @@ ExecuteTruncateGuts(List *explicit_rels,
 	foreach(cell, rels)
 	{
 		Relation	rel = (Relation) lfirst(cell);
+		LOCKMODE	lockmode;
 
 		/* Skip partitioned tables as there is nothing to do */
 		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
@@ -1936,6 +1996,19 @@ ExecuteTruncateGuts(List *explicit_rels,
 			continue;
 		}
 
+		/*
+		 * Skip the global temporary table that is not initialized for storage
+		 * in current backend.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel))
+		{
+			lockmode = AccessShareLock;
+			if (!gtt_storage_attached(RelationGetRelid(rel)))
+				continue;
+		}
+		else
+			lockmode = AccessExclusiveLock;
+
 		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
@@ -1954,6 +2027,7 @@ ExecuteTruncateGuts(List *explicit_rels,
 			Oid			heap_relid;
 			Oid			toast_relid;
 			ReindexParams reindex_params = {0};
+			int			reindex_flags = 0;
 
 			/*
 			 * This effectively deletes all rows in the table, and may be done
@@ -1981,17 +2055,21 @@ ExecuteTruncateGuts(List *explicit_rels,
 			if (OidIsValid(toast_relid))
 			{
 				Relation	toastrel = relation_open(toast_relid,
-													 AccessExclusiveLock);
+													 lockmode);
 
 				RelationSetNewRelfilenode(toastrel,
 										  toastrel->rd_rel->relpersistence);
 				table_close(toastrel, NoLock);
 			}
 
+			reindex_flags = REINDEX_REL_PROCESS_TOAST;
+			if (RELATION_IS_GLOBAL_TEMP(rel))
+				reindex_flags |= REINDEX_REL_PROCESS_GLOBAL_TEMP;
+
 			/*
 			 * Reconstruct the indexes to match, and we're done.
 			 */
-			reindex_relation(heap_relid, REINDEX_REL_PROCESS_TOAST,
+			reindex_relation(heap_relid, reindex_flags,
 							 &reindex_params);
 		}
 
@@ -3998,6 +4076,16 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temporary table only current backend use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -5329,6 +5417,24 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 
 			rel = table_open(tab->relid, NoLock);
 			find_composite_type_dependencies(rel->rd_rel->reltype, rel, NULL);
+
+			if (RELATION_IS_GLOBAL_TEMP(rel) && tab->rewrite > 0)
+			{
+				if (tab->chgPersistence)
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot change global temporary table persistence setting")));
+
+				if(gtt_storage_attached(tab->relid))
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("Only support alter global temporary table in an empty context."),
+						 errhint("Please create a new connection and execute ALTER TABLE on the new connection.")));
+
+				/* There is no need to override the whole temp table */
+				tab->rewrite = 0;
+			}
+
 			table_close(rel, NoLock);
 		}
 
@@ -5380,6 +5486,8 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot rewrite temporary tables of other sessions")));
 
+			Assert(!RELATION_IS_GLOBAL_TEMP(OldHeap));
+
 			/*
 			 * Select destination tablespace (same as original unless user
 			 * requested a change)
@@ -9011,6 +9119,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -13709,6 +13823,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -13908,6 +14025,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temporary table");
+
 	/* Check first if relation can be moved to new tablespace */
 	if (!CheckRelationTableSpaceMove(rel, newTableSpace))
 	{
@@ -14211,7 +14331,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(RelationGetSmgr(rel), dstrel, MAIN_FORKNUM,
@@ -15809,6 +15929,7 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
 	switch (rel->rd_rel->relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -16223,7 +16344,7 @@ AlterSeqNamespaces(Relation classRel, Relation rel,
  * Register a newly-created relation's ON COMMIT action.
  */
 void
-register_on_commit_action(Oid relid, OnCommitAction action)
+register_on_commit_action(Oid relid, OnCommitAction action, bool is_gloal_temp)
 {
 	OnCommitItem *oc;
 	MemoryContext oldcxt;
@@ -16242,6 +16363,7 @@ register_on_commit_action(Oid relid, OnCommitAction action)
 	oc->oncommit = action;
 	oc->creating_subid = GetCurrentSubTransactionId();
 	oc->deleting_subid = InvalidSubTransactionId;
+	oc->is_global_temp = is_gloal_temp;
 
 	/*
 	 * We use lcons() here so that ON COMMIT actions are processed in reverse
@@ -16287,6 +16409,7 @@ PreCommit_on_commit_actions(void)
 	ListCell   *l;
 	List	   *oids_to_truncate = NIL;
 	List	   *oids_to_drop = NIL;
+	List	   *oids_to_truncate_gtt = NIL;
 
 	foreach(l, on_commits)
 	{
@@ -16310,7 +16433,12 @@ PreCommit_on_commit_actions(void)
 				 * tables, as they must still be empty.
 				 */
 				if ((MyXactFlags & XACT_FLAGS_ACCESSEDTEMPNAMESPACE))
-					oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid);
+				{
+					if (oc->is_global_temp)
+						oids_to_truncate_gtt = lappend_oid(oids_to_truncate_gtt, oc->relid);
+					else
+						oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid);
+				}
 				break;
 			case ONCOMMIT_DROP:
 				oids_to_drop = lappend_oid(oids_to_drop, oc->relid);
@@ -16327,7 +16455,10 @@ PreCommit_on_commit_actions(void)
 	 * exists at truncation time.
 	 */
 	if (oids_to_truncate != NIL)
-		heap_truncate(oids_to_truncate);
+		heap_truncate(oids_to_truncate, false);
+
+	if (oids_to_truncate_gtt != NIL)
+		heap_truncate(oids_to_truncate_gtt, true);
 
 	if (oids_to_drop != NIL)
 	{
@@ -17326,6 +17457,13 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot attach temporary relation of another session as partition")));
 
+	/* If the parent is permanent, so must be all of its partitions. */
+	if (attachrel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach a global temporary relation as partition of permanent relation \"%s\"",
+						RelationGetRelationName(rel))));
+
 	/* Check if there are any columns in attachrel that aren't in the parent */
 	tupleDesc = RelationGetDescr(attachrel);
 	natts = tupleDesc->natts;
@@ -18761,3 +18899,40 @@ GetAttributeCompression(Oid atttypid, char *compression)
 
 	return cmethod;
 }
+
+/*
+ * Parse the on commit clause for the temporary table
+ */
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			bool	res = false;
+			char	*sval = defGetString(def);
+
+			/* It has to be a Boolean value */
+			if (!parse_bool(sval, &res))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
+
+			if (res)
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 5c4bc15b441..3a861c47946 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"
@@ -1315,6 +1316,27 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = RELATION_IS_GLOBAL_TEMP(relation);
+
+	 /* For global temporary table */
+	if (is_gtt)
+	{
+		/* Store relation statistics and transaction information to the localhash */
+		up_gtt_relstats(RelationGetRelid(relation),
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+
+		/* Update relation statistics to local relcache */
+		relation->rd_rel->relpages = (int32) num_pages;
+		relation->rd_rel->reltuples = (float4) num_tuples;
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+		if (TransactionIdIsNormal(frozenxid))
+			relation->rd_rel->relfrozenxid = frozenxid;
+
+		if (MultiXactIdIsValid(minmulti))
+			relation->rd_rel->relminmxid = minmulti;
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1328,17 +1350,23 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (!is_gtt &&
+		pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (!is_gtt &&
+		pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (!is_gtt &&
+		pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1351,7 +1379,15 @@ vac_update_relstats(Relation relation,
 		/*
 		 * If we didn't find any indexes, reset relhasindex.
 		 */
-		if (pgcform->relhasindex && !hasindex)
+		if (is_gtt &&
+			RelationGetIndexList(relation) != NIL)
+		{
+			/*
+			 * Global temporary tables may contain indexes that are not valid locally.
+			 * The catalog should not be updated based on local invalid index.
+			 */
+		}
+		else if (pgcform->relhasindex && !hasindex)
 		{
 			pgcform->relhasindex = false;
 			dirty = true;
@@ -1383,7 +1419,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNextTransactionId(),
@@ -1394,7 +1431,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1502,6 +1540,13 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/*
+		 * The relfrozenxid for a global temporary talble is stored in localhash,
+		 * not pg_class, See list_all_session_gtt_frozenxids()
+		 */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1559,6 +1604,43 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/* If enable global temporary table */
+	if (max_active_gtt > 0)
+	{
+		TransactionId	safe_age;
+		/*  */
+		TransactionId	oldest_gtt_frozenxid =
+			list_all_backend_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid))
+		{
+			safe_age = oldest_gtt_frozenxid + vacuum_gtt_defer_check_age;
+			if (safe_age < FirstNormalTransactionId)
+				safe_age += FirstNormalTransactionId;
+
+			/*
+			 * We tolerate that the minimum age of gtt is less than
+			 * the minimum age of conventional tables, otherwise it will
+			 * throw warning message.
+			 */
+			if (TransactionIdIsNormal(safe_age) &&
+				TransactionIdPrecedes(safe_age, newFrozenXid))
+			{
+				ereport(WARNING,
+					(errmsg("global temp table oldest relfrozenxid %u is the oldest in the entire db",
+							oldest_gtt_frozenxid),
+					 errdetail("The oldest relfrozenxid in pg_class is %u", newFrozenXid),
+					 errhint("If they differ greatly, please consider cleaning up the data in global temp table.")));
+			}
+
+			/*
+			 * We need to ensure that the clog required by gtt is not cleand.
+			 */
+			if (TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+				newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1910,6 +1992,19 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	/*
+	 * Skip those global temporary table that are not initialized in
+	 * current backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel) &&
+		!gtt_storage_attached(RelationGetRelid(rel)))
+	{
+		relation_close(rel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 4df05a0b33d..4c181e2e14e 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -527,6 +527,12 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	/* Global temporary table are not sensible. */
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b3ce4bae530..611e3f18a70 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -784,6 +784,10 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		/* This is one kind of temp table */
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 5c723bc54e1..191e0f6fd21 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d328856ae5b..4e2bbb224cc 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -38,6 +38,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -633,6 +634,9 @@ ExecInsert(ModifyTableState *mtstate,
 		resultRelInfo->ri_IndexRelationDescs == NULL)
 		ExecOpenIndices(resultRelInfo, onconflict != ONCONFLICT_NONE);
 
+	/* Init storage for global temporary table in current backend */
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	/*
 	 * BEFORE ROW INSERT Triggers.
 	 *
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 296dd75c1b6..d971aea2546 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -48,7 +48,7 @@
 #include "partitioning/partprune.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/lsyscache.h"
-
+#include "utils/rel.h"
 
 /* results of subquery_is_pushdown_safe */
 typedef struct pushdown_safety_info
@@ -619,7 +619,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			if (RelpersistenceTsTemp(get_rel_persistence(rte->relid)))
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index bd01ec0526f..ff4e81ca2cf 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6071,7 +6071,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
-	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+	if (RELATION_IS_TEMP(heap) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index c5194fdbbf2..38d7c658541 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -30,6 +30,7 @@
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temporary table in current backend */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 146ee8dd1ea..2d4e9393f00 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2907,6 +2907,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 08f1bf1031c..d6048f2ee57 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3410,17 +3410,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; }
@@ -11683,19 +11677,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 c5c3f26ecf1..2a2b2789077 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -82,6 +82,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);
 
 
 /*
@@ -3665,3 +3666,53 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * Like function isQueryUsingTempRelation_walker
+ * return true if any relation underlying
+ * the query is a global temporary table.
+ */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* Check if the query uses global temporary table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 313d7b6ff02..38e0e162e82 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -447,6 +447,13 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	/*
+	 * If a sequence is bound to a global temporary table, then the sequence
+	 * must been "global temporary"
+	 */
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
@@ -3326,6 +3333,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		cxt.isforeign = false;
 	}
 	cxt.relation = stmt->relation;
+	/* Sets the table persistence to the context */
+	cxt.relation->relpersistence = RelationGetRelPersistence(rel);
 	cxt.rel = rel;
 	cxt.inhRelations = NIL;
 	cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 96332320a73..75ed4d0ae9e 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2115,6 +2115,14 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/*
+			 * Aotuvacuum cannot vacuum the private data stored in each backend
+			 * that belongs to global temporary table, so skip them.
+			 */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2181,7 +2189,7 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		if (RelpersistenceTsTemp(classForm->relpersistence))
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 08ebabfe96a..c346c59c7f4 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlogutils.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -2934,6 +2935,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * Returns 0 if this global temporary table is not initialized in current
+	 * backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 9fa3e0631e6..cc3eb928bc6 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -23,6 +23,7 @@
 #include "access/syncscan.h"
 #include "access/twophase.h"
 #include "commands/async.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
@@ -143,6 +144,7 @@ CalculateShmemSize(int *num_semaphores)
 	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
@@ -246,6 +248,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* For global temporary table shared hashtable */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index bd3c7a47fe2..22481374778 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -65,6 +65,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -5131,3 +5132,78 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temporary table.
+ */
+int
+list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct		*arrayP = procArray;
+	TransactionId		result = InvalidTransactionId;
+	int			index;
+	uint8			flags = 0;
+	int			i = 0;
+
+	/* return 0 if feature is disabled */
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+		*n = 0;
+	}
+
+	/* Disable in standby node */
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		uint8           statusFlags = ProcGlobal->statusFlags[index];
+
+		if (statusFlags & flags)
+			continue;
+
+		/* Fetch all backend that is belonging to MyDatabaseId */
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->backend_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->backend_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->backend_gtt_frozenxid, result))
+				result = proc->backend_gtt_frozenxid;
+
+			/* save backend pid and backend level oldest relfrozenxid */
+			if (max_size > 0)
+			{
+				pids[i] = proc->pid;
+				xids[i] = proc->backend_gtt_frozenxid;
+				i++;
+			}
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (max_size > 0)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 862097352bb..4edd3b31f7a 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -176,7 +176,9 @@ static const char *const BuiltinTrancheNames[] = {
 	/* LWTRANCHE_PARALLEL_APPEND: */
 	"ParallelAppend",
 	/* LWTRANCHE_PER_XACT_PREDICATE_LIST: */
-	"PerXactPredicateList"
+	"PerXactPredicateList",
+	/* LWTRANCHE_GTT_CTL */
+	"GlobalTempTableControl"
 };
 
 StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index b7d9da0aa9f..8051f2053f9 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -393,6 +393,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;	/* init backend level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
@@ -578,6 +579,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;	/* init backend level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index d5a7fb13f3c..8225cf6219f 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -982,6 +982,13 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/*
+			 * For global temporary table ,each backend has its own storage,
+			 * also only sees its own storage. Use Backendid to identify them.
+			 */
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 10895fb2876..66255eb7604 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -108,6 +108,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -5115,12 +5116,26 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								/* For global temporary table, get statistic data from localhash */
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -5368,15 +5383,28 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6820,6 +6848,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;
@@ -6837,6 +6866,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6848,6 +6885,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;
@@ -6863,6 +6902,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7781,6 +7828,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))
@@ -7793,6 +7842,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7805,6 +7863,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.
@@ -7824,6 +7884,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 4ebaa552a27..78c33d2ac87 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -3113,6 +3114,19 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		/* For global temporary table, get statistic data from localhash */
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3e..1862a8c8952 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -65,6 +65,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1116,6 +1117,36 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+				TransactionId	relfrozenxid = InvalidTransactionId;
+				MultiXactId 	relminmxid = InvalidMultiXactId;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+
+				/* For global temporary table, get relstat data from localhash */
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								&relfrozenxid,
+								&relminmxid);
+
+				/* And put them to local relcache */
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+				if (TransactionIdIsNormal(relfrozenxid))
+					relation->rd_rel->relfrozenxid = relfrozenxid;
+
+				if (MultiXactIdIsValid(relminmxid))
+					relation->rd_rel->relminmxid = relminmxid;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1173,6 +1204,8 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			/* The state of the global temporary table's index may need to be set */
+			gtt_fix_index_backend_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -1300,7 +1333,22 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+			/*
+			 * For global temporary table, get the latest relfilenode
+			 * from localhash and put it in relcache.
+			 */
+			if (OidIsValid(newrelnode) &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2251,6 +2299,9 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		/* The state of the global temporary table's index may need to be set */
+		gtt_fix_index_backend_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3489,6 +3540,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3598,28 +3653,39 @@ void
 RelationSetNewRelfilenode(Relation relation, char persistence)
 {
 	Oid			newrelfilenode;
-	Relation	pg_class;
-	HeapTuple	tuple;
+	Relation	pg_class = NULL;
+	HeapTuple	tuple = NULL;
 	Form_pg_class classform;
 	MultiXactId minmulti = InvalidMultiXactId;
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
 
+	/*
+	 * For global temporary table, storage information for the table is
+	 * maintained locally, not in catalog.
+	 */
+	bool		update_catalog = !RELATION_IS_GLOBAL_TEMP(relation);
+
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
 
-	/*
-	 * Get a writable copy of the pg_class tuple for the given relation.
-	 */
-	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+	memset(&classform, 0, sizeof(classform));
 
-	tuple = SearchSysCacheCopy1(RELOID,
-								ObjectIdGetDatum(RelationGetRelid(relation)));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "could not find tuple for relation %u",
-			 RelationGetRelid(relation));
-	classform = (Form_pg_class) GETSTRUCT(tuple);
+	if (update_catalog)
+	{
+		/*
+		 * Get a writable copy of the pg_class tuple for the given relation.
+		 */
+		pg_class = table_open(RelationRelationId, RowExclusiveLock);
+
+		tuple = SearchSysCacheCopy1(RELOID,
+									ObjectIdGetDatum(RelationGetRelid(relation)));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u",
+				 RelationGetRelid(relation));
+		classform = (Form_pg_class) GETSTRUCT(tuple);
+	}
 
 	/*
 	 * Schedule unlinking of the old storage at transaction commit.
@@ -3645,7 +3711,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;
@@ -3665,6 +3731,18 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 			break;
 	}
 
+	/* For global temporary table */
+	if (!update_catalog)
+	{
+		Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+		Assert(RELATION_IS_GLOBAL_TEMP(relation));
+		Assert(!RelationIsMapped(relation));
+
+		/* Make cache invalid and set new relnode to local cache. */
+		CacheInvalidateRelcache(relation);
+		relation->rd_node.relNode = relnode;
+	}
 	/*
 	 * If we're dealing with a mapped index, pg_class.relfilenode doesn't
 	 * change; instead we have to send the update to the relation mapper.
@@ -3674,7 +3752,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	 * possibly-inaccurate values of relpages etc, but those will be fixed up
 	 * later.
 	 */
-	if (RelationIsMapped(relation))
+	else if (RelationIsMapped(relation))
 	{
 		/* This case is only supported for indexes */
 		Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3720,9 +3798,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 		CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 	}
 
-	heap_freetuple(tuple);
+	if (update_catalog)
+	{
+		heap_freetuple(tuple);
 
-	table_close(pg_class, RowExclusiveLock);
+		table_close(pg_class, RowExclusiveLock);
+	}
 
 	/*
 	 * Make the pg_class row change or relation map change visible.  This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index d2ce4a84506..7717ee2ca13 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -44,6 +44,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "commands/prepare.h"
 #include "commands/trigger.h"
@@ -152,6 +153,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temporary table feature.
+ * table schema are still saved in catalog.
+ *
+ * num > 0 means allows the database to manage multiple active tables at the same time.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2127,6 +2140,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 "
@@ -2697,6 +2719,16 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"vacuum_gtt_defer_check_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("The defer check age of GTT, used to check expired data after vacuum."),
+			NULL
+		},
+		&vacuum_gtt_defer_check_age,
+		10000, 0, 1000000,
+		NULL, NULL, NULL
+	},
+
 	/*
 	 * See also CheckRequiredParameterValues() if this parameter changes
 	 */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a485fb2d070..80658c4f3ba 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2503,6 +2503,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
 		dopt->no_unlogged_table_data)
 		return;
 
+	/* Don't dump data in global temporary table/sequence */
+	if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		return;
+
 	/* Check that the data is not explicitly excluded */
 	if (simple_oid_list_member(&tabledata_exclude_oids,
 							   tbinfo->dobj.catId.oid))
@@ -15961,6 +15965,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
 		char	   *foreign = "";
+		char	   *table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -16014,9 +16019,15 @@ dumpTableSchema(Archive *fout, const 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);
 
@@ -16380,6 +16391,15 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 			}
 		}
 
+		/*
+		 * Transaction information for the global temporary table is not stored
+		 * in the pg_class.
+		 */
+		if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			Assert(tbinfo->frozenxid == 0);
+			Assert(tbinfo->minmxid == 0);
+		}
 		/*
 		 * In binary_upgrade mode, arrange to restore the old relfrozenxid and
 		 * relminmxid of all vacuumable relations.  (While vacuum.c processes
@@ -16387,7 +16407,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		 * of other relations; so this "if" lacks RELKIND_TOASTVALUE, and the
 		 * child toast table is handled below.)
 		 */
-		if (dopt->binary_upgrade &&
+		else if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
 			 tbinfo->relkind == RELKIND_MATVIEW))
 		{
@@ -17390,6 +17410,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	PQExpBuffer query = createPQExpBuffer();
 	PQExpBuffer delqry = createPQExpBuffer();
 	char	   *qseqname;
+	bool	   global_temp_seq = false;
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
@@ -17399,9 +17420,12 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
-						  "FROM pg_catalog.pg_sequence "
-						  "WHERE seqrelid = '%u'::oid",
+						  "seqcache, seqcycle, "
+						  "c.relpersistence "
+						  "FROM pg_catalog.pg_sequence s, "
+						  "pg_catalog.pg_class c "
+						  "WHERE seqrelid = '%u'::oid "
+						  "and s.seqrelid = c.oid",
 						  tbinfo->dobj.catId.oid);
 	}
 	else if (fout->remoteVersion >= 80400)
@@ -17446,6 +17470,9 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
 
+	if (fout->remoteVersion >= 140000)
+		global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
 	if (strcmp(seqtype, "smallint") == 0)
@@ -17523,9 +17550,13 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	}
 	else
 	{
-		appendPQExpBuffer(query,
-						  "CREATE SEQUENCE %s\n",
-						  fmtQualifiedDumpable(tbinfo));
+		appendPQExpBuffer(query, "CREATE ");
+
+		if (global_temp_seq)
+			appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+		appendPQExpBuffer(query, "SEQUENCE %s\n",
+								  fmtQualifiedDumpable(tbinfo));
 
 		if (strcmp(seqtype, "bigint") != 0)
 			appendPQExpBuffer(query, "    AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index ad5f3919956..f3819860096 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -88,7 +88,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -178,7 +178,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 5d9a26cf822..2de11d5d707 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -447,8 +449,17 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
 			 "         ON c.relnamespace = n.oid "
 			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
+			 CppAsString2(RELKIND_MATVIEW) ") AND ");
+
+	if (skip_gtt)
+	{
+		/* exclude global temp tables */
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+			"    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND ");
+	}
+
 	/* exclude possible orphaned temp tables */
+	snprintf(query + strlen(query), sizeof(query) - strlen(query),
 			 "    ((n.nspname !~ '^pg_temp_' AND "
 			 "      n.nspname !~ '^pg_toast_temp_' AND "
 			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 3628bd74a7b..bbb9b5ea13d 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -407,7 +407,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -645,7 +645,10 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+			/* exclude global temp tables */
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -656,7 +659,10 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+		/* exclude global temp tables */
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index ca0795f68ff..018a2effd4b 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -389,7 +389,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index ea4ca5c05c2..68d04e554ee 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -4066,7 +4066,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ecae9df8eda..d226b2dfef9 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1059,6 +1059,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2542,6 +2544,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2755,6 +2760,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE", "SEQUENCE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 6ce480b49c1..dda3f3c5a60 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -85,7 +85,7 @@ extern Oid	heap_create_with_catalog(const char *relname,
 
 extern void heap_drop_with_catalog(Oid relid);
 
-extern void heap_truncate(List *relids);
+extern void heap_truncate(List *relids, bool is_global_temp);
 
 extern void heap_truncate_one_rel(Relation rel);
 
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 008f723e104..875b1003899 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -157,6 +157,7 @@ extern void reindex_index(Oid indexId, bool skip_constraint_checks,
 #define REINDEX_REL_CHECK_CONSTRAINTS		0x04
 #define REINDEX_REL_FORCE_INDEXES_UNLOGGED	0x08
 #define REINDEX_REL_FORCE_INDEXES_PERMANENT 0x10
+#define REINDEX_REL_PROCESS_GLOBAL_TEMP		0x20
 
 extern bool reindex_relation(Oid relid, int flags, ReindexParams *params);
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index fef9945ed8f..9176b7dcc07 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -172,6 +172,7 @@ DECLARE_INDEX(pg_class_tblspc_relfilenode_index, 3455, ClassTblspcRelfilenodeInd
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index d068d6532ec..fd5089388f2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5735,6 +5735,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '9874',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '9875',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '9876',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '9877',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 0ab32b44e91..92e9f8ba485 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,7 +22,7 @@
 /* GUC variables */
 extern int	wal_skip_threshold;
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationPreTruncate(Relation rel);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 00000000000..8a3d9558712
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,45 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern int		vacuum_gtt_defer_check_age;
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Oid relid,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void force_enable_gtt_index(Relation index);
+extern void gtt_fix_index_backend_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index 40544dd4c70..7b66d808fc5 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 336549cc5f0..3e8167134b7 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -86,7 +86,7 @@ extern void find_composite_type_dependencies(Oid typeOid,
 
 extern void check_of_type(HeapTuple typetuple);
 
-extern void register_on_commit_action(Oid relid, OnCommitAction action);
+extern void register_on_commit_action(Oid relid, OnCommitAction action, bool is_gloal_temp);
 extern void remove_on_commit_action(Oid relid);
 
 extern void PreCommit_on_commit_actions(void);
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 8336c2c5a29..bddcfe7256d 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index c86ccdaf608..6b395551c1d 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -399,6 +399,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index a8f052e4845..4b4ed1a13aa 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -189,6 +189,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_SHARED_TIDBITMAP,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_PER_XACT_PREDICATE_LIST,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index be67d8a8616..e2f8bb5162d 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -157,6 +157,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId backend_gtt_frozenxid;	/* backend level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index b01fa52139a..8efffa55ac5 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -94,4 +94,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index aa18d304ac0..524c9d7de3f 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -288,6 +288,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 b4faa1c1238..a74558a8383 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	SMgrRelation rd_smgr;		/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -326,6 +326,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -608,11 +609,13 @@ RelationGetSmgr(Relation rel)
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -620,6 +623,7 @@ RelationGetSmgr(Relation rel)
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -632,6 +636,30 @@ RelationGetSmgr(Relation rel)
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP_ON_CURRENT_SESSION
+ *		Test a rel is either local temp relation of this session
+ *		or global temp relation.
+ */
+#define RELATION_IS_TEMP_ON_CURRENT_SESSION(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is local temp relation or global temporary relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RelpersistenceTsTemp
+ *		Test a relpersistence is local temp relation or global temporary relation.
+ */
+#define RelpersistenceTsTemp(relpersistence) \
+	(relpersistence == RELPERSISTENCE_TEMP || \
+	 relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -677,6 +705,19 @@ RelationGetSmgr(Relation rel)
 	 (relation)->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&	\
 	 !IsCatalogRelation(relation))
 
+/* For global temporary table */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/* Get on commit clause value only for global temporary table */
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+/* Get relpersistence for relation */
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
-- 
2.30.1 (Apple Git-130)

#336Andrew Bille
andrewbille@gmail.com
In reply to: wenjing (#335)
Re: [Proposal] Global temporary tables

Thanks, the vacuum is fixed

But I found another crash (on v57 patches), reproduced with:

psql -t -c "create global temp table t (a integer); insert into t values
(1); select count(*) from t group by t;"
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
connection to server was lost

with trace:

[New LWP 2580215]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Core was generated by `postgres: andrew postgres [local] SELECT
'.
Program terminated with signal SIGABRT, Aborted.
#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
50 ../sysdeps/unix/sysv/linux/raise.c: No such file or directory.
(gdb) bt
#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
#1 0x00007f258d482859 in __GI_abort () at abort.c:79
#2 0x000055ad0be8878f in ExceptionalCondition
(conditionName=conditionName@entry=0x55ad0bf19743
"gtt_rnode->att_stat_tups[i]", errorType=errorType@entry=0x55ad0bee500b
"FailedAssertion", fileName=fileName@entry=0x55ad0bf1966b "storage_gtt.c",
lineNumber=lineNumber@entry=902) at assert.c:69
#3 0x000055ad0ba9379f in get_gtt_att_statistic (reloid=<optimized out>,
attnum=0, inh=<optimized out>) at storage_gtt.c:902
#4 0x000055ad0be35625 in examine_simple_variable
(root=root@entry=0x55ad0c498748,
var=var@entry=0x55ad0c498c68, vardata=vardata@entry=0x7fff06c9ebf0) at
selfuncs.c:5391
#5 0x000055ad0be36a89 in examine_variable (root=root@entry=0x55ad0c498748,
node=node@entry=0x55ad0c498c68, varRelid=varRelid@entry=0,
vardata=vardata@entry=0x7fff06c9ebf0) at selfuncs.c:4990
#6 0x000055ad0be3ad64 in estimate_num_groups (root=root@entry=0x55ad0c498748,
groupExprs=<optimized out>, input_rows=input_rows@entry=255,
pgset=pgset@entry=0x0, estinfo=estinfo@entry=0x0) at selfuncs.c:3455
#7 0x000055ad0bc50835 in get_number_of_groups (root=root@entry=0x55ad0c498748,
path_rows=255, gd=gd@entry=0x0, target_list=0x55ad0c498bb8) at
planner.c:3241
#8 0x000055ad0bc5576f in create_ordinary_grouping_paths
(root=root@entry=0x55ad0c498748,
input_rel=input_rel@entry=0x55ad0c3ce148,
grouped_rel=grouped_rel@entry=0x55ad0c4983f0,
agg_costs=agg_costs@entry=0x7fff06c9edb0, gd=gd@entry=0x0,
extra=extra@entry=0x7fff06c9ede0,
partially_grouped_rel_p=0x7fff06c9eda8)
at planner.c:3628
#9 0x000055ad0bc55a72 in create_grouping_paths
(root=root@entry=0x55ad0c498748,
input_rel=input_rel@entry=0x55ad0c3ce148, target=target@entry=0x55ad0c4c95d8,
target_parallel_safe=target_parallel_safe@entry=true, gd=gd@entry=0x0) at
planner.c:3377
#10 0x000055ad0bc5686d in grouping_planner (root=root@entry=0x55ad0c498748,
tuple_fraction=<optimized out>, tuple_fraction@entry=0) at planner.c:1592
#11 0x000055ad0bc57910 in subquery_planner (glob=glob@entry=0x55ad0c497880,
parse=parse@entry=0x55ad0c3cdbb8, parent_root=parent_root@entry=0x0,
hasRecursion=hasRecursion@entry=false, tuple_fraction=tuple_fraction@entry=0)
at planner.c:1025
#12 0x000055ad0bc57f36 in standard_planner (parse=0x55ad0c3cdbb8,
query_string=<optimized out>, cursorOptions=2048, boundParams=0x0) at
planner.c:406
#13 0x000055ad0bc584d4 in planner (parse=parse@entry=0x55ad0c3cdbb8,
query_string=query_string@entry=0x55ad0c3cc470 "create global temp table t
(a integer); insert into t values (1); select count(*) from t group by t;",
cursorOptions=cursorOptions@entry=2048, boundParams=boundParams@entry=0x0)
at planner.c:277
#14 0x000055ad0bd4855f in pg_plan_query
(querytree=querytree@entry=0x55ad0c3cdbb8,
query_string=query_string@entry=0x55ad0c3cc470 "create global temp table t
(a integer); insert into t values (1); select count(*) from t group by t;",
cursorOptions=cursorOptions@entry=2048, boundParams=boundParams@entry=0x0)
at postgres.c:847
#15 0x000055ad0bd4863b in pg_plan_queries (querytrees=0x55ad0c4986f0,
query_string=query_string@entry=0x55ad0c3cc470 "create global temp table t
(a integer); insert into t values (1); select count(*) from t group by t;",
cursorOptions=cursorOptions@entry=2048, boundParams=boundParams@entry=0x0)
at postgres.c:939
#16 0x000055ad0bd48b20 in exec_simple_query
(query_string=query_string@entry=0x55ad0c3cc470
"create global temp table t (a integer); insert into t values (1); select
count(*) from t group by t;") at postgres.c:1133
#17 0x000055ad0bd4aaf3 in PostgresMain (dbname=<optimized out>,
username=<optimized out>) at postgres.c:4497
#18 0x000055ad0bca3f91 in BackendRun (port=port@entry=0x55ad0c3f1020) at
postmaster.c:4560
#19 0x000055ad0bca7115 in BackendStartup (port=port@entry=0x55ad0c3f1020)
at postmaster.c:4288
#20 0x000055ad0bca735c in ServerLoop () at postmaster.c:1801
#21 0x000055ad0bca893e in PostmasterMain (argc=3, argv=<optimized out>) at
postmaster.c:1473
#22 0x000055ad0bbe8e31 in main (argc=3, argv=0x55ad0c3c6660) at main.c:198

On Thu, Oct 21, 2021 at 4:25 PM wenjing <wjzeng2012@gmail.com> wrote:

Show quoted text

Andrew Bille <andrewbille@gmail.com> 于2021年10月20日周三 上午2:59写道:

Another thanks for the fix. It works for me.

But I found another crash!

This is a check code that was added this year, but it did find a problem
and I fixed it.
Please review the new code(v57) again.

#337wenjing
wjzeng2012@gmail.com
In reply to: Andrew Bille (#336)
4 attachment(s)
Re: [Proposal] Global temporary tables

Andrew Bille <andrewbille@gmail.com> 于2021年10月23日周六 下午9:22写道:

Thanks, the vacuum is fixed

But I found another crash (on v57 patches), reproduced with:

psql -t -c "create global temp table t (a integer); insert into t values
(1); select count(*) from t group by t;"
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
connection to server was lost

I missed whole row and system column. It has been fixed in v58.

Please review the new code(v58) again

Wenjing

with trace:

Show quoted text

[New LWP 2580215]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Core was generated by `postgres: andrew postgres [local] SELECT
'.
Program terminated with signal SIGABRT, Aborted.
#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
50 ../sysdeps/unix/sysv/linux/raise.c: No such file or directory.
(gdb) bt
#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
#1 0x00007f258d482859 in __GI_abort () at abort.c:79
#2 0x000055ad0be8878f in ExceptionalCondition
(conditionName=conditionName@entry=0x55ad0bf19743
"gtt_rnode->att_stat_tups[i]", errorType=errorType@entry=0x55ad0bee500b
"FailedAssertion", fileName=fileName@entry=0x55ad0bf1966b
"storage_gtt.c", lineNumber=lineNumber@entry=902) at assert.c:69
#3 0x000055ad0ba9379f in get_gtt_att_statistic (reloid=<optimized out>,
attnum=0, inh=<optimized out>) at storage_gtt.c:902
#4 0x000055ad0be35625 in examine_simple_variable (root=root@entry=0x55ad0c498748,
var=var@entry=0x55ad0c498c68, vardata=vardata@entry=0x7fff06c9ebf0) at
selfuncs.c:5391
#5 0x000055ad0be36a89 in examine_variable (root=root@entry=0x55ad0c498748,
node=node@entry=0x55ad0c498c68, varRelid=varRelid@entry=0,
vardata=vardata@entry=0x7fff06c9ebf0) at selfuncs.c:4990
#6 0x000055ad0be3ad64 in estimate_num_groups (root=root@entry=0x55ad0c498748,
groupExprs=<optimized out>, input_rows=input_rows@entry=255,
pgset=pgset@entry=0x0, estinfo=estinfo@entry=0x0) at selfuncs.c:3455
#7 0x000055ad0bc50835 in get_number_of_groups (root=root@entry=0x55ad0c498748,
path_rows=255, gd=gd@entry=0x0, target_list=0x55ad0c498bb8) at
planner.c:3241
#8 0x000055ad0bc5576f in create_ordinary_grouping_paths (root=root@entry=0x55ad0c498748,
input_rel=input_rel@entry=0x55ad0c3ce148, grouped_rel=grouped_rel@entry=0x55ad0c4983f0,
agg_costs=agg_costs@entry=0x7fff06c9edb0, gd=gd@entry=0x0,
extra=extra@entry=0x7fff06c9ede0, partially_grouped_rel_p=0x7fff06c9eda8)
at planner.c:3628
#9 0x000055ad0bc55a72 in create_grouping_paths (root=root@entry=0x55ad0c498748,
input_rel=input_rel@entry=0x55ad0c3ce148, target=target@entry=0x55ad0c4c95d8,
target_parallel_safe=target_parallel_safe@entry=true, gd=gd@entry=0x0) at
planner.c:3377
#10 0x000055ad0bc5686d in grouping_planner (root=root@entry=0x55ad0c498748,
tuple_fraction=<optimized out>, tuple_fraction@entry=0) at planner.c:1592
#11 0x000055ad0bc57910 in subquery_planner (glob=glob@entry=0x55ad0c497880,
parse=parse@entry=0x55ad0c3cdbb8, parent_root=parent_root@entry=0x0,
hasRecursion=hasRecursion@entry=false, tuple_fraction=tuple_fraction@entry=0)
at planner.c:1025
#12 0x000055ad0bc57f36 in standard_planner (parse=0x55ad0c3cdbb8,
query_string=<optimized out>, cursorOptions=2048, boundParams=0x0) at
planner.c:406
#13 0x000055ad0bc584d4 in planner (parse=parse@entry=0x55ad0c3cdbb8,
query_string=query_string@entry=0x55ad0c3cc470 "create global temp table
t (a integer); insert into t values (1); select count(*) from t group by
t;", cursorOptions=cursorOptions@entry=2048, boundParams=boundParams@entry=0x0)
at planner.c:277
#14 0x000055ad0bd4855f in pg_plan_query (querytree=querytree@entry=0x55ad0c3cdbb8,
query_string=query_string@entry=0x55ad0c3cc470 "create global temp table
t (a integer); insert into t values (1); select count(*) from t group by
t;", cursorOptions=cursorOptions@entry=2048, boundParams=boundParams@entry
=0x0)
at postgres.c:847
#15 0x000055ad0bd4863b in pg_plan_queries (querytrees=0x55ad0c4986f0,
query_string=query_string@entry=0x55ad0c3cc470 "create global temp table
t (a integer); insert into t values (1); select count(*) from t group by
t;", cursorOptions=cursorOptions@entry=2048, boundParams=boundParams@entry=0x0)
at postgres.c:939
#16 0x000055ad0bd48b20 in exec_simple_query
(query_string=query_string@entry=0x55ad0c3cc470 "create global temp table
t (a integer); insert into t values (1); select count(*) from t group by
t;") at postgres.c:1133
#17 0x000055ad0bd4aaf3 in PostgresMain (dbname=<optimized out>,
username=<optimized out>) at postgres.c:4497
#18 0x000055ad0bca3f91 in BackendRun (port=port@entry=0x55ad0c3f1020) at
postmaster.c:4560
#19 0x000055ad0bca7115 in BackendStartup (port=port@entry=0x55ad0c3f1020)
at postmaster.c:4288
#20 0x000055ad0bca735c in ServerLoop () at postmaster.c:1801
#21 0x000055ad0bca893e in PostmasterMain (argc=3, argv=<optimized out>) at
postmaster.c:1473
#22 0x000055ad0bbe8e31 in main (argc=3, argv=0x55ad0c3c6660) at main.c:198

On Thu, Oct 21, 2021 at 4:25 PM wenjing <wjzeng2012@gmail.com> wrote:

Andrew Bille <andrewbille@gmail.com> 于2021年10月20日周三 上午2:59写道:

Another thanks for the fix. It works for me.

But I found another crash!

This is a check code that was added this year, but it did find a problem
and I fixed it.
Please review the new code(v57) again.

Attachments:

0001-gtt-v58-reademe.patchapplication/octet-stream; name=0001-gtt-v58-reademe.patchDownload
diff --git a/README.gtt.txt b/README.gtt.txt
new file mode 100644
index 00000000000..d181df9acd7
--- /dev/null
+++ b/README.gtt.txt
@@ -0,0 +1,172 @@
+Global Temporary Table(GTT)
+=========================================
+
+Feature description
+-----------------------------------------
+
+Previously, temporary tables are defined once and automatically
+exist (starting with empty contents) in every session before using them.
+
+The temporary table implementation in PostgreSQL, known as Local temp tables(LTT),
+did not fully comply with the SQL standard. This version added the support of
+Global Temporary Table .
+
+The metadata of Global Temporary Table is persistent and shared among sessions.
+The data stored in the Global temporary table is independent of sessions. This
+means, when a session creates a Global Temporary Table and writes some data.
+Other sessions cannot see those data, but they have an empty Global Temporary
+Table with same schema.
+
+Like local temporary table, Global Temporary Table supports ON COMMIT PRESERVE ROWS
+or ON COMMIT DELETE ROWS clause, so that data in the temporary table can be
+cleaned up or preserved automatically when a session exits or a transaction COMMITs.
+
+Unlike Local Temporary Table, Global Temporary Table does not support
+ON COMMIT DROP clauses.
+
+In following paragraphs, we use GTT for Global Temporary Table and LTT for
+local temporary table.
+
+Main design ideas
+-----------------------------------------
+In general, GTT and LTT use the same storage and buffer design and
+implementation. The storage files for both types of temporary tables are named
+as t_backendid_relfilenode, and the local buffer is used to cache the data.
+
+The schema of GTTs is shared among sessions while their data are not. We build
+a new mechanisms to manage those non-shared data and their statistics.
+Here is the summary of changes:
+
+1) CATALOG
+GTTs store session-specific data. The storage information of GTTs'data, their
+transaction information, and their statistics are not stored in the catalog.
+
+2) STORAGE INFO & STATISTICS INFO & TRANSACTION INFO
+In order to maintain durability and availability of GTTs'session-specific data,
+their storage information, statistics, and transaction information is managed
+in a local hash table tt_storage_local_hash.
+
+3) DDL
+Currently, GTT supports almost all table'DDL except CLUSTER/VACUUM FULL.
+Part of the DDL behavior is limited by shared definitions and multiple copies of
+local data, and we added some structures to handle this.
+
+A shared hash table active_gtt_shared_hash is added to track the state of the
+GTT in a different session. This information is recorded in the hash table
+during the DDL execution of the GTT.
+
+The data stored in a GTT can only be modified or accessed by owning session.
+The statements that only modify data in a GTT do not need a high level of
+table locking. The operations making those changes include truncate GTT,
+reindex GTT, and lock GTT.
+
+4) MVCC commit log(clog) cleanup
+Each GTT in a session has its own piece of data, and they have their own
+transaction information. We set up data structures to track and maintain
+this information. The cleaning of CLOGs also needs to consider the transaction
+information of GTT.
+
+Detailed design
+-----------------------------------------
+
+1. CATALOG
+1.1 relpersistence
+define RELPERSISTENCEGLOBALTEMP 'g'
+Mark Global Temporary Table in pg_class relpersistence to 'g'. The relpersistence
+of indexes created on the GTT, sequences on GTT and toast tables on GTT are
+also set to 'g'
+
+1.2 on commit clause
+LTT's status associated with on commit DELETE ROWS and on commit PRESERVE ROWS
+is not stored in catalog. Instead, GTTs need a bool value on_commit_delete_rows
+in reloptions which is shared among sessions.
+
+1.3 gram.y
+GTT is already supported in syntax tree. We remove the warning message
+"GLOBAL is deprecated in temporary table creation" and mark
+relpersistence = RELPERSISTENCEGLOBALTEMP.
+
+2. STORAGE INFO & STATISTICS INFO & TRANSACTION INFO
+2.1. gtt_storage_local_hash
+Each backend creates a local hashtable gtt_storage_local_hash to track a GTT's
+storage file information, statistics, and transaction information.
+
+2.2 GTT storage file info track
+1) When one session inserts data into a GTT for the first time, record the
+storage info to gtt_storage_local_hash.
+2) Use beforeshmemexit to ensure that all files of session GTT are deleted when
+the session exits.
+
+2.3 statistics info
+1) relpages reltuples relallvisible relfilenode
+2) The statistics of each column from pg_statistic
+All the above information is stored in gtt_storage_local_hash.
+When doing vacuum or analyze, GTT's statistic is updated, which is used by
+the SQL planner.
+The statistics summarizes only data in the current session.
+
+2.3 transaction info track
+frozenxid minmulti from pg_class is stored to gtt_storage_local_hash.
+
+3 DDL
+3.1. active_gtt_shared_hash
+This is the hash table created in shared memory to trace the GTT files initialized
+in each session. Each hash entry contains a bitmap that records the backendid of
+the initialized GTT file. With this hash table, we know which backend/session
+is using this GTT. Such information is used during GTT's DDL operations.
+
+3.2 DROP GTT
+One GTT is allowed to be deleted when there is only one session using the table
+and the session is the current session. After holding the lock on GTT,
+active_gtt_shared_hash is checked to ensure that.
+
+3.3 ALTER GTT/DROP INDEX ON GTT
+Same as drop GTT.
+
+3.4 CREATE INDEX ON GTT
+1) create index on GTT statements build index based on local data in a session.
+2) After the index is created, record the index metadata to the catalog.
+3) Other sessions can enable or disable the local GTT index.
+
+3.5 TRUNCATE/REINDEX GTT
+The SQL truncate/reindex command open the GTT using AccessShareLock lock,
+not AccessExclusiveLock, because this command only cleans up local data and
+local buffers in current session. This allows these operations to be executed
+concurrently between sessions, unlike normal tables.
+
+3.6 LOCK GTT
+A lock GTT statement does not hold any relation lock.
+
+3.7 CLUSTER GTT/VACUUM FULL GTT
+The current version does not support.
+
+4 MVCC commit log(clog) cleanup
+
+The GTT storage file contains transaction information. Queries for GTT data rely
+on transaction information such as clog. The transaction information required by
+each session may be completely different. We need to ensure that the transaction
+information of the GTT data is not cleaned up during its lifetime and that
+transaction resources are recycled at the instance level.
+
+4.1 The session level GTT oldest frozenxid
+1) To manage all GTT transaction information, add session level oldest frozenxid
+in each session. When one GTT is created or removed, record the session level
+oldest frozenxid and store it in MyProc.
+2) When vacuum advances the database's frozenxid, session level oldest frozenxid
+should be considered. This is acquired by searching all of MyProc. This way,
+we can avoid the clog required by GTTs to be cleaned.
+
+4.2 vacuum GTT
+Users can perform vacuum over a GTT to clean up local data in the GTT.
+
+4.3 autovacuum GTT
+Autovacuum skips all GTTs, because the data in GTTs is only visible in current session.
+
+5 OTHERS
+5.1 Parallel query
+Planner does not produce parallel query plans for SQL related to GTT. Because
+GTT private data cannot be accessed across processes.
+
+5.2 WAL and Logical replication
+Like LTT, the DML on GTT does not record WAL and is not parsed or replay by
+the logical replication.
\ No newline at end of file
-- 
2.30.1 (Apple Git-130)

0002-gtt-v58-doc.patchapplication/octet-stream; name=0002-gtt-v58-doc.patchDownload
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 473a0a4aeb..e510bde8ac 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -169,32 +169,67 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     <listitem>
      <para>
       If specified, the table is created as a temporary table.
-      Temporary tables are automatically dropped at the end of a
-      session, or optionally at the end of the current transaction
-      (see <literal>ON COMMIT</literal> below).  The default
-      search_path includes the temporary schema first and so identically
-      named existing permanent tables are not chosen for new plans
+      Optionally, <literal>GLOBAL</literal> or <literal>LOCAL</literal>
+      can be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
+      They represent two types of temporary tables supported by <productname>PostgreSQL</productname>:
+      global temporary table and local temporary table. Without specified
+      GLOBAL or LOCAL, a local temporary table is created by default.
+     </para>
+
+    <para>
+     Both types of temporary tables’ data are truncated at the
+     end of a session or optionally at the end of the current transaction.
+     (see <literal>ON COMMIT</literal> below). For global temporary table,
+     its schema is reserved and reused by future sessions or transactions.
+     For local temporary table, both its data and its schema are dropped.
+    </para>
+
+    <variablelist>
+     <varlistentry>
+      <term><literal>Global Temporary Table</literal></term>
+      <listitem>
+       <para>
+        Global temporary table are defined just once and automatically exist
+        (starting with empty contents) in every session that needs them.
+        The schema definition of temporary tables is persistent and shared among sessions.
+        However, the data in temporary tables are kept private to sessions themselves,
+        even though they use same name and same schema.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>Local Temporary Table</literal></term>
+     <listitem>
+     <para>
+      Local temporary table are automatically dropped at the end of a
+      session (include schema and data). Future sessions need to create
+      their own temporary tables when they are used.
+     </para>
+     <para>
+      The default search_path includes the temporary schema first and so
+      identically named existing permanent tables are not chosen for new plans
       while the temporary table exists, unless they are referenced
       with schema-qualified names. Any indexes created on a temporary
       table are automatically temporary as well.
      </para>
+     </listitem>
+     </varlistentry>
+    </variablelist>
 
-     <para>
-      The <link linkend="autovacuum">autovacuum daemon</link> cannot
-      access and therefore cannot vacuum or analyze temporary tables.
-      For this reason, appropriate vacuum and analyze operations should be
-      performed via session SQL commands.  For example, if a temporary
-      table is going to be used in complex queries, it is wise to run
-      <command>ANALYZE</command> on the temporary table after it is populated.
-     </para>
+    <para>
+     The <link linkend="autovacuum">autovacuum daemon</link> cannot
+     access and therefore cannot vacuum or analyze temporary tables.
+     For this reason, appropriate vacuum and analyze operations should be
+     performed via session SQL commands.  For example, if a temporary
+     table is going to be used in complex queries, it is wise to run
+     <command>ANALYZE</command> on the temporary table after it is populated.
+    </para>
+    <para>
+     The Temporary table resembles the SQL standard, but has some differences.
+     see <xref linkend="sql-createtable-compatibility"/> below.
+    </para>
 
-     <para>
-      Optionally, <literal>GLOBAL</literal> or <literal>LOCAL</literal>
-      can be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
-      This presently makes no difference in <productname>PostgreSQL</productname>
-      and is deprecated; see
-      <xref linkend="sql-createtable-compatibility"/> below.
-     </para>
     </listitem>
    </varlistentry>
 
@@ -2133,13 +2168,17 @@ CREATE TABLE cities_partdef
    <title>Temporary Tables</title>
 
    <para>
-    Although the syntax of <literal>CREATE TEMPORARY TABLE</literal>
-    resembles that of the SQL standard, the effect is not the same.  In the
-    standard,
-    temporary tables are defined just once and automatically exist (starting
-    with empty contents) in every session that needs them.
-    <productname>PostgreSQL</productname> instead
-    requires each session to issue its own <literal>CREATE TEMPORARY
+    Although the syntax of <literal>CREATE GLOBAL/LOCAL TEMPORARY TABLE</literal>
+    resembles that of the SQL standard, the effect is not the same.
+    The global temporary table follows the SQL standards while local temporary
+    table does not.
+   </para>
+
+   <para>
+    First, in the standard, both global and local temporary tables are defined just
+    once and automatically exist (starting with empty contents) in every session
+    that needs them. For local temporary tables, <productname>PostgreSQL</productname>
+    instead requires each session to issue its own <literal>CREATE LOCAL TEMPORARY
     TABLE</literal> command for each temporary table to be used.  This allows
     different sessions to use the same temporary table name for different
     purposes, whereas the standard's approach constrains all instances of a
@@ -2147,29 +2186,14 @@ CREATE TABLE cities_partdef
    </para>
 
    <para>
-    The standard's definition of the behavior of temporary tables is
-    widely ignored.  <productname>PostgreSQL</productname>'s behavior
-    on this point is similar to that of several other SQL databases.
-   </para>
-
-   <para>
-    The SQL standard also distinguishes between global and local temporary
+    Second, the SQL standard distinguishes between global and local temporary
     tables, where a local temporary table has a separate set of contents for
     each SQL module within each session, though its definition is still shared
-    across sessions.  Since <productname>PostgreSQL</productname> does not
+    across sessions. Since <productname>PostgreSQL</productname> does not
     support SQL modules, this distinction is not relevant in
     <productname>PostgreSQL</productname>.
    </para>
 
-   <para>
-    For compatibility's sake, <productname>PostgreSQL</productname> will
-    accept the <literal>GLOBAL</literal> and <literal>LOCAL</literal> keywords
-    in a temporary table declaration, but they currently have no effect.
-    Use of these keywords is discouraged, since future versions of
-    <productname>PostgreSQL</productname> might adopt a more
-    standard-compliant interpretation of their meaning.
-   </para>
-
    <para>
     The <literal>ON COMMIT</literal> clause for temporary tables
     also resembles the SQL standard, but has some differences.
@@ -2177,7 +2201,8 @@ CREATE TABLE cities_partdef
     default behavior is <literal>ON COMMIT DELETE ROWS</literal>.  However, the
     default behavior in <productname>PostgreSQL</productname> is
     <literal>ON COMMIT PRESERVE ROWS</literal>.  The <literal>ON COMMIT
-    DROP</literal> option does not exist in SQL.
+    DROP</literal> option does not exist in SQL and is not supported by
+    global temporary table.
    </para>
   </refsect2>
 
-- 
2.30.1 (Apple Git-130)

0003-gtt-v58-implementation.patchapplication/octet-stream; name=0003-gtt-v58-implementation.patchDownload
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index e84ecd1c981..c58218b1508 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -18,6 +18,7 @@
 #include "access/toast_internals.h"
 #include "access/visibilitymap.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "funcapi.h"
 #include "miscadmin.h"
 #include "storage/bufmgr.h"
@@ -223,6 +224,8 @@ verify_heapam(PG_FUNCTION_ARGS)
 	BlockNumber last_block;
 	BlockNumber nblocks;
 	const char *skip;
+	TransactionId	relfrozenxid = InvalidTransactionId;
+	MultiXactId		relminmxid = InvalidMultiXactId;
 
 	/* Check to see if caller supports us returning a tuplestore */
 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -339,6 +342,13 @@ verify_heapam(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(ctx.rel) &&
+		!gtt_storage_attached(RelationGetRelid(ctx.rel)))
+	{
+		relation_close(ctx.rel, AccessShareLock);
+		PG_RETURN_NULL();
+	}
+
 	/* Early exit if the relation is empty */
 	nblocks = RelationGetNumberOfBlocks(ctx.rel);
 	if (!nblocks)
@@ -406,9 +416,25 @@ verify_heapam(PG_FUNCTION_ARGS)
 
 	update_cached_xid_range(&ctx);
 	update_cached_mxid_range(&ctx);
-	ctx.relfrozenxid = ctx.rel->rd_rel->relfrozenxid;
+
+	if (RELATION_IS_GLOBAL_TEMP(ctx.rel))
+	{
+		get_gtt_relstats(RelationGetRelid(ctx.rel),
+						NULL,
+						NULL,
+						NULL,
+						&relfrozenxid,
+						&relminmxid);
+	}
+	else
+	{
+		relfrozenxid = ctx.rel->rd_rel->relfrozenxid;
+		relminmxid = ctx.rel->rd_rel->relminmxid;
+	}
+
+	ctx.relfrozenxid = relfrozenxid;
 	ctx.relfrozenfxid = FullTransactionIdFromXidAndCtx(ctx.relfrozenxid, &ctx);
-	ctx.relminmxid = ctx.rel->rd_rel->relminmxid;
+	ctx.relminmxid = relminmxid;
 
 	if (TransactionIdIsNormal(ctx.relfrozenxid))
 		ctx.oldest_xid = ctx.relfrozenxid;
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index b5602f53233..21b2d2a9527 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -159,6 +159,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * In order to avoid consistency problems, the global temporary table
+	 * uses ShareUpdateExclusiveLock.
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temporary table on commit options",
+			RELOPT_KIND_HEAP,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},
 	/* list terminator */
 	{{NULL}}
 };
@@ -1834,6 +1847,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 43ba03b6eb9..49f1052fdb1 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1023,7 +1023,7 @@ gistproperty(Oid index_oid, int attno,
 XLogRecPtr
 gistGetFakeLSN(Relation rel)
 {
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	if (RELATION_IS_TEMP(rel))
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index eb3810494f2..cbd22909582 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -151,7 +151,7 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	if (!RELATION_IS_TEMP(index))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 2da2be16969..34ecef39de9 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -52,6 +52,7 @@
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/atomics.h"
@@ -5844,6 +5845,19 @@ heap_abort_speculative(Relation relation, ItemPointer tid)
 	BlockNumber block;
 	Buffer		buffer;
 	TransactionId prune_xid;
+	TransactionId relfrozenxid = InvalidTransactionId;
+
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		get_gtt_relstats(RelationGetRelid(relation),
+						NULL,
+						NULL,
+						NULL,
+						&relfrozenxid,
+						NULL);
+	}
+	else
+		relfrozenxid = relation->rd_rel->relfrozenxid;
 
 	Assert(ItemPointerIsValid(tid));
 
@@ -5896,8 +5910,8 @@ heap_abort_speculative(Relation relation, ItemPointer tid)
 	 * TransactionXmin, so there's no race here).
 	 */
 	Assert(TransactionIdIsValid(TransactionXmin));
-	if (TransactionIdPrecedes(TransactionXmin, relation->rd_rel->relfrozenxid))
-		prune_xid = relation->rd_rel->relfrozenxid;
+	if (TransactionIdPrecedes(TransactionXmin, relfrozenxid))
+		prune_xid = relfrozenxid;
 	else
 		prune_xid = TransactionXmin;
 	PageSetPrunable(page, prune_xid);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 9befe012a9e..26fce0c4b83 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -593,7 +593,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
@@ -645,7 +645,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(RelationGetSmgr(rel), dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 05221cc1d6d..984e08b4a78 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -63,6 +63,7 @@
 #include "access/xlog.h"
 #include "catalog/index.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -507,6 +508,25 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 	TransactionId OldestXmin;
 	TransactionId FreezeLimit;
 	MultiXactId MultiXactCutoff;
+	TransactionId	relfrozenxid = InvalidTransactionId;
+	MultiXactId		relminmxid = InvalidMultiXactId;
+	double			reltuples = 0;
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		get_gtt_relstats(RelationGetRelid(rel),
+						NULL,
+						&reltuples,
+						NULL,
+						&relfrozenxid,
+						&relminmxid);
+	}
+	else
+	{
+		relfrozenxid = rel->rd_rel->relfrozenxid;
+		relminmxid = rel->rd_rel->relminmxid;
+		reltuples = rel->rd_rel->reltuples;
+	}
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
@@ -542,9 +562,9 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 	 * table's minimum MultiXactId is older than or equal to the requested
 	 * mxid full-table scan limit; or if DISABLE_PAGE_SKIPPING was specified.
 	 */
-	aggressive = TransactionIdPrecedesOrEquals(rel->rd_rel->relfrozenxid,
+	aggressive = TransactionIdPrecedesOrEquals(relfrozenxid,
 											   xidFullScanLimit);
-	aggressive |= MultiXactIdPrecedesOrEquals(rel->rd_rel->relminmxid,
+	aggressive |= MultiXactIdPrecedesOrEquals(relminmxid,
 											  mxactFullScanLimit);
 	if (params->options & VACOPT_DISABLE_PAGE_SKIPPING)
 		aggressive = true;
@@ -591,9 +611,9 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 	}
 
 	vacrel->bstrategy = bstrategy;
-	vacrel->relfrozenxid = rel->rd_rel->relfrozenxid;
-	vacrel->relminmxid = rel->rd_rel->relminmxid;
-	vacrel->old_live_tuples = rel->rd_rel->reltuples;
+	vacrel->relfrozenxid = relfrozenxid;
+	vacrel->relminmxid = relminmxid;
+	vacrel->old_live_tuples = reltuples;
 
 	/* Set cutoffs for entire VACUUM */
 	vacrel->OldestXmin = OldestXmin;
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 5bc7c3616a9..0b261f723d7 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -677,6 +678,14 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		/*
+		 * If a global temporary table storage file is not initialized in the
+		 * current backend, its index does not have a root page, just returns 0.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d297e773612..8c21979625f 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -44,6 +44,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index aa7d4d5456b..595cb03eb4a 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -504,6 +504,7 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 5898203972b..159b4e9c812 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -62,6 +62,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,
@@ -366,13 +368,24 @@ heap_create(const char *relname,
 			break;
 	}
 
+	/* For global temporary table, even if the storage is not initialized,
+	 * the relfilenode needs to be generated and put into the catalog.
+	 */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		create_storage = false;
+		if (!OidIsValid(relfilenode))
+			relfilenode = relid;
+	}
 	/*
 	 * Decide whether to create storage. If caller passed a valid relfilenode,
 	 * storage is already created, so don't do it here.  Also don't create it
 	 * for relkinds without physical storage.
 	 */
-	if (!RELKIND_HAS_STORAGE(relkind) || OidIsValid(relfilenode))
+	else if (!RELKIND_HAS_STORAGE(relkind) || OidIsValid(relfilenode))
+	{
 		create_storage = false;
+	}
 	else
 	{
 		create_storage = true;
@@ -427,7 +440,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:
@@ -445,7 +458,8 @@ heap_create(const char *relname,
 	 * protected by the existence of a physical file; but for relations with
 	 * no files, add a pg_shdepend entry to account for that.
 	 */
-	if (!create_storage && reltablespace != InvalidOid)
+	if (!create_storage && reltablespace != InvalidOid &&
+		relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
 		recordDependencyOnTablespace(RelationRelationId, relid,
 									 reltablespace);
 
@@ -998,6 +1012,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -1036,8 +1051,21 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/*
+	 * The transaction information of the global temporary table is stored
+	 * in the local hash table, not in catalog.
+	 */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1410,6 +1438,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1495,8 +1524,9 @@ heap_create_with_catalog(const char *relname,
 	/*
 	 * If there's a special on-commit action, remember it
 	 */
-	if (oncommit != ONCOMMIT_NOOP)
-		register_on_commit_action(relid, oncommit);
+	if (oncommit != ONCOMMIT_NOOP &&
+		relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+		register_on_commit_action(relid, oncommit, false);
 
 	/*
 	 * ok, the relation has been cataloged, so close our relations and return
@@ -1993,6 +2023,19 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/*
+	 * Only when other sessions are not using this Global temporary table,
+	 * is it allowed to DROP it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot drop global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3277,7 +3320,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3289,7 +3332,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3325,7 +3368,7 @@ RelationTruncateIndexes(Relation heapRelation)
  * ON COMMIT truncation of temporary tables, where it doesn't matter.
  */
 void
-heap_truncate(List *relids)
+heap_truncate(List *relids, bool is_global_temp)
 {
 	List	   *relations = NIL;
 	ListCell   *cell;
@@ -3335,8 +3378,23 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode;
+
+		/*
+		 * Truncate global temporary table only clears local data,
+		 * so only low-level locks need to be held.
+		 */
+		if (is_global_temp)
+		{
+			if (!gtt_storage_attached(rid))
+				continue;
+
+			lockmode = RowExclusiveLock;
+		}
+		else
+			lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3369,6 +3427,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3377,23 +3436,39 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	/*
+	 * Truncate GTT only clears local data, so only low-level locks
+	 * need to be held.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		lockmode = AccessShareLock;
+	else
+		lockmode = AccessExclusiveLock;
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	/*
+	 * After the data is cleaned up on the GTT, the transaction information
+	 * for the data(stored in local hash table) is also need reset.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(RelationGetRelid(rel), 0, 0, 0, RecentXmin, GetOldestMultiXactId());
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 26bfa74ce75..c32b45f9673 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -54,6 +54,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"
@@ -733,6 +734,25 @@ index_create(Relation heapRelation,
 	TransactionId relfrozenxid;
 	MultiXactId relminmxid;
 
+	/* For global temporary table only */
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temporary table with concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot reindex global temporary tables concurrently")));
+
+		/*
+		 * For the case that some backend is applied relcache message to create
+		 * an index on a global temporary table, if this table in the current
+		 * backend are not initialized, the creation of index storage on the
+		 * table are also skipped.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/* constraint flags can only be set when a constraint is requested */
 	Assert((constr_flags == 0) ||
 		   ((flags & INDEX_CREATE_ADD_CONSTRAINT) != 0));
@@ -2107,7 +2127,7 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 * lock (see comments in RemoveRelations), and a non-concurrent DROP is
 	 * more efficient.
 	 */
-	Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+	Assert(!RelpersistenceTsTemp(get_rel_persistence(indexId)) ||
 		   (!concurrent && !concurrent_lock_mode));
 
 	/*
@@ -2139,6 +2159,21 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/*
+	 * Allow to drop index on global temporary table when only current
+	 * backend use it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation) &&
+		is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+	{
+		ereport(ERROR,
+			(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+			 errmsg("cannot drop index %s on global temporary table %s",
+					RelationGetRelationName(userIndexRelation), RelationGetRelationName(userHeapRelation)),
+					errdetail("Because the index is created on the global temporary table and other backend attached it."),
+					errhint("Please try detach all sessions using this temporary table and try again.")));
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2747,6 +2782,7 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = RELATION_IS_GLOBAL_TEMP(rel);
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2841,20 +2877,37 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
-		{
-			rd_rel->relpages = (int32) relpages;
-			dirty = true;
-		}
-		if (rd_rel->reltuples != (float4) reltuples)
+		/* For global temporary table */
+		if (is_gtt)
 		{
-			rd_rel->reltuples = (float4) reltuples;
-			dirty = true;
+			/* Update GTT'statistics into local relcache */
+			rel->rd_rel->relpages = (int32) relpages;
+			rel->rd_rel->reltuples = (float4) reltuples;
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+
+			/* Update GTT'statistics into local hashtable */
+			up_gtt_relstats(RelationGetRelid(rel), relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+		else
 		{
-			rd_rel->relallvisible = (int32) relallvisible;
-			dirty = true;
+			if (rd_rel->relpages != (int32) relpages)
+			{
+				rd_rel->relpages = (int32) relpages;
+				dirty = true;
+			}
+
+			if (rd_rel->reltuples != (float4) reltuples)
+			{
+				rd_rel->reltuples = (float4) reltuples;
+				dirty = true;
+			}
+
+			if (rd_rel->relallvisible != (int32) relallvisible)
+			{
+				rd_rel->relallvisible = (int32) relallvisible;
+				dirty = true;
+			}
 		}
 	}
 
@@ -2967,6 +3020,26 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, progress_index, progress_vals);
 	}
 
+	/* For build index on global temporary table */
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		/*
+		 * If the storage for the index in this session is not initialized,
+		 * it needs to be created.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			/* Before create init storage, fix the local Relcache first */
+			force_enable_gtt_index(indexRelation);
+
+			Assert(gtt_storage_attached(RelationGetRelid(heapRelation)));
+
+			/* Init storage for index */
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3508,6 +3581,8 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	PGRUsage	ru0;
 	bool		progress = ((params->options & REINDEXOPT_REPORT_PROGRESS) != 0);
 	bool		set_tablespace = false;
+	LOCKMODE	lockmode_on_heap = ShareLock;
+	LOCKMODE	lockmode_on_index = AccessExclusiveLock;
 
 	pg_rusage_init(&ru0);
 
@@ -3521,10 +3596,29 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	if (!OidIsValid(heapId))
 		return;
 
+	/*
+	 * For reindex on global temporary table, If the storage for the index
+	 * in current backend is not initialized, nothing is done.
+	 */
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		if (!gtt_storage_attached(indexId))
+		{
+			/* Suppress use of the target index while rebuilding it */
+			SetReindexProcessing(heapId, indexId);
+			/* Re-allow use of target index */
+			ResetReindexProcessing();
+			return;
+		}
+
+		lockmode_on_heap = AccessShareLock;
+		lockmode_on_index = AccessShareLock;
+	}
+
 	if ((params->options & REINDEXOPT_MISSING_OK) != 0)
-		heapRelation = try_table_open(heapId, ShareLock);
+		heapRelation = try_table_open(heapId, lockmode_on_heap);
 	else
-		heapRelation = table_open(heapId, ShareLock);
+		heapRelation = table_open(heapId, lockmode_on_heap);
 
 	/* if relation is gone, leave */
 	if (!heapRelation)
@@ -3550,7 +3644,7 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	 * Open the target index relation and get an exclusive lock on it, to
 	 * ensure that no one else is touching this particular index.
 	 */
-	iRel = index_open(indexId, AccessExclusiveLock);
+	iRel = index_open(indexId, lockmode_on_index);
 
 	if (progress)
 		pgstat_progress_update_param(PROGRESS_CREATEIDX_ACCESS_METHOD_OID,
@@ -3801,6 +3895,12 @@ reindex_relation(Oid relid, int flags, ReindexParams *params)
 	bool		result;
 	ListCell   *indexId;
 	int			i;
+	LOCKMODE	lockmode;
+
+	if (flags & REINDEX_REL_PROCESS_GLOBAL_TEMP)
+		lockmode = AccessShareLock;
+	else
+		lockmode = ShareLock;
 
 	/*
 	 * Open and lock the relation.  ShareLock is sufficient since we only need
@@ -3808,9 +3908,9 @@ reindex_relation(Oid relid, int flags, ReindexParams *params)
 	 * should match ReindexTable().
 	 */
 	if ((params->options & REINDEXOPT_MISSING_OK) != 0)
-		rel = try_table_open(relid, ShareLock);
+		rel = try_table_open(relid, lockmode);
 	else
-		rel = table_open(relid, ShareLock);
+		rel = table_open(relid, lockmode);
 
 	/* if relation is gone, leave */
 	if (!rel)
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 4de8400fd0f..fe3fcc712cb 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -656,6 +656,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/* Do not allow create global temporary table in temporary schemas */
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp table in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index c5ad28d71fe..707068a6fd8 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,6 +27,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -61,6 +62,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			temprelOid;			/* InvalidOid if not a global temporary rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -115,7 +117,7 @@ AddPendingSync(const RelFileNode *rnode)
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -126,7 +128,12 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/*
+		 * Global temporary table and local temporary table use same
+		 * design on storage module.
+		 */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -154,6 +161,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
@@ -165,6 +173,21 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		AddPendingSync(&rnode);
 	}
 
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		Assert(rel && RELATION_IS_GLOBAL_TEMP(rel));
+
+		/*
+		 * Remember the reloid of global temporary table, which is used for
+		 * transaction commit or rollback.
+		 * see smgrDoPendingDeletes.
+		 */
+		pending->temprelOid = RelationGetRelid(rel);
+
+		/* Remember global temporary table storage info to localhash */
+		remember_gtt_storage_info(rnode, rel);
+	}
+
 	return srel;
 }
 
@@ -201,11 +224,20 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/*
+	 * Remember the reloid of global temporary table, which is used for
+	 * transaction commit or rollback.
+	 * see smgrDoPendingDeletes.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->temprelOid = RelationGetRelid(rel);
+
 	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
@@ -618,6 +650,7 @@ smgrDoPendingDeletes(bool isCommit)
 	int			nrels = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -647,14 +680,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->temprelOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -664,12 +701,21 @@ smgrDoPendingDeletes(bool isCommit)
 
 	if (nrels > 0)
 	{
+		int	i;
+
 		smgrdounlinkall(srels, nrels, false);
 
-		for (int i = 0; i < nrels; i++)
+		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* Delete global temporary table info in localhash */
+			if (gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 00000000000..8746db56063
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1528 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  The body implementation of Global Temparary table.
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *	  See src/backend/catalog/GTT_README for Global temparary table's
+ *	  requirements and design.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_info_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+
+int		vacuum_gtt_defer_check_age = 0;
+
+/*
+ * The Global temporary table's shared hash table data structure
+ */
+typedef struct gtt_ctl_data
+{
+	LWLock		lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+/* record this global temporary table in which backends are being used */
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+/*
+ * The Global temporary table's local hash table data structure
+ */
+/* Record the storage information and statistical information of the global temporary table */
+typedef struct
+{
+	Oid			relfilenode;
+	Oid			spcnode;
+
+	/* pg_class relstat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+
+	/* pg_statistic column stat */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_free_statistics(gtt_relfilenode *rnode);
+static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok);
+static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok);
+static Bitmapset *copy_active_gtt_bitmap(Oid relid);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+/*
+ * Calculate shared hash table entry size for GTT.
+ */
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	/* hash entry header size */
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	/*
+	 * hash entry data size
+	 * this is a bitmap in shared memory, each backend have a bit.
+	 */
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+/*
+ * Calculate shared hash table max size for GTT.
+ */
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	/* shared hash header size */
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	/* hash entry size */
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	/* max size */
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+/*
+ * Initialization shared hash table for GTT.
+ */
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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();
+	}
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(gtt_fnode);
+	info.entrysize = action_gtt_shared_hash_entry_size();
+	active_gtt_shared_hash =
+		ShmemInitHash("active gtt shared hash",
+						gtt_shared_ctl->max_entry,
+						gtt_shared_ctl->max_entry,
+						&info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+/*
+ * Record GTT relid to shared hash table, which means that current backend is using this GTT.
+ */
+static void
+gtt_storage_checkin(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			found;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *)&(fnode), 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_global_temporary_table.")));
+	}
+
+	if (!found)
+	{
+		int			wordnum;
+
+		/* init bitmap */
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	/* record itself in bitmap */
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+/*
+ * Remove the GTT relid record from the shared hash table which means that current backend is
+ * not use this GTT.
+ */
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	if (!skiplock)
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		if (!skiplock)
+			LWLockRelease(&gtt_shared_ctl->lock);
+
+		if (isCommit)
+			elog(WARNING, "relid %u not exist in gtt shared hash when drop local storage", relid);
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+	/* remove itself from bitmap */
+	bms_del_member(entry->map, MyBackendId);
+
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+
+	if (!skiplock)
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+	return;
+}
+
+/*
+ * Gets usage information for a GTT from shared hash table.
+ * The information is in the form of bitmap.
+ * Quickly copy the entire bitmap from shared memory and return it.
+ * that to avoid holding locks for a long time.
+ */
+static Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	Bitmapset		*map_copy = NULL;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		return NULL;
+	}
+
+	Assert(entry->map);
+
+	/* copy the entire bitmap */
+	if (!bms_is_empty(entry->map))
+		map_copy = bms_copy(entry->map);
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return map_copy;
+}
+
+/*
+ * Check if there are other backends using this GTT besides the current backend.
+ */
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			in_use = false;
+	int			num_use = 0;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		return false;
+	}
+
+	Assert(entry->map);
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+	/* how many backend are using this GTT */
+	num_use = bms_num_members(entry->map);
+	if (num_use == 0)
+		in_use = false;
+	else if (num_use == 1)
+	{
+		/* check if this is itself */
+		if(bms_is_member(MyBackendId, entry->map))
+			in_use = false;
+		else
+			in_use = true;
+	}
+	else
+		in_use = true;
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return in_use;
+}
+
+/*
+ * Record GTT information to local hash.
+ * They include GTT storage info, transaction info and statistical info.
+ */
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+	gtt_local_hash_entry		*entry;
+	MemoryContext			oldcontext;
+	gtt_relfilenode			*new_node = NULL;
+	Oid				relid = RelationGetRelid(rel);
+	int				natts = 0;
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	/* First time through: initialize the hash table */
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		HASHCTL		ctl;
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_info_context =
+			AllocSetContextCreate(CacheMemoryContext,
+								"gtt info context",
+								ALLOCSET_DEFAULT_SIZES);
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		ctl.hcxt = gtt_info_context;
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+	}
+
+	Assert(CacheMemoryContext);
+	Assert(gtt_info_context);
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	entry = gtt_search_by_relid(relid, true);
+	if (!entry)
+	{
+		bool		found = false;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash,
+						(void *) &relid, HASH_ENTER, &found);
+
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			/* record the on commit clause */
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS, true);
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			gtt_storage_checkin(relid);
+		}
+	}
+
+	/* record storage info relstat columnstats and transaction info to relfilenode list */
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	new_node->natts = 0;
+	new_node->attnum = NULL;
+	new_node->att_stat_tups = NULL;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* init column stats structure */
+	natts = RelationGetNumberOfAttributes(rel);
+	new_node->attnum = palloc0(sizeof(int) * natts);
+	new_node->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+	new_node->natts = natts;
+
+	/* only heap rel or toast rel have transaction info */
+	if (entry->relkind == RELKIND_RELATION ||
+		entry->relkind == RELKIND_TOASTVALUE)
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+
+	/* Registration callbacks are used to trigger cleanup during process exit */
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+/*
+ * Remove GTT information from local hash when transaction commit/rollback.
+ */
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	gtt_relfilenode		*d_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+		return;
+	}
+
+	d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+		else
+		{
+			/* rollback transaction */
+			if (entry->relfilenode_list == NIL)
+			{
+				if (entry->relkind == RELKIND_RELATION ||
+					entry->relkind == RELKIND_SEQUENCE)
+					gtt_storage_checkout(relid, false, isCommit);
+
+				hash_search(gtt_storage_local_hash,
+						(void *) &(relid), HASH_REMOVE, NULL);
+			}
+
+			return;
+		}
+	}
+
+	/* Clean up transaction info from Local order list and MyProc */
+	if (entry->relkind == RELKIND_RELATION ||
+		entry->relkind == RELKIND_TOASTVALUE)
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+
+		/* this is valid relfrozenxid */
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	/* delete relfilenode from rel entry */
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	gtt_free_statistics(d_rnode);
+
+	if (entry->relfilenode_list == NIL)
+	{
+		/* tell shared hash that current backend will no longer use this GTT */
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(relid, false, isCommit);
+
+		hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_REMOVE, NULL);
+	}
+
+	return;
+}
+
+/*
+ * Check if current backend is using this GTT.
+ */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool			found = false;
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (!OidIsValid(relid))
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry)
+		found = true;
+
+	return found;
+}
+
+/*
+ * When backend exit, bulk cleaning all GTT storage and local buffer of this backend.
+ */
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS			status;
+	gtt_local_hash_entry	*entry;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	/* Need to ensure we have a usable transaction. */
+	AbortOutOfAnyTransaction();
+
+	/* Search all relfilenode for GTT in current backend */
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel[1];
+			RelFileNode		rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel[0] = smgropen(rnode, MyBackendId);
+			smgrdounlinkall(srel, 1, false);
+			smgrclose(srel[0]);
+		}
+
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(entry->relid, true, false);
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+		hash_search(gtt_storage_local_hash, (void *) &(entry->relid), HASH_REMOVE, NULL);
+	}
+
+	/* set to global area */
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;
+
+	return;
+}
+
+/*
+ * Update GTT relstats(relpage/reltuple/relallvisible)
+ * to local hash.
+ */
+void
+up_gtt_relstats(Oid relid,
+					BlockNumber num_pages,
+					double num_tuples,
+					BlockNumber num_all_visible_pages,
+					TransactionId relfrozenxid,
+					TransactionId relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (!OidIsValid(relid))
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (num_pages > 0 &&
+		gtt_rnode->relpages != (int32)num_pages)
+		gtt_rnode->relpages = (int32)num_pages;
+
+	if (num_tuples > 0 &&
+		gtt_rnode->reltuples != (float4)num_tuples)
+		gtt_rnode->reltuples = (float4)num_tuples;
+
+	/* only heap contain transaction information and relallvisible */
+	if (entry->relkind == RELKIND_RELATION ||
+		entry->relkind == RELKIND_TOASTVALUE)
+	{
+		if (num_all_visible_pages > 0 &&
+			gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+		{
+			gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+		}
+
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNextTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			/* set to local order list */
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			/* set to global area */
+			set_gtt_session_relfrozenxid();
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search GTT relstats(relpage/reltuple/relallvisible)
+ * from local has.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update GTT info(definition is same as pg_statistic)
+ * to local hash.
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+	MemoryContext		oldcontext;
+	bool		found = false;
+	int			i = 0;
+
+	/* not support whole row or system column */
+	if (attnum <= 0)
+		return;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return;
+
+	Assert(entry->relid == reloid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	/* switch context to gtt_info_context for store tuple at heap_form_tuple */
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == 0)
+		{
+			Assert(gtt_rnode->att_stat_tups[i] == NULL);
+			gtt_rnode->attnum[i] = attnum;
+			gtt_rnode->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+			found = true;
+			break;
+		}
+		else if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			heap_freetuple(gtt_rnode->att_stat_tups[i]);
+			gtt_rnode->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+			found = true;
+			break;
+		}
+	}
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!found)
+		elog(WARNING, "analyze can not update relid %u column %d statistics after add or drop column, try truncate table first", reloid, attnum);
+
+	return;
+}
+
+/*
+ * Search GTT statistic info(definition is same as pg_statistic)
+ * from local hash.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	int			i = 0;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	/* not support whole row or system column */
+	if (attnum <= 0)
+		return NULL;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return NULL;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return NULL;
+
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			return gtt_rnode->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Insert a RelfrozenXID into the list and keep the list in order.
+ */
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int		i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Remove a RelfrozenXID from order list gtt_session_relfrozenxid_list.
+ */
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+/*
+ * Update of backend Level oldest relfrozenxid to MyProc.
+ * This makes each backend's oldest RelFrozenxID globally visible.
+ */
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list != NIL)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	if (MyProc->backend_gtt_frozenxid != gtt_frozenxid)
+		MyProc->backend_gtt_frozenxid = gtt_frozenxid;
+}
+
+/*
+ * Get GTT column level data statistics.
+ */
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate		*tupstore;
+	HeapTuple		tuple;
+	Relation		rel = NULL;
+	Oid			reloid = PG_GETARG_OID(0);
+	int			attnum = PG_GETARG_INT32(1);
+	char			rel_persistence;
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	/* get data from local hash */
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum		values[31];
+		bool		isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, NoLock);
+	relation_close(pg_tatistic, AccessShareLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get GTT table level data statistics.
+ */
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate	*tupstore;
+	TupleDesc	tupdesc;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid			reloid = PG_GETARG_OID(0);
+	Oid			relnode = 0;
+	char			rel_persistence;
+	BlockNumber		relpages = 0;
+	BlockNumber		relallvisible = 0;
+	uint32			relfrozenxid = 0;
+	uint32			relminmxid = 0;
+	double			reltuples = 0;
+	Relation		rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[6];
+		bool	isnull[6];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get a list of backend pids that are currently using this GTT.
+ */
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	PGPROC			*proc = NULL;
+	Bitmapset		*map = NULL;
+	Tuplestorestate		*tupstore;
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	HeapTuple		tuple;
+	Oid			reloid = PG_GETARG_OID(0);
+	char			rel_persistence;
+	Relation		rel = NULL;
+	pid_t			pid = 0;
+	int				backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	/* get data from share hash */
+	map = copy_active_gtt_bitmap(reloid);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			/* backendid map to process pid */
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get backend level oldest relfrozenxid of each backend using GTT in current database.
+ */
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate		*tupstore;
+	int			*pids = NULL;
+	uint32			*xids = NULL;
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	HeapTuple		tuple;
+	int			num_xid = MaxBackends + 1;
+	int			i = 0;
+	int			j = 0;
+	uint32			oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+
+	/* Get backend level oldest relfrozenxid in all backend that in MyDatabaseId use GTT */
+	oldest = list_all_backend_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+/*
+ * In order to build the GTT index, force enable GTT'index.
+ */
+void
+force_enable_gtt_index(Relation index)
+{
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(RelationGetRelid(index)));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+/*
+ * Fix the local state of the GTT's index.
+ */
+void
+gtt_fix_index_backend_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid heapOid = index->rd_index->indrelid;
+
+	/* Must be GTT */
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	/*
+	 * If this GTT is not initialized in the current backend,
+	 * its index status is temporarily set to invalid(local relcache).
+	 */
+	if (gtt_storage_attached(heapOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+/*
+ * During the SQL initialization of the executor (InitPlan)
+ * Initialize storage of GTT GTT'indexes and build empty index.
+ */
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int		i;
+	Oid		toastrelid;
+
+	if (!(operation == CMD_UTILITY || operation == CMD_INSERT))
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	/* Each GTT is initialized once in each backend */
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	/* init heap storage */
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	/* rebuild all local index for global temp table */
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		index_build(relation, index, info, true, false);
+
+		/* after build index, index re-enabled */
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+		Assert(info->ii_ReadyForInserts);
+	}
+
+	/* rebuild index for global temp toast table */
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+
+		/* init index storage */
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid			indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+			/* build empty index */
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			Assert(currentIndex->rd_index->indisvalid);
+			Assert(currentIndex->rd_index->indislive);
+			Assert(currentIndex->rd_index->indisready);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+/*
+ * Release the data structure memory used to store GTT storage info.
+ */
+static void
+gtt_free_statistics(gtt_relfilenode *rnode)
+{
+	int i;
+
+	Assert(rnode);
+
+	for (i = 0; i < rnode->natts; i++)
+	{
+		if (rnode->att_stat_tups[i])
+		{
+			heap_freetuple(rnode->att_stat_tups[i]);
+			rnode->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (rnode->attnum)
+		pfree(rnode->attnum);
+
+	if (rnode->att_stat_tups)
+		pfree(rnode->att_stat_tups);
+
+	pfree(rnode);
+
+	return;
+}
+
+/*
+ * Get the current relfilenode of this GTT.
+ */
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
+/*
+ * Get a relfilenode used by this GTT during the transaction life cycle.
+ */
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok)
+{
+	gtt_relfilenode		*rnode = NULL;
+	ListCell		*lc;
+
+	Assert(entry);
+
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == relfilenode)
+		{
+			rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (!missing_ok && rnode == NULL)
+		elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+	return rnode;
+}
+
+/*
+ * Get one GTT info from local hash.
+ */
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry == NULL && !missing_ok)
+		elog(ERROR, "relid %u not found in local hash", relid);
+
+	return entry;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 55f6e3711d8..fc75533263b 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 4928702aec0..fc6e64a79b6 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -104,7 +105,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -185,6 +186,17 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -601,14 +613,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]));
 		}
 
 		/*
@@ -1622,7 +1635,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;
@@ -1724,31 +1737,48 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/*
+		 * For global temporary table,
+		 * Update column statistic to localhash, not catalog.
+		 */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 9d22f648a84..a44eefa1f6b 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/toasting.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
@@ -390,6 +391,22 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+	{
+		if (gtt_storage_attached(RelationGetRelid(OldHeap)))
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("not support cluster global temporary table yet")));
+
+		relation_close(OldHeap, AccessExclusiveLock);
+		pgstat_progress_end_command();
+		return;
+	}
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
@@ -585,6 +602,8 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
 	TransactionId frozenXid;
 	MultiXactId cutoffMulti;
 
+	Assert(!RELATION_IS_GLOBAL_TEMP(OldHeap));
+
 	/* Mark the correct index as clustered */
 	if (OidIsValid(indexOid))
 		mark_index_clustered(OldHeap, indexOid, true);
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 53f48531419..c03191cce94 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -289,7 +289,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP_ON_CURRENT_SESSION(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, whereClause,
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 40a54ad0bd7..7895e7d99b9 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -30,6 +30,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/copyfrom_internal.h"
 #include "commands/progress.h"
@@ -659,6 +660,9 @@ CopyFrom(CopyFromState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	/* Check and init global temporary table storage in current backend */
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	/*
 	 * Set up a ModifyTableState so we can let FDW(s) init themselves for
 	 * foreign-table result relation(s).
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index c14ca27c5ed..d65ae895356 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -111,6 +111,7 @@ struct ReindexIndexCallbackState
 {
 	ReindexParams params;		/* options from statement */
 	Oid			locked_table_oid;	/* tracks previously locked table */
+	LOCKMODE	lockmode;
 };
 
 /*
@@ -570,7 +571,7 @@ DefineIndex(Oid relationId,
 	 * is more efficient.  Do this before any use of the concurrent option is
 	 * done.
 	 */
-	if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+	if (stmt->concurrent && !RelpersistenceTsTemp(get_rel_persistence(relationId)))
 		concurrent = true;
 	else
 		concurrent = false;
@@ -2581,9 +2582,9 @@ ReindexIndex(RangeVar *indexRelation, ReindexParams *params, bool isTopLevel)
 	 */
 	state.params = *params;
 	state.locked_table_oid = InvalidOid;
+	state.lockmode = AccessShareLock;
 	indOid = RangeVarGetRelidExtended(indexRelation,
-									  (params->options & REINDEXOPT_CONCURRENTLY) != 0 ?
-									  ShareUpdateExclusiveLock : AccessExclusiveLock,
+									  AccessShareLock,
 									  0,
 									  RangeVarCallbackForReindexIndex,
 									  &state);
@@ -2594,11 +2595,25 @@ ReindexIndex(RangeVar *indexRelation, ReindexParams *params, bool isTopLevel)
 	 */
 	persistence = get_rel_persistence(indOid);
 	relkind = get_rel_relkind(indOid);
+	if (persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		LOCKMODE	lockmode;
+
+		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0)
+			lockmode = ShareUpdateExclusiveLock;
+		else
+			lockmode = AccessExclusiveLock;
+
+		/* lock heap first */
+		Assert(OidIsValid(state.locked_table_oid));
+		LockRelationOid(state.locked_table_oid, lockmode);
+		LockRelationOid(indOid, lockmode);
+	}
 
 	if (relkind == RELKIND_PARTITIONED_INDEX)
 		ReindexPartitions(indOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 persistence != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(persistence))
 		ReindexRelationConcurrently(indOid, params);
 	else
 	{
@@ -2620,15 +2635,7 @@ RangeVarCallbackForReindexIndex(const RangeVar *relation,
 {
 	char		relkind;
 	struct ReindexIndexCallbackState *state = arg;
-	LOCKMODE	table_lockmode;
-
-	/*
-	 * Lock level here should match table lock in reindex_index() for
-	 * non-concurrent case and table locks used by index_concurrently_*() for
-	 * concurrent case.
-	 */
-	table_lockmode = (state->params.options & REINDEXOPT_CONCURRENTLY) != 0 ?
-		ShareUpdateExclusiveLock : ShareLock;
+	LOCKMODE	table_lockmode = state->lockmode;
 
 	/*
 	 * If we previously locked some other index's heap, and the name we're
@@ -2689,6 +2696,8 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 {
 	Oid			heapOid;
 	bool		result;
+	char		relpersistence;
+	int 		reindex_flags = 0;
 
 	/*
 	 * The lock level used here should match reindex_relation().
@@ -2699,15 +2708,27 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 	 * locks on our temporary table.
 	 */
 	heapOid = RangeVarGetRelidExtended(relation,
-									   (params->options & REINDEXOPT_CONCURRENTLY) != 0 ?
-									   ShareUpdateExclusiveLock : ShareLock,
+									   AccessShareLock,
 									   0,
 									   RangeVarCallbackOwnsTable, NULL);
 
+	relpersistence = get_rel_persistence(heapOid);
+	if (relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		LOCKMODE	lockmode;
+
+		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0)
+			lockmode = ShareUpdateExclusiveLock;
+		else
+			lockmode = ShareLock;
+
+		LockRelationOid(heapOid, lockmode);
+	}
+
 	if (get_rel_relkind(heapOid) == RELKIND_PARTITIONED_TABLE)
 		ReindexPartitions(heapOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(relpersistence))
 	{
 		result = ReindexRelationConcurrently(heapOid, params);
 
@@ -2721,9 +2742,14 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 		ReindexParams newparams = *params;
 
 		newparams.options |= REINDEXOPT_REPORT_PROGRESS;
+
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			reindex_flags |= REINDEX_REL_PROCESS_GLOBAL_TEMP;
+
+		reindex_flags |= REINDEX_REL_PROCESS_TOAST;
+		reindex_flags |= REINDEX_REL_CHECK_CONSTRAINTS;
 		result = reindex_relation(heapOid,
-								  REINDEX_REL_PROCESS_TOAST |
-								  REINDEX_REL_CHECK_CONSTRAINTS,
+								  reindex_flags,
 								  &newparams);
 		if (!result)
 			ereport(NOTICE,
@@ -3122,7 +3148,7 @@ ReindexMultipleInternal(List *relids, ReindexParams *params)
 			   relkind != RELKIND_PARTITIONED_TABLE);
 
 		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			relpersistence != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(relpersistence))
 		{
 			ReindexParams newparams = *params;
 
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 62465bacd81..ef37f79ba68 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -51,12 +51,32 @@ LockTableCommand(LockStmt *lockstmt)
 		RangeVar   *rv = (RangeVar *) lfirst(p);
 		bool		recurse = rv->inh;
 		Oid			reloid;
+		LOCKMODE	lockmode = lockstmt->mode;
+		char		relpersistence;
 
-		reloid = RangeVarGetRelidExtended(rv, lockstmt->mode,
-										  lockstmt->nowait ? RVR_NOWAIT : 0,
+		reloid = RangeVarGetRelidExtended(rv, NoLock, 0,
 										  RangeVarCallbackForLockTable,
 										  (void *) &lockstmt->mode);
 
+		relpersistence = get_rel_persistence(reloid);
+		if (relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			if (!lockstmt->nowait)
+				LockRelationOid(reloid, lockmode);
+			else if (!ConditionalLockRelationOid(reloid, lockmode))
+			{
+				/* try to throw error by name; relation could be deleted... */
+				char	   *relname = get_rel_name(reloid);
+
+				if (!relname)
+					return;		/* child concurrently dropped, just skip it */
+				ereport(ERROR,
+						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						 errmsg("could not obtain lock on relation \"%s\"",
+								relname)));
+			}
+		}
+
 		if (get_rel_relkind(reloid) == RELKIND_VIEW)
 			LockViewRecurse(reloid, lockstmt->mode, lockstmt->nowait, NIL);
 		else if (recurse)
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 72bfdc07a49..e5257f610f6 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -220,9 +223,12 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	rel = table_open(seqoid, AccessExclusiveLock);
 	tupDesc = RelationGetDescr(rel);
 
-	/* now initialize the sequence's data */
-	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	if (!RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		/* now initialize the sequence's data */
+		tuple = heap_form_tuple(tupDesc, value, null);
+		fill_seq_with_data(rel, tuple);
+	}
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -275,8 +281,6 @@ ResetSequence(Oid seq_relid)
 	Buffer		buf;
 	HeapTupleData seqdatatuple;
 	HeapTuple	tuple;
-	HeapTuple	pgstuple;
-	Form_pg_sequence pgsform;
 	int64		startv;
 
 	/*
@@ -287,12 +291,7 @@ ResetSequence(Oid seq_relid)
 	init_sequence(seq_relid, &elm, &seq_rel);
 	(void) read_seq_tuple(seq_rel, &buf, &seqdatatuple);
 
-	pgstuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seq_relid));
-	if (!HeapTupleIsValid(pgstuple))
-		elog(ERROR, "cache lookup failed for sequence %u", seq_relid);
-	pgsform = (Form_pg_sequence) GETSTRUCT(pgstuple);
-	startv = pgsform->seqstart;
-	ReleaseSysCache(pgstuple);
+	startv = get_seqence_start_value(seq_relid);
 
 	/*
 	 * Copy the existing sequence tuple.
@@ -451,6 +450,15 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 
 	init_sequence(relid, &elm, &seqrel);
 
+	if (RELATION_IS_GLOBAL_TEMP(seqrel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(seqrel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary sequence %s when other backend attached it.",
+						RelationGetRelationName(seqrel))));
+	}
+
 	rel = table_open(SequenceRelationId, RowExclusiveLock);
 	seqtuple = SearchSysCacheCopy1(SEQRELID,
 								   ObjectIdGetDatum(relid));
@@ -611,7 +619,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +944,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1153,6 +1161,14 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	/* Initializes the storage for sequence which the global temporary table belongs. */
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1927,3 +1943,58 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+/*
+ * Get the startValue of the sequence from syscache.
+ */
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+/*
+ * Initialize sequence which global temporary table belongs.
+ */
+void
+gtt_init_seq(Relation rel)
+{
+	Datum		value[SEQ_COL_LASTCOL];
+	bool		null[SEQ_COL_LASTCOL];
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL - 1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+	null[SEQ_COL_LASTVAL - 1] = false;
+
+	value[SEQ_COL_LOG - 1] = Int64GetDatum((int64)0);
+	null[SEQ_COL_LOG - 1] = false;
+
+	value[SEQ_COL_CALLED - 1] = BoolGetDatum(false);
+	null[SEQ_COL_CALLED - 1] = false;
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1a2f159f24e..ca6e64937ee 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -118,6 +119,7 @@ typedef struct OnCommitItem
 	 */
 	SubTransactionId creating_subid;
 	SubTransactionId deleting_subid;
+	bool			 is_global_temp;
 } OnCommitItem;
 
 static List *on_commits = NIL;
@@ -602,7 +604,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
 static char GetAttributeCompression(Oid atttypid, char *compression);
-
+static OnCommitAction gtt_oncommit_option(List *options);
 
 /* ----------------------------------------------------------------
  *		DefineRelation
@@ -647,6 +649,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -658,7 +661,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * Check consistency of arguments
 	 */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !RelpersistenceTsTemp(stmt->relation->relpersistence))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -688,7 +691,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	if (RelpersistenceTsTemp(stmt->relation->relpersistence)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -789,6 +792,50 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* For global temporary table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		if (!(relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE))
+			elog(ERROR, "Only support global temporary regular table.");
+
+		/* Check parent table */
+		if (inheritOids)
+			elog(ERROR, "Not support global temporary partition table or inherit table.");
+
+		/* Check oncommit clause and save to reloptions */
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temporary table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1415,7 +1462,7 @@ RemoveRelations(DropStmt *drop)
 		 * relation persistence cannot be known without its OID.
 		 */
 		if (drop->concurrent &&
-			get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(get_rel_persistence(relOid)))
 		{
 			Assert(list_length(drop->objects) == 1 &&
 				   drop->removeType == OBJECT_INDEX);
@@ -1624,9 +1671,9 @@ ExecuteTruncate(TruncateStmt *stmt)
 		Relation	rel;
 		bool		recurse = rv->inh;
 		Oid			myrelid;
-		LOCKMODE	lockmode = AccessExclusiveLock;
+		LOCKMODE	lockmode;
 
-		myrelid = RangeVarGetRelidExtended(rv, lockmode,
+		myrelid = RangeVarGetRelidExtended(rv, AccessShareLock,
 										   0, RangeVarCallbackForTruncate,
 										   NULL);
 
@@ -1634,9 +1681,21 @@ ExecuteTruncate(TruncateStmt *stmt)
 		if (list_member_oid(relids, myrelid))
 			continue;
 
-		/* open the relation, we already hold a lock on it */
+		/* open the relation, we need hold a low-level lock first */
 		rel = table_open(myrelid, NoLock);
 
+		/*
+		 * Truncate global temp table only cleans up the data in current backend,
+		 * only low-level locks are required.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel))
+			lockmode = AccessShareLock;
+		else
+		{
+			lockmode = AccessExclusiveLock;
+			LockRelationOid(myrelid, lockmode);
+		}
+
 		/*
 		 * RangeVarGetRelidExtended() has done most checks with its callback,
 		 * but other checks with the now-opened Relation remain.
@@ -1886,6 +1945,7 @@ ExecuteTruncateGuts(List *explicit_rels,
 	foreach(cell, rels)
 	{
 		Relation	rel = (Relation) lfirst(cell);
+		LOCKMODE	lockmode;
 
 		/* Skip partitioned tables as there is nothing to do */
 		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
@@ -1936,6 +1996,19 @@ ExecuteTruncateGuts(List *explicit_rels,
 			continue;
 		}
 
+		/*
+		 * Skip the global temporary table that is not initialized for storage
+		 * in current backend.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel))
+		{
+			lockmode = AccessShareLock;
+			if (!gtt_storage_attached(RelationGetRelid(rel)))
+				continue;
+		}
+		else
+			lockmode = AccessExclusiveLock;
+
 		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
@@ -1954,6 +2027,7 @@ ExecuteTruncateGuts(List *explicit_rels,
 			Oid			heap_relid;
 			Oid			toast_relid;
 			ReindexParams reindex_params = {0};
+			int			reindex_flags = 0;
 
 			/*
 			 * This effectively deletes all rows in the table, and may be done
@@ -1981,17 +2055,21 @@ ExecuteTruncateGuts(List *explicit_rels,
 			if (OidIsValid(toast_relid))
 			{
 				Relation	toastrel = relation_open(toast_relid,
-													 AccessExclusiveLock);
+													 lockmode);
 
 				RelationSetNewRelfilenode(toastrel,
 										  toastrel->rd_rel->relpersistence);
 				table_close(toastrel, NoLock);
 			}
 
+			reindex_flags = REINDEX_REL_PROCESS_TOAST;
+			if (RELATION_IS_GLOBAL_TEMP(rel))
+				reindex_flags |= REINDEX_REL_PROCESS_GLOBAL_TEMP;
+
 			/*
 			 * Reconstruct the indexes to match, and we're done.
 			 */
-			reindex_relation(heap_relid, REINDEX_REL_PROCESS_TOAST,
+			reindex_relation(heap_relid, reindex_flags,
 							 &reindex_params);
 		}
 
@@ -4032,6 +4110,16 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temporary table only current backend use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -5363,6 +5451,24 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 
 			rel = table_open(tab->relid, NoLock);
 			find_composite_type_dependencies(rel->rd_rel->reltype, rel, NULL);
+
+			if (RELATION_IS_GLOBAL_TEMP(rel) && tab->rewrite > 0)
+			{
+				if (tab->chgPersistence)
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot change global temporary table persistence setting")));
+
+				if(gtt_storage_attached(tab->relid))
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("Only support alter global temporary table in an empty context."),
+						 errhint("Please create a new connection and execute ALTER TABLE on the new connection.")));
+
+				/* There is no need to override the whole temp table */
+				tab->rewrite = 0;
+			}
+
 			table_close(rel, NoLock);
 		}
 
@@ -5414,6 +5520,8 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot rewrite temporary tables of other sessions")));
 
+			Assert(!RELATION_IS_GLOBAL_TEMP(OldHeap));
+
 			/*
 			 * Select destination tablespace (same as original unless user
 			 * requested a change)
@@ -9045,6 +9153,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -13743,6 +13857,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -13942,6 +14059,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temporary table");
+
 	/* Check first if relation can be moved to new tablespace */
 	if (!CheckRelationTableSpaceMove(rel, newTableSpace))
 	{
@@ -14245,7 +14365,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(RelationGetSmgr(rel), dstrel, MAIN_FORKNUM,
@@ -15843,6 +15963,7 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
 	switch (rel->rd_rel->relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -16257,7 +16378,7 @@ AlterSeqNamespaces(Relation classRel, Relation rel,
  * Register a newly-created relation's ON COMMIT action.
  */
 void
-register_on_commit_action(Oid relid, OnCommitAction action)
+register_on_commit_action(Oid relid, OnCommitAction action, bool is_gloal_temp)
 {
 	OnCommitItem *oc;
 	MemoryContext oldcxt;
@@ -16276,6 +16397,7 @@ register_on_commit_action(Oid relid, OnCommitAction action)
 	oc->oncommit = action;
 	oc->creating_subid = GetCurrentSubTransactionId();
 	oc->deleting_subid = InvalidSubTransactionId;
+	oc->is_global_temp = is_gloal_temp;
 
 	/*
 	 * We use lcons() here so that ON COMMIT actions are processed in reverse
@@ -16321,6 +16443,7 @@ PreCommit_on_commit_actions(void)
 	ListCell   *l;
 	List	   *oids_to_truncate = NIL;
 	List	   *oids_to_drop = NIL;
+	List	   *oids_to_truncate_gtt = NIL;
 
 	foreach(l, on_commits)
 	{
@@ -16344,7 +16467,12 @@ PreCommit_on_commit_actions(void)
 				 * tables, as they must still be empty.
 				 */
 				if ((MyXactFlags & XACT_FLAGS_ACCESSEDTEMPNAMESPACE))
-					oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid);
+				{
+					if (oc->is_global_temp)
+						oids_to_truncate_gtt = lappend_oid(oids_to_truncate_gtt, oc->relid);
+					else
+						oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid);
+				}
 				break;
 			case ONCOMMIT_DROP:
 				oids_to_drop = lappend_oid(oids_to_drop, oc->relid);
@@ -16361,7 +16489,10 @@ PreCommit_on_commit_actions(void)
 	 * exists at truncation time.
 	 */
 	if (oids_to_truncate != NIL)
-		heap_truncate(oids_to_truncate);
+		heap_truncate(oids_to_truncate, false);
+
+	if (oids_to_truncate_gtt != NIL)
+		heap_truncate(oids_to_truncate_gtt, true);
 
 	if (oids_to_drop != NIL)
 	{
@@ -17360,6 +17491,13 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot attach temporary relation of another session as partition")));
 
+	/* If the parent is permanent, so must be all of its partitions. */
+	if (attachrel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach a global temporary relation as partition of permanent relation \"%s\"",
+						RelationGetRelationName(rel))));
+
 	/* Check if there are any columns in attachrel that aren't in the parent */
 	tupleDesc = RelationGetDescr(attachrel);
 	natts = tupleDesc->natts;
@@ -18830,3 +18968,40 @@ GetAttributeCompression(Oid atttypid, char *compression)
 
 	return cmethod;
 }
+
+/*
+ * Parse the on commit clause for the temporary table
+ */
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			bool	res = false;
+			char	*sval = defGetString(def);
+
+			/* It has to be a Boolean value */
+			if (!parse_bool(sval, &res))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
+
+			if (res)
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 5c4bc15b441..3a861c47946 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"
@@ -1315,6 +1316,27 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = RELATION_IS_GLOBAL_TEMP(relation);
+
+	 /* For global temporary table */
+	if (is_gtt)
+	{
+		/* Store relation statistics and transaction information to the localhash */
+		up_gtt_relstats(RelationGetRelid(relation),
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+
+		/* Update relation statistics to local relcache */
+		relation->rd_rel->relpages = (int32) num_pages;
+		relation->rd_rel->reltuples = (float4) num_tuples;
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+		if (TransactionIdIsNormal(frozenxid))
+			relation->rd_rel->relfrozenxid = frozenxid;
+
+		if (MultiXactIdIsValid(minmulti))
+			relation->rd_rel->relminmxid = minmulti;
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1328,17 +1350,23 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (!is_gtt &&
+		pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (!is_gtt &&
+		pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (!is_gtt &&
+		pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1351,7 +1379,15 @@ vac_update_relstats(Relation relation,
 		/*
 		 * If we didn't find any indexes, reset relhasindex.
 		 */
-		if (pgcform->relhasindex && !hasindex)
+		if (is_gtt &&
+			RelationGetIndexList(relation) != NIL)
+		{
+			/*
+			 * Global temporary tables may contain indexes that are not valid locally.
+			 * The catalog should not be updated based on local invalid index.
+			 */
+		}
+		else if (pgcform->relhasindex && !hasindex)
 		{
 			pgcform->relhasindex = false;
 			dirty = true;
@@ -1383,7 +1419,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNextTransactionId(),
@@ -1394,7 +1431,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1502,6 +1540,13 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/*
+		 * The relfrozenxid for a global temporary talble is stored in localhash,
+		 * not pg_class, See list_all_session_gtt_frozenxids()
+		 */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1559,6 +1604,43 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/* If enable global temporary table */
+	if (max_active_gtt > 0)
+	{
+		TransactionId	safe_age;
+		/*  */
+		TransactionId	oldest_gtt_frozenxid =
+			list_all_backend_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid))
+		{
+			safe_age = oldest_gtt_frozenxid + vacuum_gtt_defer_check_age;
+			if (safe_age < FirstNormalTransactionId)
+				safe_age += FirstNormalTransactionId;
+
+			/*
+			 * We tolerate that the minimum age of gtt is less than
+			 * the minimum age of conventional tables, otherwise it will
+			 * throw warning message.
+			 */
+			if (TransactionIdIsNormal(safe_age) &&
+				TransactionIdPrecedes(safe_age, newFrozenXid))
+			{
+				ereport(WARNING,
+					(errmsg("global temp table oldest relfrozenxid %u is the oldest in the entire db",
+							oldest_gtt_frozenxid),
+					 errdetail("The oldest relfrozenxid in pg_class is %u", newFrozenXid),
+					 errhint("If they differ greatly, please consider cleaning up the data in global temp table.")));
+			}
+
+			/*
+			 * We need to ensure that the clog required by gtt is not cleand.
+			 */
+			if (TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+				newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1910,6 +1992,19 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	/*
+	 * Skip those global temporary table that are not initialized in
+	 * current backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel) &&
+		!gtt_storage_attached(RelationGetRelid(rel)))
+	{
+		relation_close(rel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 4df05a0b33d..4c181e2e14e 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -527,6 +527,12 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	/* Global temporary table are not sensible. */
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b3ce4bae530..611e3f18a70 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -784,6 +784,10 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		/* This is one kind of temp table */
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 5c723bc54e1..191e0f6fd21 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d328856ae5b..4e2bbb224cc 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -38,6 +38,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -633,6 +634,9 @@ ExecInsert(ModifyTableState *mtstate,
 		resultRelInfo->ri_IndexRelationDescs == NULL)
 		ExecOpenIndices(resultRelInfo, onconflict != ONCONFLICT_NONE);
 
+	/* Init storage for global temporary table in current backend */
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	/*
 	 * BEFORE ROW INSERT Triggers.
 	 *
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 296dd75c1b6..d971aea2546 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -48,7 +48,7 @@
 #include "partitioning/partprune.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/lsyscache.h"
-
+#include "utils/rel.h"
 
 /* results of subquery_is_pushdown_safe */
 typedef struct pushdown_safety_info
@@ -619,7 +619,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			if (RelpersistenceTsTemp(get_rel_persistence(rte->relid)))
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index bd01ec0526f..ff4e81ca2cf 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6071,7 +6071,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
-	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+	if (RELATION_IS_TEMP(heap) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index c5194fdbbf2..38d7c658541 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -30,6 +30,7 @@
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temporary table in current backend */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 146ee8dd1ea..2d4e9393f00 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2907,6 +2907,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 08f1bf1031c..d6048f2ee57 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3410,17 +3410,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; }
@@ -11683,19 +11677,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 c5c3f26ecf1..2a2b2789077 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -82,6 +82,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);
 
 
 /*
@@ -3665,3 +3666,53 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * Like function isQueryUsingTempRelation_walker
+ * return true if any relation underlying
+ * the query is a global temporary table.
+ */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* Check if the query uses global temporary table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 313d7b6ff02..38e0e162e82 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -447,6 +447,13 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	/*
+	 * If a sequence is bound to a global temporary table, then the sequence
+	 * must been "global temporary"
+	 */
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
@@ -3326,6 +3333,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		cxt.isforeign = false;
 	}
 	cxt.relation = stmt->relation;
+	/* Sets the table persistence to the context */
+	cxt.relation->relpersistence = RelationGetRelPersistence(rel);
 	cxt.rel = rel;
 	cxt.inhRelations = NIL;
 	cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 96332320a73..75ed4d0ae9e 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2115,6 +2115,14 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/*
+			 * Aotuvacuum cannot vacuum the private data stored in each backend
+			 * that belongs to global temporary table, so skip them.
+			 */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2181,7 +2189,7 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		if (RelpersistenceTsTemp(classForm->relpersistence))
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 08ebabfe96a..c346c59c7f4 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlogutils.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -2934,6 +2935,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * Returns 0 if this global temporary table is not initialized in current
+	 * backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 9fa3e0631e6..cc3eb928bc6 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -23,6 +23,7 @@
 #include "access/syncscan.h"
 #include "access/twophase.h"
 #include "commands/async.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
@@ -143,6 +144,7 @@ CalculateShmemSize(int *num_semaphores)
 	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
@@ -246,6 +248,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* For global temporary table shared hashtable */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index bd3c7a47fe2..22481374778 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -65,6 +65,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -5131,3 +5132,78 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temporary table.
+ */
+int
+list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct		*arrayP = procArray;
+	TransactionId		result = InvalidTransactionId;
+	int			index;
+	uint8			flags = 0;
+	int			i = 0;
+
+	/* return 0 if feature is disabled */
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+		*n = 0;
+	}
+
+	/* Disable in standby node */
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		uint8           statusFlags = ProcGlobal->statusFlags[index];
+
+		if (statusFlags & flags)
+			continue;
+
+		/* Fetch all backend that is belonging to MyDatabaseId */
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->backend_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->backend_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->backend_gtt_frozenxid, result))
+				result = proc->backend_gtt_frozenxid;
+
+			/* save backend pid and backend level oldest relfrozenxid */
+			if (max_size > 0)
+			{
+				pids[i] = proc->pid;
+				xids[i] = proc->backend_gtt_frozenxid;
+				i++;
+			}
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (max_size > 0)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 862097352bb..4edd3b31f7a 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -176,7 +176,9 @@ static const char *const BuiltinTrancheNames[] = {
 	/* LWTRANCHE_PARALLEL_APPEND: */
 	"ParallelAppend",
 	/* LWTRANCHE_PER_XACT_PREDICATE_LIST: */
-	"PerXactPredicateList"
+	"PerXactPredicateList",
+	/* LWTRANCHE_GTT_CTL */
+	"GlobalTempTableControl"
 };
 
 StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index b7d9da0aa9f..8051f2053f9 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -393,6 +393,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;	/* init backend level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
@@ -578,6 +579,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;	/* init backend level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index d5a7fb13f3c..8225cf6219f 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -982,6 +982,13 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/*
+			 * For global temporary table ,each backend has its own storage,
+			 * also only sees its own storage. Use Backendid to identify them.
+			 */
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 10895fb2876..66255eb7604 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -108,6 +108,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -5115,12 +5116,26 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								/* For global temporary table, get statistic data from localhash */
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -5368,15 +5383,28 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6820,6 +6848,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;
@@ -6837,6 +6866,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6848,6 +6885,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;
@@ -6863,6 +6902,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7781,6 +7828,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))
@@ -7793,6 +7842,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7805,6 +7863,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.
@@ -7824,6 +7884,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 4ebaa552a27..78c33d2ac87 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -3113,6 +3114,19 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		/* For global temporary table, get statistic data from localhash */
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index b54c9117669..65740a1d3b2 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -65,6 +65,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1152,6 +1153,36 @@ retry:
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+				TransactionId	relfrozenxid = InvalidTransactionId;
+				MultiXactId 	relminmxid = InvalidMultiXactId;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+
+				/* For global temporary table, get relstat data from localhash */
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								&relfrozenxid,
+								&relminmxid);
+
+				/* And put them to local relcache */
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+				if (TransactionIdIsNormal(relfrozenxid))
+					relation->rd_rel->relfrozenxid = relfrozenxid;
+
+				if (MultiXactIdIsValid(relminmxid))
+					relation->rd_rel->relminmxid = relminmxid;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1209,6 +1240,8 @@ retry:
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			/* The state of the global temporary table's index may need to be set */
+			gtt_fix_index_backend_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -1351,7 +1384,22 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+			/*
+			 * For global temporary table, get the latest relfilenode
+			 * from localhash and put it in relcache.
+			 */
+			if (OidIsValid(newrelnode) &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2302,6 +2350,9 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		/* The state of the global temporary table's index may need to be set */
+		gtt_fix_index_backend_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3582,6 +3633,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3691,28 +3746,39 @@ void
 RelationSetNewRelfilenode(Relation relation, char persistence)
 {
 	Oid			newrelfilenode;
-	Relation	pg_class;
-	HeapTuple	tuple;
+	Relation	pg_class = NULL;
+	HeapTuple	tuple = NULL;
 	Form_pg_class classform;
 	MultiXactId minmulti = InvalidMultiXactId;
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
 
+	/*
+	 * For global temporary table, storage information for the table is
+	 * maintained locally, not in catalog.
+	 */
+	bool		update_catalog = !RELATION_IS_GLOBAL_TEMP(relation);
+
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
 
-	/*
-	 * Get a writable copy of the pg_class tuple for the given relation.
-	 */
-	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+	memset(&classform, 0, sizeof(classform));
 
-	tuple = SearchSysCacheCopy1(RELOID,
-								ObjectIdGetDatum(RelationGetRelid(relation)));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "could not find tuple for relation %u",
-			 RelationGetRelid(relation));
-	classform = (Form_pg_class) GETSTRUCT(tuple);
+	if (update_catalog)
+	{
+		/*
+		 * Get a writable copy of the pg_class tuple for the given relation.
+		 */
+		pg_class = table_open(RelationRelationId, RowExclusiveLock);
+
+		tuple = SearchSysCacheCopy1(RELOID,
+									ObjectIdGetDatum(RelationGetRelid(relation)));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u",
+				 RelationGetRelid(relation));
+		classform = (Form_pg_class) GETSTRUCT(tuple);
+	}
 
 	/*
 	 * Schedule unlinking of the old storage at transaction commit.
@@ -3738,7 +3804,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;
@@ -3758,6 +3824,18 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 			break;
 	}
 
+	/* For global temporary table */
+	if (!update_catalog)
+	{
+		Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+		Assert(RELATION_IS_GLOBAL_TEMP(relation));
+		Assert(!RelationIsMapped(relation));
+
+		/* Make cache invalid and set new relnode to local cache. */
+		CacheInvalidateRelcache(relation);
+		relation->rd_node.relNode = relnode;
+	}
 	/*
 	 * If we're dealing with a mapped index, pg_class.relfilenode doesn't
 	 * change; instead we have to send the update to the relation mapper.
@@ -3767,7 +3845,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	 * possibly-inaccurate values of relpages etc, but those will be fixed up
 	 * later.
 	 */
-	if (RelationIsMapped(relation))
+	else if (RelationIsMapped(relation))
 	{
 		/* This case is only supported for indexes */
 		Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3813,9 +3891,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 		CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 	}
 
-	heap_freetuple(tuple);
+	if (update_catalog)
+	{
+		heap_freetuple(tuple);
 
-	table_close(pg_class, RowExclusiveLock);
+		table_close(pg_class, RowExclusiveLock);
+	}
 
 	/*
 	 * Make the pg_class row change or relation map change visible.  This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index d2ce4a84506..7717ee2ca13 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -44,6 +44,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "commands/prepare.h"
 #include "commands/trigger.h"
@@ -152,6 +153,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temporary table feature.
+ * table schema are still saved in catalog.
+ *
+ * num > 0 means allows the database to manage multiple active tables at the same time.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2127,6 +2140,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 "
@@ -2697,6 +2719,16 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"vacuum_gtt_defer_check_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("The defer check age of GTT, used to check expired data after vacuum."),
+			NULL
+		},
+		&vacuum_gtt_defer_check_age,
+		10000, 0, 1000000,
+		NULL, NULL, NULL
+	},
+
 	/*
 	 * See also CheckRequiredParameterValues() if this parameter changes
 	 */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index e9f68e89867..40ed2e5f263 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2503,6 +2503,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
 		dopt->no_unlogged_table_data)
 		return;
 
+	/* Don't dump data in global temporary table/sequence */
+	if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		return;
+
 	/* Check that the data is not explicitly excluded */
 	if (simple_oid_list_member(&tabledata_exclude_oids,
 							   tbinfo->dobj.catId.oid))
@@ -15685,6 +15689,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
 		char	   *foreign = "";
+		char	   *table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15738,9 +15743,15 @@ dumpTableSchema(Archive *fout, const 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);
 
@@ -16104,6 +16115,15 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 			}
 		}
 
+		/*
+		 * Transaction information for the global temporary table is not stored
+		 * in the pg_class.
+		 */
+		if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			Assert(tbinfo->frozenxid == 0);
+			Assert(tbinfo->minmxid == 0);
+		}
 		/*
 		 * In binary_upgrade mode, arrange to restore the old relfrozenxid and
 		 * relminmxid of all vacuumable relations.  (While vacuum.c processes
@@ -16111,7 +16131,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		 * of other relations; so this "if" lacks RELKIND_TOASTVALUE, and the
 		 * child toast table is handled below.)
 		 */
-		if (dopt->binary_upgrade &&
+		else if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
 			 tbinfo->relkind == RELKIND_MATVIEW))
 		{
@@ -17114,6 +17134,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	PQExpBuffer query = createPQExpBuffer();
 	PQExpBuffer delqry = createPQExpBuffer();
 	char	   *qseqname;
+	bool	   global_temp_seq = false;
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
@@ -17123,9 +17144,12 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
-						  "FROM pg_catalog.pg_sequence "
-						  "WHERE seqrelid = '%u'::oid",
+						  "seqcache, seqcycle, "
+						  "c.relpersistence "
+						  "FROM pg_catalog.pg_sequence s, "
+						  "pg_catalog.pg_class c "
+						  "WHERE seqrelid = '%u'::oid "
+						  "and s.seqrelid = c.oid",
 						  tbinfo->dobj.catId.oid);
 	}
 	else if (fout->remoteVersion >= 80400)
@@ -17170,6 +17194,9 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
 
+	if (fout->remoteVersion >= 140000)
+		global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
 	if (strcmp(seqtype, "smallint") == 0)
@@ -17247,9 +17274,13 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	}
 	else
 	{
-		appendPQExpBuffer(query,
-						  "CREATE SEQUENCE %s\n",
-						  fmtQualifiedDumpable(tbinfo));
+		appendPQExpBuffer(query, "CREATE ");
+
+		if (global_temp_seq)
+			appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+		appendPQExpBuffer(query, "SEQUENCE %s\n",
+								  fmtQualifiedDumpable(tbinfo));
 
 		if (strcmp(seqtype, "bigint") != 0)
 			appendPQExpBuffer(query, "    AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index ad5f3919956..f3819860096 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -88,7 +88,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -178,7 +178,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 5d9a26cf822..2de11d5d707 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -447,8 +449,17 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
 			 "         ON c.relnamespace = n.oid "
 			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
+			 CppAsString2(RELKIND_MATVIEW) ") AND ");
+
+	if (skip_gtt)
+	{
+		/* exclude global temp tables */
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+			"    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND ");
+	}
+
 	/* exclude possible orphaned temp tables */
+	snprintf(query + strlen(query), sizeof(query) - strlen(query),
 			 "    ((n.nspname !~ '^pg_temp_' AND "
 			 "      n.nspname !~ '^pg_toast_temp_' AND "
 			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 3628bd74a7b..bbb9b5ea13d 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -407,7 +407,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -645,7 +645,10 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+			/* exclude global temp tables */
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -656,7 +659,10 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+		/* exclude global temp tables */
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index ca0795f68ff..018a2effd4b 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -389,7 +389,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index ea4ca5c05c2..68d04e554ee 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -4066,7 +4066,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ecae9df8eda..d226b2dfef9 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1059,6 +1059,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2542,6 +2544,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2755,6 +2760,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE", "SEQUENCE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 6ce480b49c1..dda3f3c5a60 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -85,7 +85,7 @@ extern Oid	heap_create_with_catalog(const char *relname,
 
 extern void heap_drop_with_catalog(Oid relid);
 
-extern void heap_truncate(List *relids);
+extern void heap_truncate(List *relids, bool is_global_temp);
 
 extern void heap_truncate_one_rel(Relation rel);
 
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 008f723e104..875b1003899 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -157,6 +157,7 @@ extern void reindex_index(Oid indexId, bool skip_constraint_checks,
 #define REINDEX_REL_CHECK_CONSTRAINTS		0x04
 #define REINDEX_REL_FORCE_INDEXES_UNLOGGED	0x08
 #define REINDEX_REL_FORCE_INDEXES_PERMANENT 0x10
+#define REINDEX_REL_PROCESS_GLOBAL_TEMP		0x20
 
 extern bool reindex_relation(Oid relid, int flags, ReindexParams *params);
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index fef9945ed8f..9176b7dcc07 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -172,6 +172,7 @@ DECLARE_INDEX(pg_class_tblspc_relfilenode_index, 3455, ClassTblspcRelfilenodeInd
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index d068d6532ec..fd5089388f2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5735,6 +5735,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '9874',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '9875',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '9876',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '9877',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 0ab32b44e91..92e9f8ba485 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,7 +22,7 @@
 /* GUC variables */
 extern int	wal_skip_threshold;
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationPreTruncate(Relation rel);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 00000000000..8a3d9558712
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,45 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern int		vacuum_gtt_defer_check_age;
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Oid relid,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void force_enable_gtt_index(Relation index);
+extern void gtt_fix_index_backend_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index 40544dd4c70..7b66d808fc5 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 336549cc5f0..3e8167134b7 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -86,7 +86,7 @@ extern void find_composite_type_dependencies(Oid typeOid,
 
 extern void check_of_type(HeapTuple typetuple);
 
-extern void register_on_commit_action(Oid relid, OnCommitAction action);
+extern void register_on_commit_action(Oid relid, OnCommitAction action, bool is_gloal_temp);
 extern void remove_on_commit_action(Oid relid);
 
 extern void PreCommit_on_commit_actions(void);
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 8336c2c5a29..bddcfe7256d 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index c86ccdaf608..6b395551c1d 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -399,6 +399,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index a8f052e4845..4b4ed1a13aa 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -189,6 +189,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_SHARED_TIDBITMAP,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_PER_XACT_PREDICATE_LIST,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index be67d8a8616..e2f8bb5162d 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -157,6 +157,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId backend_gtt_frozenxid;	/* backend level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index b01fa52139a..8efffa55ac5 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -94,4 +94,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index aa18d304ac0..524c9d7de3f 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -288,6 +288,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 b4faa1c1238..a74558a8383 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	SMgrRelation rd_smgr;		/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -326,6 +326,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -608,11 +609,13 @@ RelationGetSmgr(Relation rel)
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -620,6 +623,7 @@ RelationGetSmgr(Relation rel)
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -632,6 +636,30 @@ RelationGetSmgr(Relation rel)
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP_ON_CURRENT_SESSION
+ *		Test a rel is either local temp relation of this session
+ *		or global temp relation.
+ */
+#define RELATION_IS_TEMP_ON_CURRENT_SESSION(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is local temp relation or global temporary relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RelpersistenceTsTemp
+ *		Test a relpersistence is local temp relation or global temporary relation.
+ */
+#define RelpersistenceTsTemp(relpersistence) \
+	(relpersistence == RELPERSISTENCE_TEMP || \
+	 relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -677,6 +705,19 @@ RelationGetSmgr(Relation rel)
 	 (relation)->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&	\
 	 !IsCatalogRelation(relation))
 
+/* For global temporary table */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/* Get on commit clause value only for global temporary table */
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+/* Get relpersistence for relation */
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
-- 
2.30.1 (Apple Git-130)

0004-gtt-v58-regress.patchapplication/octet-stream; name=0004-gtt-v58-regress.patchDownload
diff --git a/src/test/isolation/expected/gtt-sequence.out b/src/test/isolation/expected/gtt-sequence.out
new file mode 100644
index 00000000000..31db2ebd423
--- /dev/null
+++ b/src/test/isolation/expected/gtt-sequence.out
@@ -0,0 +1,48 @@
+unused step name: s1_seq_restart
+Parsed test spec with 2 sessions
+
+starting permutation: s1_seq_next s2_seq_next s1_seq_next
+step s1_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      1
+(1 row)
+
+step s2_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      1
+(1 row)
+
+step s1_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      2
+(1 row)
+
+
+starting permutation: s1_select s2_select s1_insert s2_insert s1_select s2_select
+step s1_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+(0 rows)
+
+step s2_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+(0 rows)
+
+step s1_insert: insert into gtt_with_seq values(1);
+step s2_insert: insert into gtt_with_seq values(10);
+step s1_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+ 1| 3
+(1 row)
+
+step s2_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+10| 1
+(1 row)
+
diff --git a/src/test/isolation/expected/gtt-table.out b/src/test/isolation/expected/gtt-table.out
new file mode 100644
index 00000000000..5825773aa12
--- /dev/null
+++ b/src/test/isolation/expected/gtt-table.out
@@ -0,0 +1,675 @@
+Parsed test spec with 3 sessions
+
+starting permutation: s1_update_d
+step s1_update_d: update gtt_on_commit_delete_row set b = 'update'
+
+starting permutation: s1_select_d s1_insert_d s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_begin s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+1|test1
+(1 row)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_begin s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+1|test1
+(1 row)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_select_d s1_truncate_d s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_begin s1_insert_d s1_select_d s1_truncate_d s1_select_d s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_begin s1_insert_d s1_select_d s1_truncate_d s1_select_d s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_insert_d s1_select_d s1_begin s1_insert_d s1_select_d s1_save_1 s1_truncate_d s1_insert_d s1_select_d s1_save_2 s1_truncate_d s1_insert_d s1_select_d s1_save_3 s1_rollback_to_save_2 s1_select_d s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_save_1: SAVEPOINT save1
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_save_2: SAVEPOINT save2
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+4|test1
+(1 row)
+
+step s1_save_3: SAVEPOINT save3
+step s1_rollback_to_save_2: rollback to savepoint save2
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+5|test1
+(2 rows)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_insert_d s1_select_d s1_begin s1_insert_d s1_select_d s1_save_1 s1_truncate_d s1_insert_d s1_select_d s1_save_2 s1_truncate_d s1_insert_d s1_select_d s1_save_3 s1_rollback_to_save_2 s1_select_d s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_save_1: SAVEPOINT save1
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_save_2: SAVEPOINT save2
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+4|test1
+(1 row)
+
+step s1_save_3: SAVEPOINT save3
+step s1_rollback_to_save_2: rollback to savepoint save2
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+5|test1
+(2 rows)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_update_p
+step s2_update_p: update gtt_on_commit_preserve_row set b = 'update'
+
+starting permutation: s2_select_p s2_insert_p s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_begin s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_begin s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_select_p s2_insert_p s2_select_p s2_truncate_p s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_select_p s2_insert_p s2_begin s2_insert_p s2_select_p s2_truncate_p s2_select_p s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_insert_p s2_begin s2_insert_p s2_select_p s2_truncate_p s2_select_p s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_insert_p s2_select_p s2_begin s2_insert_p s2_select_p s2_save_1 s2_truncate_p s2_insert_p s2_select_p s2_save_2 s2_truncate_p s2_insert_p s2_select_p s2_save_3 s2_rollback_to_save_2 s2_select_p s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_save_1: SAVEPOINT save1
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_save_2: SAVEPOINT save2
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+4|test10
+(1 row)
+
+step s2_save_3: SAVEPOINT save3
+step s2_rollback_to_save_2: rollback to savepoint save2
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+
+starting permutation: s2_insert_p s2_select_p s2_begin s2_insert_p s2_select_p s2_save_1 s2_truncate_p s2_insert_p s2_select_p s2_save_2 s2_truncate_p s2_insert_p s2_select_p s2_save_3 s2_rollback_to_save_2 s2_select_p s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_save_1: SAVEPOINT save1
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_save_2: SAVEPOINT save2
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+4|test10
+(1 row)
+
+step s2_save_3: SAVEPOINT save3
+step s2_rollback_to_save_2: rollback to savepoint save2
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_insert_p s2_insert_p s1_select_p s2_select_p
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_truncate_p s2_truncate_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_begin s1_insert_d s2_insert_d s1_truncate_d s2_insert_d s1_commit
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s2_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s2_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_commit: commit
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_reindex_p s2_reindex_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_reindex_p: reindex table gtt_on_commit_preserve_row
+step s2_reindex_p: reindex table gtt_on_commit_preserve_row
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_reindex_i_p s2_reindex_i_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_reindex_i_p: reindex index gtt_on_commit_preserve_row_pkey
+step s2_reindex_i_p: reindex index gtt_on_commit_preserve_row_pkey
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_insert_c s3_create_c s3_insert_c s1_insert_c s1_analyze_c s2_analyze_c s3_analyze_c s1_select_c s2_select_c s3_select_c
+step s2_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s3_create_c: create unique index idx_temp_table_a on gtt_test_createindex(a)
+step s3_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s1_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s1_analyze_c: analyze gtt_test_createindex
+step s2_analyze_c: analyze gtt_test_createindex
+step s3_analyze_c: analyze gtt_test_createindex
+step s1_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                                               
+---------------------------------------------------------
+Index Scan using idx_temp_table_a on gtt_test_createindex
+  Index Cond: (a = 1)                                    
+(2 rows)
+
+step s2_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                      
+--------------------------------
+Seq Scan on gtt_test_createindex
+  Filter: (a = 1)               
+(2 rows)
+
+step s3_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                                               
+---------------------------------------------------------
+Index Scan using idx_temp_table_a on gtt_test_createindex
+  Index Cond: (a = 1)                                    
+(2 rows)
+
+
+starting permutation: s1_begin s2_begin s1_lock_p s2_lock_p s1_truncate_p s2_truncate_p s1_insert_p s2_insert_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_lock_p: LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE
+step s2_lock_p: LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE
+step s1_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index f4c01006fc1..746a17f824c 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -96,3 +96,5 @@ test: plpgsql-toast
 test: truncate-conflict
 test: serializable-parallel
 test: serializable-parallel-2
+test: gtt-sequence
+test: gtt-table
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 88594a3cb5d..ec643aadb5f 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -80,9 +80,30 @@ disconnect_atexit(void)
 {
 	int			i;
 
-	for (i = 0; i < nconns; i++)
+	for (i = 1; i < nconns; i++)
 		if (conns[i].conn)
 			PQfinish(conns[i].conn);
+
+	if (parseresult.destroy)
+	{
+		PGresult   *res;
+
+		res = PQexec(conns[0].conn, parseresult.destroy);
+		if (PQresultStatus(res) == PGRES_TUPLES_OK)
+		{
+			printResultSet(res);
+		}
+		else if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		{
+			fprintf(stderr, "destroy failed: %s",
+					PQerrorMessage(conns[0].conn));
+			/* don't exit on teardown failure */
+		}
+		PQclear(res);
+	}
+
+	if (conns[0].conn)
+		PQfinish(conns[0].conn);
 }
 
 int
@@ -214,6 +235,24 @@ main(int argc, char **argv)
 	PQclear(res);
 	termPQExpBuffer(&wait_query);
 
+	if (parseresult.initialize)
+	{
+		PGresult   *res;
+
+		res = PQexec(conns[0].conn, parseresult.initialize);
+		if (PQresultStatus(res) == PGRES_TUPLES_OK)
+		{
+			printResultSet(res);
+		}
+		else if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		{
+			fprintf(stderr, "initialize failed: %s",
+					PQerrorMessage(conns[0].conn));
+			/* don't exit on teardown failure */
+		}
+		PQclear(res);
+	}
+
 	/*
 	 * Run the permutations specified in the spec, or all if none were
 	 * explicitly specified.
diff --git a/src/test/isolation/isolationtester.h b/src/test/isolation/isolationtester.h
index 5f300219c20..b5a29893da9 100644
--- a/src/test/isolation/isolationtester.h
+++ b/src/test/isolation/isolationtester.h
@@ -81,6 +81,8 @@ typedef struct
 	int			nsessions;
 	Permutation **permutations;
 	int			npermutations;
+	char	   *initialize;
+	char	   *destroy;
 } TestSpec;
 
 extern TestSpec parseresult;
diff --git a/src/test/isolation/specparse.y b/src/test/isolation/specparse.y
index c25aa1a73fa..2784f758ed9 100644
--- a/src/test/isolation/specparse.y
+++ b/src/test/isolation/specparse.y
@@ -39,7 +39,7 @@ TestSpec		parseresult;			/* result of parsing is left here */
 }
 
 %type <ptr_list> setup_list
-%type <str>  opt_setup opt_teardown
+%type <str>  opt_setup opt_teardown opt_initialize opt_destroy
 %type <str> setup
 %type <ptr_list> step_list session_list permutation_list opt_permutation_list
 %type <ptr_list> permutation_step_list blocker_list
@@ -51,23 +51,27 @@ TestSpec		parseresult;			/* result of parsing is left here */
 
 %token <str> sqlblock identifier
 %token <integer> INTEGER
-%token NOTICES PERMUTATION SESSION SETUP STEP TEARDOWN TEST
+%token NOTICES PERMUTATION SESSION SETUP STEP TEARDOWN TEST INITIALIZE DESTROY
 
 %%
 
 TestSpec:
+			opt_initialize
 			setup_list
 			opt_teardown
+			opt_destroy
 			session_list
 			opt_permutation_list
 			{
-				parseresult.setupsqls = (char **) $1.elements;
-				parseresult.nsetupsqls = $1.nelements;
-				parseresult.teardownsql = $2;
-				parseresult.sessions = (Session **) $3.elements;
-				parseresult.nsessions = $3.nelements;
-				parseresult.permutations = (Permutation **) $4.elements;
-				parseresult.npermutations = $4.nelements;
+				parseresult.setupsqls = (char **) $2.elements;
+				parseresult.nsetupsqls = $2.nelements;
+				parseresult.teardownsql = $3;
+				parseresult.sessions = (Session **) $5.elements;
+				parseresult.nsessions = $5.nelements;
+				parseresult.permutations = (Permutation **) $6.elements;
+				parseresult.npermutations = $6.nelements;
+				parseresult.initialize = $1;
+				parseresult.destroy = $4;
 			}
 		;
 
@@ -100,6 +104,16 @@ opt_teardown:
 			| TEARDOWN sqlblock	{ $$ = $2; }
 		;
 
+opt_initialize:
+			/* EMPTY */			{ $$ = NULL; }
+			| INITIALIZE sqlblock	{ $$ = $2; }
+		;
+
+opt_destroy:
+			/* EMPTY */			{ $$ = NULL; }
+			| DESTROY sqlblock	{ $$ = $2; }
+		;
+
 session_list:
 			session_list session
 			{
diff --git a/src/test/isolation/specs/gtt-sequence.spec b/src/test/isolation/specs/gtt-sequence.spec
new file mode 100644
index 00000000000..88eece45e29
--- /dev/null
+++ b/src/test/isolation/specs/gtt-sequence.spec
@@ -0,0 +1,39 @@
+# Tests for global temporary relations
+
+initialize
+{
+  CREATE GLOBAL TEMPORARY TABLE if not exists gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+}
+
+destroy
+{
+  /* wait other backend exit */
+  select pg_sleep(1);
+
+  DROP TABLE gtt_with_seq;
+}
+
+# Session 1
+session "s1"
+step "s1_seq_next" { select nextval('gtt_with_seq_c2_seq'); }
+step "s1_seq_restart" { alter sequence gtt_with_seq_c2_seq RESTART; }
+step "s1_insert" { insert into gtt_with_seq values(1); }
+step "s1_select" { select * from gtt_with_seq order by c1,c2; }
+teardown
+{
+  TRUNCATE gtt_with_seq;
+}
+
+# Session 2
+session "s2"
+step "s2_seq_next" { select nextval('gtt_with_seq_c2_seq'); }
+step "s2_insert" { insert into gtt_with_seq values(10); }
+step "s2_select" { select * from gtt_with_seq order by c1,c2; }
+teardown
+{
+  TRUNCATE gtt_with_seq RESTART IDENTITY;
+}
+
+permutation "s1_seq_next" "s2_seq_next" "s1_seq_next"
+permutation "s1_select" "s2_select" "s1_insert" "s2_insert" "s1_select" "s2_select"
+
diff --git a/src/test/isolation/specs/gtt-table.spec b/src/test/isolation/specs/gtt-table.spec
new file mode 100644
index 00000000000..e0396b21ef0
--- /dev/null
+++ b/src/test/isolation/specs/gtt-table.spec
@@ -0,0 +1,135 @@
+# Tests for global temporary relations
+
+initialize
+{
+  create global temp table gtt_on_commit_delete_row(a bigserial primary key, b text) on commit delete rows;
+  create global temp table gtt_on_commit_preserve_row(a bigserial primary key, b text) on commit preserve rows;
+  create global temp table gtt_test_createindex(a int, b char(1000)) on commit preserve rows;
+}
+
+destroy
+{
+  /* wait other backend exit */
+  select pg_sleep(1);
+
+  DROP TABLE gtt_on_commit_delete_row;
+  DROP TABLE gtt_on_commit_preserve_row;
+  DROP TABLE gtt_test_createindex;
+}
+
+# Session 1
+session "s1"
+step "s1_begin" {begin}
+step "s1_commit" {commit}
+step "s1_rollback" {rollback}
+step "s1_insert_d" {insert into gtt_on_commit_delete_row (b) values('test1')}
+step "s1_select_d" {select a,b from gtt_on_commit_delete_row order by a,b}
+step "s1_insert_p" {insert into gtt_on_commit_preserve_row (b) values('test20')}
+step "s1_select_p" {select a,b from gtt_on_commit_preserve_row order by a,b}
+step "s1_truncate_d" {truncate gtt_on_commit_delete_row}
+step "s1_truncate_p" {truncate gtt_on_commit_preserve_row}
+step "s1_lock_p" {LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE}
+step "s1_update_d" {update gtt_on_commit_delete_row set b = 'update'}
+step "s1_save_1" {SAVEPOINT save1}
+step "s1_save_2" {SAVEPOINT save2}
+step "s1_save_3" {SAVEPOINT save3}
+step "s1_rollback_to_save_2" {rollback to savepoint save2}
+step "s1_reindex_p" {reindex table gtt_on_commit_preserve_row}
+step "s1_reindex_i_p" {reindex index gtt_on_commit_preserve_row_pkey}
+step "s1_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s1_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s1_analyze_c" {analyze gtt_test_createindex}
+teardown
+{
+  TRUNCATE gtt_on_commit_delete_row RESTART IDENTITY;
+  TRUNCATE gtt_on_commit_preserve_row RESTART IDENTITY;
+}
+
+# Session 2
+session "s2"
+step "s2_begin" {begin}
+step "s2_commit" {commit}
+step "s2_rollback" {rollback}
+step "s2_insert_p" {insert into gtt_on_commit_preserve_row (b) values('test10')}
+step "s2_select_p" {select a,b from gtt_on_commit_preserve_row order by a,b}
+step "s2_insert_d" {insert into gtt_on_commit_delete_row (b) values('test1')}
+step "s2_truncate_p" {truncate gtt_on_commit_preserve_row}
+step "s2_update_p" {update gtt_on_commit_preserve_row set b = 'update'}
+step "s2_lock_p" {LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE}
+step "s2_save_1" {SAVEPOINT save1}
+step "s2_save_2" {SAVEPOINT save2}
+step "s2_save_3" {SAVEPOINT save3}
+step "s2_rollback_to_save_2" {rollback to savepoint save2}
+step "s2_reindex_p" {reindex table gtt_on_commit_preserve_row}
+step "s2_reindex_i_p" {reindex index gtt_on_commit_preserve_row_pkey}
+step "s2_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s2_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s2_analyze_c" {analyze gtt_test_createindex}
+teardown
+{
+  TRUNCATE gtt_on_commit_delete_row RESTART IDENTITY;
+  TRUNCATE gtt_on_commit_preserve_row RESTART IDENTITY;
+}
+
+session "s3"
+step "s3_create_c" {create unique index idx_temp_table_a on gtt_test_createindex(a)}
+step "s3_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s3_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s3_analyze_c" {analyze gtt_test_createindex}
+
+
+#
+# test on commit delete temp table
+#
+
+# test update empty temp table
+permutation "s1_update_d"
+# test insert into temp table
+permutation "s1_select_d" "s1_insert_d" "s1_select_d"
+# test temp table in transaction(commit)
+permutation "s1_select_d" "s1_begin"    "s1_insert_d" "s1_select_d"   "s1_commit"   "s1_select_d" 
+# test temp table in transaction(rollback)
+permutation "s1_select_d" "s1_begin"    "s1_insert_d" "s1_select_d"   "s1_rollback" "s1_select_d" 
+# test truncate
+permutation "s1_select_d" "s1_insert_d" "s1_select_d" "s1_truncate_d" "s1_select_d"
+# test truncate in transaction block
+permutation "s1_select_d" "s1_insert_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_truncate_d" "s1_select_d"   "s1_insert_d" "s1_select_d" "s1_commit"   "s1_select_d" 
+permutation "s1_select_d" "s1_insert_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_truncate_d" "s1_select_d"   "s1_insert_d" "s1_select_d" "s1_rollback" "s1_select_d"
+# test temp table with subtransaction or savepoint
+permutation "s1_insert_d" "s1_select_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_save_1"     "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_2"   "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_3" "s1_rollback_to_save_2" "s1_select_d" "s1_insert_d" "s1_select_d" "s1_commit" "s1_select_d"
+permutation "s1_insert_d" "s1_select_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_save_1"     "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_2"   "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_3" "s1_rollback_to_save_2" "s1_select_d" "s1_insert_d" "s1_select_d" "s1_rollback" "s1_select_d"
+
+#
+# test on commit preserve table
+#
+
+# same as test on commit delete temp table
+permutation "s2_update_p"
+permutation "s2_select_p" "s2_insert_p" "s2_select_p"
+permutation "s2_select_p" "s2_begin"    "s2_insert_p" "s2_select_p"   "s2_commit"   "s2_select_p"
+permutation "s2_select_p" "s2_begin"    "s2_insert_p" "s2_select_p"   "s2_rollback" "s2_select_p"
+permutation "s2_select_p" "s2_insert_p" "s2_select_p" "s2_truncate_p" "s2_select_p"
+permutation "s2_select_p" "s2_insert_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_truncate_p" "s2_select_p"   "s2_insert_p" "s2_select_p" "s2_commit"   "s2_select_p" 
+permutation "s2_select_p" "s2_insert_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_truncate_p" "s2_select_p"   "s2_insert_p" "s2_select_p" "s2_rollback" "s2_select_p" 
+permutation "s2_insert_p" "s2_select_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_save_1"     "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_2"   "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_3" "s2_rollback_to_save_2" "s2_select_p" "s2_insert_p" "s2_select_p" "s2_commit" "s2_select_p"
+permutation "s2_insert_p" "s2_select_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_save_1"     "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_2"   "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_3" "s2_rollback_to_save_2" "s2_select_p" "s2_insert_p" "s2_select_p" "s2_rollback" "s2_select_p"
+
+#
+# test concurrent operation on temp table
+#
+
+#  test concurrent read
+permutation "s1_insert_p" "s2_insert_p" "s1_select_p" "s2_select_p" 
+#  test concurrent truncate
+permutation "s1_begin" "s2_begin"    "s1_insert_p" "s2_insert_p"   "s1_truncate_p" "s2_truncate_p"  "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+permutation "s1_begin" "s1_insert_d" "s2_insert_d" "s1_truncate_d" "s2_insert_d"   "s1_commit" 
+#  test concurrent reindex table
+permutation "s1_begin" "s2_begin" "s1_insert_p" "s2_insert_p" "s1_reindex_p"   "s2_reindex_p"   "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+#  test concurrent reindex index
+permutation "s1_begin" "s2_begin" "s1_insert_p" "s2_insert_p" "s1_reindex_i_p" "s2_reindex_i_p" "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+
+# test create index
+permutation "s2_insert_c" "s3_create_c" "s3_insert_c" "s1_insert_c" "s1_analyze_c" "s2_analyze_c" "s3_analyze_c" "s1_select_c" "s2_select_c" "s3_select_c"
+
+# test lock gtt
+permutation "s1_begin" "s2_begin" "s1_lock_p" "s2_lock_p" "s1_truncate_p" "s2_truncate_p" "s1_insert_p" "s2_insert_p" "s1_commit" "s2_commit" "s1_select_p" "s2_select_p"
diff --git a/src/test/isolation/specscanner.l b/src/test/isolation/specscanner.l
index d9fa6a5b54a..697db975479 100644
--- a/src/test/isolation/specscanner.l
+++ b/src/test/isolation/specscanner.l
@@ -67,6 +67,8 @@ session			{ return SESSION; }
 setup			{ return SETUP; }
 step			{ return STEP; }
 teardown		{ return TEARDOWN; }
+initialize		 { return INITIALIZE; }
+destroy			 { return DESTROY; }
 
  /* Whitespace and comments */
 [\n]			{ yyline++; }
diff --git a/src/test/regress/expected/global_temporary_table.out b/src/test/regress/expected/global_temporary_table.out
new file mode 100644
index 00000000000..f85d826269c
--- /dev/null
+++ b/src/test/regress/expected/global_temporary_table.out
@@ -0,0 +1,549 @@
+--
+-- GLobal emparary table test case 
+--
+CREATE SCHEMA IF NOT EXISTS global_temporary_table;
+set search_path=global_temporary_table,sys;
+--
+--  test create global temp table basic syntax
+--
+create global temp table gtt_on_commit_default(a int primary key, b text);
+create global temp table gtt_on_commit_delete(a int primary key, b text) on commit delete rows;
+create global temp table gtt_on_commit_delete2(n int) with (on_commit_delete_rows='true');
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit PRESERVE rows;
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt_test_createindex(c0 tsvector,c1 varchar(100), c2 int);
+--
+-- test DML on global temp table
+--
+-- update empty temp table
+update gtt_on_commit_delete set b ='test';
+begin;
+insert into gtt_on_commit_delete values (1);
+update gtt_on_commit_delete set b ='test';
+-- should 1 row
+select * from gtt_on_commit_delete;
+ a |  b   
+---+------
+ 1 | test
+(1 row)
+
+commit;
+-- data delete after transaction commit
+-- should 0 row
+select * from gtt_on_commit_delete;
+ a | b 
+---+---
+(0 rows)
+
+-- update empty temp table
+update gtt_on_commit_preserve set b ='test';
+insert into gtt_on_commit_preserve values (2);
+begin;
+insert into gtt_on_commit_preserve values (3);
+update gtt_on_commit_preserve set b ='test';
+-- should 2 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 2 | test
+ 3 | test
+(2 rows)
+
+delete from gtt_on_commit_preserve where a=2;
+commit;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 3 | test
+(1 row)
+
+begin;
+insert into gtt_on_commit_preserve values (4);
+-- temp table support truncate;
+truncate gtt_on_commit_preserve;
+select * from gtt_on_commit_preserve order by a;
+ a | b 
+---+---
+(0 rows)
+
+rollback;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 3 | test
+(1 row)
+
+--
+-- test unsupported global temp partition table
+--
+-- should fail
+CREATE global temp TABLE global_temp_partition_01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+on commit delete rows;
+ERROR:  Only support global temporary regular table.
+CREATE TABLE regular_partition_01_2019 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+);
+CREATE TABLE regular_partition01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time);
+-- should fail
+CREATE global temp TABLE temp_partition01_2018
+PARTITION OF regular_partition01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+ERROR:  Not support global temporary partition table or inherit table.
+CREATE global temp TABLE global_temp_partition_01_2021 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+)on commit delete rows;
+-- should fail
+ALTER TABLE regular_partition01 ATTACH PARTITION global_temp_partition_01_2021 FOR VALUES FROM ('2021-01-01 00:00:00') TO ('2022-01-01 00:00:00');
+ERROR:  cannot attach a global temporary relation as partition of permanent relation "regular_partition01"
+--
+-- test unsupported inherit table
+--
+create table 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 inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent);
+ERROR:  Not support global temporary partition table or inherit table.
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent_global_temp) on commit delete rows;
+ERROR:  Not support global temporary partition table or inherit table.
+--
+-- test DDL on global temp table
+--
+create index gtt_idx_1 on gtt_test_createindex using gin (c0);
+create index gtt_idx_2 on gtt_test_createindex using gist (c0);
+create index gtt_idx_3 on gtt_test_createindex using hash (c2);
+alter table gtt_test_rename rename to gtt_test_new;
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+create index CONCURRENTLY idx_b on gtt_on_commit_default (b);
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+insert into gtt_on_commit_default values(1,'test');
+create global temp table gtt_test_alter1 (a int primary key,b text);
+alter table gtt_test_alter1 alter a type varchar;
+-- should fail
+alter table gtt_on_commit_default alter a type varchar;
+ERROR:  Only support alter global temporary table in an empty context.
+HINT:  Please create a new connection and execute ALTER TABLE on the new connection.
+-- should fail
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+ERROR:  not support cluster global temporary table yet
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- should fail
+alter table gtt_on_commit_default SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temporary table
+-- should fail
+alter table gtt_on_commit_default set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table
+-- should fail
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- should fail
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- should fail
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit drop;
+ERROR:  global temporary table not support on commit drop clause
+-- should fail
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  could not create global temporary table with on commit and with clause at same time
+-- should fail
+CREATE MATERIALIZED VIEW mv_gtt_on_commit_default as select * from gtt_on_commit_default;
+ERROR:  materialized views must not use global temporary tables or views
+--
+-- test foreign key dependencies for global temp table
+--
+CREATE global temp TABLE temp_products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+CREATE global temp TABLE temp_orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+)on commit delete rows;
+-- should fail
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+CREATE global temp TABLE temp_orders_2 (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products(product_no),
+    quantity integer
+);
+--should fail
+insert into temp_orders values(1,1,1);
+ERROR:  insert or update on table "temp_orders" violates foreign key constraint "temp_orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "temp_products".
+insert into temp_products values(1,'test',1.0);
+begin;
+insert into temp_orders values(1,1,1);
+commit;
+-- should 1 row
+select count(*) from temp_products;
+ count 
+-------
+     1
+(1 row)
+
+-- should 0 row
+select count(*) from temp_orders;
+ count 
+-------
+     0
+(1 row)
+
+--
+-- test sequence on global temp table
+--
+create global temp table global_temp_with_serial (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+-- support insert data to temp table in read only transaction
+insert into global_temp_with_serial (b) values(1);
+select * from global_temp_with_serial;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_delete(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_preserve(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table temp_table_with_sequence_oncommit_delete add c2 int default nextval('seq_1');
+alter table temp_table_with_sequence_oncommit_preserve add c2 int default nextval('seq_1');
+begin;
+insert into temp_table_with_sequence_oncommit_delete (c1)values(1);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(2);
+insert into temp_table_with_sequence_oncommit_delete (c1)values(3);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(4);
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  3 |  3
+(2 rows)
+
+commit;
+-- should 0 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_preserve order by c1;
+ c1 | c2 
+----+----
+  2 |  2
+  4 |  4
+(2 rows)
+
+--
+-- test statistics on temp table
+--
+create global temp table temp_table_test_statistics(a int);
+insert into temp_table_test_statistics values(generate_series(1,100000));
+create index idx_test_1 on temp_table_test_statistics (a);
+create index idx_test_2 on temp_table_test_statistics((a*10));
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+                   QUERY PLAN                   
+------------------------------------------------
+ Bitmap Heap Scan on temp_table_test_statistics
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_test_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+                   QUERY PLAN                   
+------------------------------------------------
+ Bitmap Heap Scan on temp_table_test_statistics
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_test_2
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+-- test statistic for whole row
+explain (costs off) select count(*) from temp_table_test_statistics group by temp_table_test_statistics;
+                  QUERY PLAN                  
+----------------------------------------------
+ HashAggregate
+   Group Key: temp_table_test_statistics.*
+   ->  Seq Scan on temp_table_test_statistics
+(3 rows)
+
+-- test statistic for system column
+explain (costs off) select count(*) from temp_table_test_statistics group by tableoid;
+                  QUERY PLAN                  
+----------------------------------------------
+ HashAggregate
+   Group Key: tableoid
+   ->  Seq Scan on temp_table_test_statistics
+(3 rows)
+
+analyze temp_table_test_statistics;
+-- indexscan by idx_test_1
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Index Only Scan using idx_test_1 on temp_table_test_statistics
+   Index Cond: (a = 200000)
+(2 rows)
+
+-- indexscan by idx_test_2
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+                        QUERY PLAN                         
+-----------------------------------------------------------
+ Index Scan using idx_test_2 on temp_table_test_statistics
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+explain (costs off) select count(*) from temp_table_test_statistics group by temp_table_test_statistics;
+                  QUERY PLAN                  
+----------------------------------------------
+ HashAggregate
+   Group Key: temp_table_test_statistics.*
+   ->  Seq Scan on temp_table_test_statistics
+(3 rows)
+
+explain (costs off) select count(*) from temp_table_test_statistics group by tableoid;
+                  QUERY PLAN                  
+----------------------------------------------
+ HashAggregate
+   Group Key: tableoid
+   ->  Seq Scan on temp_table_test_statistics
+(3 rows)
+
+--
+-- test temp table with toast table
+--
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |                0 |        483328 |           98304 |                 581632
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            32768 |                  |         32768 |               0 |                  32768
+(3 rows)
+
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |             8192 |        499712 |          106496 |                 606208
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            40960 |                  |         40960 |               0 |                  40960
+(3 rows)
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,10),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',10);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |             8192 |                0 |         16384 |           32768 |                  49152
+ idx_gtt_t_kenyon_1 |            16384 |                  |         16384 |               0 |                  16384
+ idx_gtt_t_kenyon_2 |            16384 |                  |         16384 |               0 |                  16384
+(3 rows)
+
+rollback;
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |             8192 |        499712 |          106496 |                 606208
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            40960 |                  |         40960 |               0 |                  40960
+(3 rows)
+
+-- test analyze/vacuum on global temp table
+ANALYZE gtt_t_kenyon;
+VACUUM gtt_t_kenyon;
+--
+-- test global temp table system view
+--
+create global temp table temp_table_test_systemview(a int primary key, b text) on commit PRESERVE rows;
+-- should empty, storage not initialized
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+ tablename 
+-----------
+(0 rows)
+
+-- should empty, storage not initialized
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into temp_table_test_systemview values(generate_series(1,10000),'test');
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+         tablename          
+----------------------------
+ temp_table_test_systemview
+(1 row)
+
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+       schemaname       |            tablename            | relpages | reltuples | relallvisible 
+------------------------+---------------------------------+----------+-----------+---------------
+ global_temporary_table | temp_table_test_systemview      |        0 |         0 |             0
+ global_temporary_table | temp_table_test_systemview_pkey |        1 |         0 |             0
+(2 rows)
+
+-- should empty
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+analyze temp_table_test_systemview;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+       schemaname       |            tablename            | relpages | reltuples | relallvisible 
+------------------------+---------------------------------+----------+-----------+---------------
+ global_temporary_table | temp_table_test_systemview      |       55 |     10000 |             0
+ global_temporary_table | temp_table_test_systemview_pkey |       30 |     10000 |             0
+(2 rows)
+
+-- get data after analyze;
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+       schemaname       |         tablename          | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------------------+----------------------------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ global_temporary_table | temp_table_test_systemview | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ global_temporary_table | temp_table_test_systemview | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+-- get all object info in current schema
+select relname ,relkind, relpersistence, reloptions from pg_class c, pg_namespace n where c.relnamespace = n.oid and n.nspname = 'global_temporary_table' order by relkind,relpersistence,relname;
+                     relname                     | relkind | relpersistence |          reloptions           
+-------------------------------------------------+---------+----------------+-------------------------------
+ global_temp_partition_01_2021_id_seq            | S       | g              | {on_commit_delete_rows=false}
+ global_temp_with_serial_a_seq                   | S       | g              | {on_commit_delete_rows=false}
+ regular_partition01_id_seq                      | S       | p              | 
+ regular_partition_01_2019_id_seq                | S       | p              | 
+ seq_1                                           | S       | p              | 
+ gtt_idx_1                                       | i       | g              | 
+ gtt_idx_2                                       | i       | g              | 
+ gtt_idx_3                                       | i       | g              | 
+ gtt_on_commit_default_pkey                      | i       | g              | 
+ gtt_on_commit_delete_pkey                       | i       | g              | 
+ gtt_on_commit_preserve_pkey                     | i       | g              | 
+ gtt_test_alter1_pkey                            | i       | g              | 
+ gtt_test_rename_pkey                            | i       | g              | 
+ idx_b                                           | i       | g              | 
+ idx_gtt_t_kenyon_1                              | i       | g              | 
+ idx_gtt_t_kenyon_2                              | i       | g              | 
+ idx_test_1                                      | i       | g              | 
+ idx_test_2                                      | i       | g              | 
+ products_pkey                                   | i       | g              | 
+ temp_orders_2_pkey                              | i       | g              | 
+ temp_orders_pkey                                | i       | g              | 
+ temp_products_pkey                              | i       | g              | 
+ temp_table_test_systemview_pkey                 | i       | g              | 
+ temp_table_with_sequence_oncommit_delete_pkey   | i       | g              | 
+ temp_table_with_sequence_oncommit_preserve_pkey | i       | g              | 
+ regular_partition01                             | p       | p              | 
+ global_temp_partition_01_2021                   | r       | g              | {on_commit_delete_rows=true}
+ global_temp_with_serial                         | r       | g              | {on_commit_delete_rows=false}
+ gtt_on_commit_default                           | r       | g              | {on_commit_delete_rows=false}
+ gtt_on_commit_delete                            | r       | g              | {on_commit_delete_rows=true}
+ gtt_on_commit_delete2                           | r       | g              | {on_commit_delete_rows=true}
+ gtt_on_commit_preserve                          | r       | g              | {on_commit_delete_rows=false}
+ gtt_t_kenyon                                    | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_alter1                                 | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_createindex                            | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_new                                    | r       | g              | {on_commit_delete_rows=false}
+ inherits_parent_global_temp                     | r       | g              | {on_commit_delete_rows=true}
+ products                                        | r       | g              | {on_commit_delete_rows=false}
+ temp_orders                                     | r       | g              | {on_commit_delete_rows=true}
+ temp_orders_2                                   | r       | g              | {on_commit_delete_rows=false}
+ temp_products                                   | r       | g              | {on_commit_delete_rows=false}
+ temp_table_test_statistics                      | r       | g              | {on_commit_delete_rows=false}
+ temp_table_test_systemview                      | r       | g              | {on_commit_delete_rows=false}
+ temp_table_with_sequence_oncommit_delete        | r       | g              | {on_commit_delete_rows=true}
+ temp_table_with_sequence_oncommit_preserve      | r       | g              | {on_commit_delete_rows=false}
+ foo                                             | r       | p              | 
+ inherits_parent                                 | r       | p              | 
+ regular_partition_01_2019                       | r       | p              | 
+(48 rows)
+
+reset search_path;
+drop schema global_temporary_table cascade;
+NOTICE:  drop cascades to 24 other objects
+DETAIL:  drop cascades to table global_temporary_table.gtt_on_commit_default
+drop cascades to table global_temporary_table.gtt_on_commit_delete
+drop cascades to table global_temporary_table.gtt_on_commit_delete2
+drop cascades to table global_temporary_table.gtt_on_commit_preserve
+drop cascades to table global_temporary_table.gtt_test_new
+drop cascades to table global_temporary_table.gtt_test_createindex
+drop cascades to table global_temporary_table.regular_partition_01_2019
+drop cascades to table global_temporary_table.regular_partition01
+drop cascades to table global_temporary_table.global_temp_partition_01_2021
+drop cascades to table global_temporary_table.inherits_parent
+drop cascades to table global_temporary_table.inherits_parent_global_temp
+drop cascades to table global_temporary_table.gtt_test_alter1
+drop cascades to table global_temporary_table.foo
+drop cascades to table global_temporary_table.temp_products
+drop cascades to table global_temporary_table.products
+drop cascades to table global_temporary_table.temp_orders
+drop cascades to table global_temporary_table.temp_orders_2
+drop cascades to table global_temporary_table.global_temp_with_serial
+drop cascades to sequence global_temporary_table.seq_1
+drop cascades to table global_temporary_table.temp_table_with_sequence_oncommit_delete
+drop cascades to table global_temporary_table.temp_table_with_sequence_oncommit_preserve
+drop cascades to table global_temporary_table.temp_table_test_statistics
+drop cascades to table global_temporary_table.gtt_t_kenyon
+drop cascades to table global_temporary_table.temp_table_test_systemview
+-- should empty
+select * from pg_list_gtt_relfrozenxids();
+ pid | relfrozenxid 
+-----+--------------
+(0 rows)
+
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2fa00a3c29a..e0001bc3448 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1359,6 +1359,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 7be89178f0f..db8095d30bf 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -130,3 +130,6 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: global_temporary_table
diff --git a/src/test/regress/sql/global_temporary_table.sql b/src/test/regress/sql/global_temporary_table.sql
new file mode 100644
index 00000000000..53dca816cf3
--- /dev/null
+++ b/src/test/regress/sql/global_temporary_table.sql
@@ -0,0 +1,296 @@
+--
+-- GLobal emparary table test case 
+--
+
+CREATE SCHEMA IF NOT EXISTS global_temporary_table;
+set search_path=global_temporary_table,sys;
+
+--
+--  test create global temp table basic syntax
+--
+create global temp table gtt_on_commit_default(a int primary key, b text);
+create global temp table gtt_on_commit_delete(a int primary key, b text) on commit delete rows;
+create global temp table gtt_on_commit_delete2(n int) with (on_commit_delete_rows='true');
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit PRESERVE rows;
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt_test_createindex(c0 tsvector,c1 varchar(100), c2 int);
+
+--
+-- test DML on global temp table
+--
+
+-- update empty temp table
+update gtt_on_commit_delete set b ='test';
+begin;
+insert into gtt_on_commit_delete values (1);
+update gtt_on_commit_delete set b ='test';
+-- should 1 row
+select * from gtt_on_commit_delete;
+commit;
+-- data delete after transaction commit
+-- should 0 row
+select * from gtt_on_commit_delete;
+
+-- update empty temp table
+update gtt_on_commit_preserve set b ='test';
+insert into gtt_on_commit_preserve values (2);
+begin;
+insert into gtt_on_commit_preserve values (3);
+update gtt_on_commit_preserve set b ='test';
+-- should 2 row
+select * from gtt_on_commit_preserve order by a;
+delete from gtt_on_commit_preserve where a=2;
+commit;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+
+begin;
+insert into gtt_on_commit_preserve values (4);
+-- temp table support truncate;
+truncate gtt_on_commit_preserve;
+select * from gtt_on_commit_preserve order by a;
+rollback;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+
+--
+-- test unsupported global temp partition table
+--
+
+-- should fail
+CREATE global temp TABLE global_temp_partition_01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+on commit delete rows;
+
+CREATE TABLE regular_partition_01_2019 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+);
+
+CREATE TABLE regular_partition01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time);
+
+-- should fail
+CREATE global temp TABLE temp_partition01_2018
+PARTITION OF regular_partition01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE global_temp_partition_01_2021 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+)on commit delete rows;
+
+-- should fail
+ALTER TABLE regular_partition01 ATTACH PARTITION global_temp_partition_01_2021 FOR VALUES FROM ('2021-01-01 00:00:00') TO ('2022-01-01 00:00:00');
+
+--
+-- test unsupported inherit table
+--
+create table 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 inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent);
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent_global_temp) on commit delete rows;
+
+--
+-- test DDL on global temp table
+--
+create index gtt_idx_1 on gtt_test_createindex using gin (c0);
+create index gtt_idx_2 on gtt_test_createindex using gist (c0);
+create index gtt_idx_3 on gtt_test_createindex using hash (c2);
+alter table gtt_test_rename rename to gtt_test_new;
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+create index CONCURRENTLY idx_b on gtt_on_commit_default (b);
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+insert into gtt_on_commit_default values(1,'test');
+create global temp table gtt_test_alter1 (a int primary key,b text);
+alter table gtt_test_alter1 alter a type varchar;
+-- should fail
+alter table gtt_on_commit_default alter a type varchar;
+-- should fail
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) on commit delete rows;
+-- should fail
+alter table gtt_on_commit_default SET TABLESPACE pg_default;
+-- should fail
+alter table gtt_on_commit_default set ( on_commit_delete_rows='true');
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) with(on_commit_delete_rows=true);
+-- should fail
+create or replace global temp view gtt_v as select 5;
+create table foo();
+-- should fail
+alter table foo set (on_commit_delete_rows='true');
+-- should fail
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit drop;
+-- should fail
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+-- should fail
+CREATE MATERIALIZED VIEW mv_gtt_on_commit_default as select * from gtt_on_commit_default;
+
+--
+-- test foreign key dependencies for global temp table
+--
+CREATE global temp TABLE temp_products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+CREATE global temp TABLE temp_orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+)on commit delete rows;
+
+-- should fail
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+);
+
+CREATE global temp TABLE temp_orders_2 (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products(product_no),
+    quantity integer
+);
+
+--should fail
+insert into temp_orders values(1,1,1);
+
+insert into temp_products values(1,'test',1.0);
+begin;
+insert into temp_orders values(1,1,1);
+commit;
+-- should 1 row
+select count(*) from temp_products;
+-- should 0 row
+select count(*) from temp_orders;
+
+--
+-- test sequence on global temp table
+--
+create global temp table global_temp_with_serial (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+-- support insert data to temp table in read only transaction
+insert into global_temp_with_serial (b) values(1);
+select * from global_temp_with_serial;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_delete(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_preserve(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table temp_table_with_sequence_oncommit_delete add c2 int default nextval('seq_1');
+alter table temp_table_with_sequence_oncommit_preserve add c2 int default nextval('seq_1');
+begin;
+insert into temp_table_with_sequence_oncommit_delete (c1)values(1);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(2);
+insert into temp_table_with_sequence_oncommit_delete (c1)values(3);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(4);
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+commit;
+-- should 0 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_preserve order by c1;
+
+--
+-- test statistics on temp table
+--
+create global temp table temp_table_test_statistics(a int);
+insert into temp_table_test_statistics values(generate_series(1,100000));
+create index idx_test_1 on temp_table_test_statistics (a);
+create index idx_test_2 on temp_table_test_statistics((a*10));
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+-- test statistic for whole row
+explain (costs off) select count(*) from temp_table_test_statistics group by temp_table_test_statistics;
+-- test statistic for system column
+explain (costs off) select count(*) from temp_table_test_statistics group by tableoid;
+analyze temp_table_test_statistics;
+-- indexscan by idx_test_1
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+-- indexscan by idx_test_2
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+explain (costs off) select count(*) from temp_table_test_statistics group by temp_table_test_statistics;
+explain (costs off) select count(*) from temp_table_test_statistics group by tableoid;
+
+--
+-- test temp table with toast table
+--
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,10),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',10);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+rollback;
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+
+-- test analyze/vacuum on global temp table
+ANALYZE gtt_t_kenyon;
+VACUUM gtt_t_kenyon;
+
+--
+-- test global temp table system view
+--
+create global temp table temp_table_test_systemview(a int primary key, b text) on commit PRESERVE rows;
+-- should empty, storage not initialized
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+-- should empty, storage not initialized
+select count(*) from pg_list_gtt_relfrozenxids();
+insert into temp_table_test_systemview values(generate_series(1,10000),'test');
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+select count(*) from pg_list_gtt_relfrozenxids();
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+-- should empty
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+analyze temp_table_test_systemview;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+-- get data after analyze;
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+
+-- get all object info in current schema
+select relname ,relkind, relpersistence, reloptions from pg_class c, pg_namespace n where c.relnamespace = n.oid and n.nspname = 'global_temporary_table' order by relkind,relpersistence,relname;
+
+reset search_path;
+drop schema global_temporary_table cascade;
+-- should empty
+select * from pg_list_gtt_relfrozenxids();
+
-- 
2.30.1 (Apple Git-130)

#338Andrew Bille
andrewbille@gmail.com
In reply to: wenjing (#337)
Re: [Proposal] Global temporary tables

Thanks, the "group by" is fixed

Yet another crash (on v58 patches), reproduced with:

psql -t -c "create global temp table t(b text)
with(on_commit_delete_rows=true); create index idx_b on t (b); insert into
t values('test'); alter table t alter b type varchar;"
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
connection to server was lost

with trace:

[New LWP 569199]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Core was generated by `postgres: andrew postgres [local] ALTER TABLE
'.
Program terminated with signal SIGABRT, Aborted.
#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
50 ../sysdeps/unix/sysv/linux/raise.c: No such file or directory.
(gdb) bt
#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
#1 0x00007f197493f859 in __GI_abort () at abort.c:79
#2 0x00005562b3306fb9 in ExceptionalCondition
(conditionName=0x5562b34dd740 "reln->md_num_open_segs[forkNum] == 0",
errorType=0x5562b34dd72c "FailedAssertion", fileName=0x5562b34dd727 "md.c",
lineNumber=187) at assert.c:69
#3 0x00005562b3148f15 in mdcreate (reln=0x5562b41abdc0,
forkNum=MAIN_FORKNUM, isRedo=false) at md.c:187
#4 0x00005562b314b73f in smgrcreate (reln=0x5562b41abdc0,
forknum=MAIN_FORKNUM, isRedo=false) at smgr.c:335
#5 0x00005562b2d88b23 in RelationCreateStorage (rnode=...,
relpersistence=103 'g', rel=0x7f196b597270) at storage.c:154
#6 0x00005562b2d5a408 in index_build (heapRelation=0x7f196b58dc40,
indexRelation=0x7f196b597270, indexInfo=0x5562b4167d60, isreindex=true,
parallel=false) at index.c:3038
#7 0x00005562b2d533c1 in RelationTruncateIndexes
(heapRelation=0x7f196b58dc40, lockmode=1) at heap.c:3354
#8 0x00005562b2d5360b in heap_truncate_one_rel (rel=0x7f196b58dc40) at
heap.c:3452
#9 0x00005562b2d53544 in heap_truncate (relids=0x5562b4167c58,
is_global_temp=true) at heap.c:3410
#10 0x00005562b2ea09fc in PreCommit_on_commit_actions () at
tablecmds.c:16495
#11 0x00005562b2d0d4ee in CommitTransaction () at xact.c:2140
#12 0x00005562b2d0e320 in CommitTransactionCommand () at xact.c:2979
#13 0x00005562b3151b7e in finish_xact_command () at postgres.c:2721
#14 0x00005562b314f340 in exec_simple_query (query_string=0x5562b40c2170
"create global temp table t(b text) with(on_commit_delete_rows=true);
create index idx_b on t (b); insert into t values('test'); alter table t
alter b type varchar;") at postgres.c:1239
#15 0x00005562b3153f0a in PostgresMain (dbname=0x5562b40ed6e8 "postgres",
username=0x5562b40ed6c8 "andrew") at postgres.c:4497
#16 0x00005562b307df6e in BackendRun (port=0x5562b40e4500) at
postmaster.c:4560
#17 0x00005562b307d853 in BackendStartup (port=0x5562b40e4500) at
postmaster.c:4288
#18 0x00005562b3079a1d in ServerLoop () at postmaster.c:1801
#19 0x00005562b30791b6 in PostmasterMain (argc=3, argv=0x5562b40bc5b0) at
postmaster.c:1473
#20 0x00005562b2f6d98e in main (argc=3, argv=0x5562b40bc5b0) at main.c:198

On Mon, Oct 25, 2021 at 7:13 PM wenjing <wjzeng2012@gmail.com> wrote:

Show quoted text

I missed whole row and system column. It has been fixed in v58.
Please review the new code(v58) again

#339wenjing
wjzeng2012@gmail.com
In reply to: Andrew Bille (#338)
4 attachment(s)
Re: [Proposal] Global temporary tables

Andrew Bille <andrewbille@gmail.com> 于2021年10月28日周四 下午6:30写道:

Thanks, the "group by" is fixed

Yet another crash (on v58 patches), reproduced with:

psql -t -c "create global temp table t(b text)
with(on_commit_delete_rows=true); create index idx_b on t (b); insert into
t values('test'); alter table t alter b type varchar;"
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
connection to server was lost

Thank you for pointing that out.
This is due to an optimization point: ALTER Table reuses the relfilenode of
the old index.
I have banned this optimization point for GTT, I am not entirely sure it is
appropriate, maybe you can give some suggestions.
Please review the new code(v59).

Wenjing

Show quoted text

with trace:

[New LWP 569199]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Core was generated by `postgres: andrew postgres [local] ALTER TABLE
'.
Program terminated with signal SIGABRT, Aborted.
#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
50 ../sysdeps/unix/sysv/linux/raise.c: No such file or directory.
(gdb) bt
#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
#1 0x00007f197493f859 in __GI_abort () at abort.c:79
#2 0x00005562b3306fb9 in ExceptionalCondition
(conditionName=0x5562b34dd740 "reln->md_num_open_segs[forkNum] == 0",
errorType=0x5562b34dd72c "FailedAssertion", fileName=0x5562b34dd727 "md.c",
lineNumber=187) at assert.c:69
#3 0x00005562b3148f15 in mdcreate (reln=0x5562b41abdc0,
forkNum=MAIN_FORKNUM, isRedo=false) at md.c:187
#4 0x00005562b314b73f in smgrcreate (reln=0x5562b41abdc0,
forknum=MAIN_FORKNUM, isRedo=false) at smgr.c:335
#5 0x00005562b2d88b23 in RelationCreateStorage (rnode=...,
relpersistence=103 'g', rel=0x7f196b597270) at storage.c:154
#6 0x00005562b2d5a408 in index_build (heapRelation=0x7f196b58dc40,
indexRelation=0x7f196b597270, indexInfo=0x5562b4167d60, isreindex=true,
parallel=false) at index.c:3038
#7 0x00005562b2d533c1 in RelationTruncateIndexes
(heapRelation=0x7f196b58dc40, lockmode=1) at heap.c:3354
#8 0x00005562b2d5360b in heap_truncate_one_rel (rel=0x7f196b58dc40) at
heap.c:3452
#9 0x00005562b2d53544 in heap_truncate (relids=0x5562b4167c58,
is_global_temp=true) at heap.c:3410
#10 0x00005562b2ea09fc in PreCommit_on_commit_actions () at
tablecmds.c:16495
#11 0x00005562b2d0d4ee in CommitTransaction () at xact.c:2140
#12 0x00005562b2d0e320 in CommitTransactionCommand () at xact.c:2979
#13 0x00005562b3151b7e in finish_xact_command () at postgres.c:2721
#14 0x00005562b314f340 in exec_simple_query (query_string=0x5562b40c2170
"create global temp table t(b text) with(on_commit_delete_rows=true);
create index idx_b on t (b); insert into t values('test'); alter table t
alter b type varchar;") at postgres.c:1239
#15 0x00005562b3153f0a in PostgresMain (dbname=0x5562b40ed6e8 "postgres",
username=0x5562b40ed6c8 "andrew") at postgres.c:4497
#16 0x00005562b307df6e in BackendRun (port=0x5562b40e4500) at
postmaster.c:4560
#17 0x00005562b307d853 in BackendStartup (port=0x5562b40e4500) at
postmaster.c:4288
#18 0x00005562b3079a1d in ServerLoop () at postmaster.c:1801
#19 0x00005562b30791b6 in PostmasterMain (argc=3, argv=0x5562b40bc5b0) at
postmaster.c:1473
#20 0x00005562b2f6d98e in main (argc=3, argv=0x5562b40bc5b0) at main.c:198

On Mon, Oct 25, 2021 at 7:13 PM wenjing <wjzeng2012@gmail.com> wrote:

I missed whole row and system column. It has been fixed in v58.
Please review the new code(v58) again

Attachments:

0002-gtt-v59-doc.patchapplication/octet-stream; name=0002-gtt-v59-doc.patchDownload
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 473a0a4aeb..e510bde8ac 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -169,32 +169,67 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     <listitem>
      <para>
       If specified, the table is created as a temporary table.
-      Temporary tables are automatically dropped at the end of a
-      session, or optionally at the end of the current transaction
-      (see <literal>ON COMMIT</literal> below).  The default
-      search_path includes the temporary schema first and so identically
-      named existing permanent tables are not chosen for new plans
+      Optionally, <literal>GLOBAL</literal> or <literal>LOCAL</literal>
+      can be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
+      They represent two types of temporary tables supported by <productname>PostgreSQL</productname>:
+      global temporary table and local temporary table. Without specified
+      GLOBAL or LOCAL, a local temporary table is created by default.
+     </para>
+
+    <para>
+     Both types of temporary tables’ data are truncated at the
+     end of a session or optionally at the end of the current transaction.
+     (see <literal>ON COMMIT</literal> below). For global temporary table,
+     its schema is reserved and reused by future sessions or transactions.
+     For local temporary table, both its data and its schema are dropped.
+    </para>
+
+    <variablelist>
+     <varlistentry>
+      <term><literal>Global Temporary Table</literal></term>
+      <listitem>
+       <para>
+        Global temporary table are defined just once and automatically exist
+        (starting with empty contents) in every session that needs them.
+        The schema definition of temporary tables is persistent and shared among sessions.
+        However, the data in temporary tables are kept private to sessions themselves,
+        even though they use same name and same schema.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>Local Temporary Table</literal></term>
+     <listitem>
+     <para>
+      Local temporary table are automatically dropped at the end of a
+      session (include schema and data). Future sessions need to create
+      their own temporary tables when they are used.
+     </para>
+     <para>
+      The default search_path includes the temporary schema first and so
+      identically named existing permanent tables are not chosen for new plans
       while the temporary table exists, unless they are referenced
       with schema-qualified names. Any indexes created on a temporary
       table are automatically temporary as well.
      </para>
+     </listitem>
+     </varlistentry>
+    </variablelist>
 
-     <para>
-      The <link linkend="autovacuum">autovacuum daemon</link> cannot
-      access and therefore cannot vacuum or analyze temporary tables.
-      For this reason, appropriate vacuum and analyze operations should be
-      performed via session SQL commands.  For example, if a temporary
-      table is going to be used in complex queries, it is wise to run
-      <command>ANALYZE</command> on the temporary table after it is populated.
-     </para>
+    <para>
+     The <link linkend="autovacuum">autovacuum daemon</link> cannot
+     access and therefore cannot vacuum or analyze temporary tables.
+     For this reason, appropriate vacuum and analyze operations should be
+     performed via session SQL commands.  For example, if a temporary
+     table is going to be used in complex queries, it is wise to run
+     <command>ANALYZE</command> on the temporary table after it is populated.
+    </para>
+    <para>
+     The Temporary table resembles the SQL standard, but has some differences.
+     see <xref linkend="sql-createtable-compatibility"/> below.
+    </para>
 
-     <para>
-      Optionally, <literal>GLOBAL</literal> or <literal>LOCAL</literal>
-      can be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
-      This presently makes no difference in <productname>PostgreSQL</productname>
-      and is deprecated; see
-      <xref linkend="sql-createtable-compatibility"/> below.
-     </para>
     </listitem>
    </varlistentry>
 
@@ -2133,13 +2168,17 @@ CREATE TABLE cities_partdef
    <title>Temporary Tables</title>
 
    <para>
-    Although the syntax of <literal>CREATE TEMPORARY TABLE</literal>
-    resembles that of the SQL standard, the effect is not the same.  In the
-    standard,
-    temporary tables are defined just once and automatically exist (starting
-    with empty contents) in every session that needs them.
-    <productname>PostgreSQL</productname> instead
-    requires each session to issue its own <literal>CREATE TEMPORARY
+    Although the syntax of <literal>CREATE GLOBAL/LOCAL TEMPORARY TABLE</literal>
+    resembles that of the SQL standard, the effect is not the same.
+    The global temporary table follows the SQL standards while local temporary
+    table does not.
+   </para>
+
+   <para>
+    First, in the standard, both global and local temporary tables are defined just
+    once and automatically exist (starting with empty contents) in every session
+    that needs them. For local temporary tables, <productname>PostgreSQL</productname>
+    instead requires each session to issue its own <literal>CREATE LOCAL TEMPORARY
     TABLE</literal> command for each temporary table to be used.  This allows
     different sessions to use the same temporary table name for different
     purposes, whereas the standard's approach constrains all instances of a
@@ -2147,29 +2186,14 @@ CREATE TABLE cities_partdef
    </para>
 
    <para>
-    The standard's definition of the behavior of temporary tables is
-    widely ignored.  <productname>PostgreSQL</productname>'s behavior
-    on this point is similar to that of several other SQL databases.
-   </para>
-
-   <para>
-    The SQL standard also distinguishes between global and local temporary
+    Second, the SQL standard distinguishes between global and local temporary
     tables, where a local temporary table has a separate set of contents for
     each SQL module within each session, though its definition is still shared
-    across sessions.  Since <productname>PostgreSQL</productname> does not
+    across sessions. Since <productname>PostgreSQL</productname> does not
     support SQL modules, this distinction is not relevant in
     <productname>PostgreSQL</productname>.
    </para>
 
-   <para>
-    For compatibility's sake, <productname>PostgreSQL</productname> will
-    accept the <literal>GLOBAL</literal> and <literal>LOCAL</literal> keywords
-    in a temporary table declaration, but they currently have no effect.
-    Use of these keywords is discouraged, since future versions of
-    <productname>PostgreSQL</productname> might adopt a more
-    standard-compliant interpretation of their meaning.
-   </para>
-
    <para>
     The <literal>ON COMMIT</literal> clause for temporary tables
     also resembles the SQL standard, but has some differences.
@@ -2177,7 +2201,8 @@ CREATE TABLE cities_partdef
     default behavior is <literal>ON COMMIT DELETE ROWS</literal>.  However, the
     default behavior in <productname>PostgreSQL</productname> is
     <literal>ON COMMIT PRESERVE ROWS</literal>.  The <literal>ON COMMIT
-    DROP</literal> option does not exist in SQL.
+    DROP</literal> option does not exist in SQL and is not supported by
+    global temporary table.
    </para>
   </refsect2>
 
-- 
2.30.1 (Apple Git-130)

0001-gtt-v59-reademe.patchapplication/octet-stream; name=0001-gtt-v59-reademe.patchDownload
diff --git a/README.gtt.txt b/README.gtt.txt
new file mode 100644
index 00000000000..d181df9acd7
--- /dev/null
+++ b/README.gtt.txt
@@ -0,0 +1,172 @@
+Global Temporary Table(GTT)
+=========================================
+
+Feature description
+-----------------------------------------
+
+Previously, temporary tables are defined once and automatically
+exist (starting with empty contents) in every session before using them.
+
+The temporary table implementation in PostgreSQL, known as Local temp tables(LTT),
+did not fully comply with the SQL standard. This version added the support of
+Global Temporary Table .
+
+The metadata of Global Temporary Table is persistent and shared among sessions.
+The data stored in the Global temporary table is independent of sessions. This
+means, when a session creates a Global Temporary Table and writes some data.
+Other sessions cannot see those data, but they have an empty Global Temporary
+Table with same schema.
+
+Like local temporary table, Global Temporary Table supports ON COMMIT PRESERVE ROWS
+or ON COMMIT DELETE ROWS clause, so that data in the temporary table can be
+cleaned up or preserved automatically when a session exits or a transaction COMMITs.
+
+Unlike Local Temporary Table, Global Temporary Table does not support
+ON COMMIT DROP clauses.
+
+In following paragraphs, we use GTT for Global Temporary Table and LTT for
+local temporary table.
+
+Main design ideas
+-----------------------------------------
+In general, GTT and LTT use the same storage and buffer design and
+implementation. The storage files for both types of temporary tables are named
+as t_backendid_relfilenode, and the local buffer is used to cache the data.
+
+The schema of GTTs is shared among sessions while their data are not. We build
+a new mechanisms to manage those non-shared data and their statistics.
+Here is the summary of changes:
+
+1) CATALOG
+GTTs store session-specific data. The storage information of GTTs'data, their
+transaction information, and their statistics are not stored in the catalog.
+
+2) STORAGE INFO & STATISTICS INFO & TRANSACTION INFO
+In order to maintain durability and availability of GTTs'session-specific data,
+their storage information, statistics, and transaction information is managed
+in a local hash table tt_storage_local_hash.
+
+3) DDL
+Currently, GTT supports almost all table'DDL except CLUSTER/VACUUM FULL.
+Part of the DDL behavior is limited by shared definitions and multiple copies of
+local data, and we added some structures to handle this.
+
+A shared hash table active_gtt_shared_hash is added to track the state of the
+GTT in a different session. This information is recorded in the hash table
+during the DDL execution of the GTT.
+
+The data stored in a GTT can only be modified or accessed by owning session.
+The statements that only modify data in a GTT do not need a high level of
+table locking. The operations making those changes include truncate GTT,
+reindex GTT, and lock GTT.
+
+4) MVCC commit log(clog) cleanup
+Each GTT in a session has its own piece of data, and they have their own
+transaction information. We set up data structures to track and maintain
+this information. The cleaning of CLOGs also needs to consider the transaction
+information of GTT.
+
+Detailed design
+-----------------------------------------
+
+1. CATALOG
+1.1 relpersistence
+define RELPERSISTENCEGLOBALTEMP 'g'
+Mark Global Temporary Table in pg_class relpersistence to 'g'. The relpersistence
+of indexes created on the GTT, sequences on GTT and toast tables on GTT are
+also set to 'g'
+
+1.2 on commit clause
+LTT's status associated with on commit DELETE ROWS and on commit PRESERVE ROWS
+is not stored in catalog. Instead, GTTs need a bool value on_commit_delete_rows
+in reloptions which is shared among sessions.
+
+1.3 gram.y
+GTT is already supported in syntax tree. We remove the warning message
+"GLOBAL is deprecated in temporary table creation" and mark
+relpersistence = RELPERSISTENCEGLOBALTEMP.
+
+2. STORAGE INFO & STATISTICS INFO & TRANSACTION INFO
+2.1. gtt_storage_local_hash
+Each backend creates a local hashtable gtt_storage_local_hash to track a GTT's
+storage file information, statistics, and transaction information.
+
+2.2 GTT storage file info track
+1) When one session inserts data into a GTT for the first time, record the
+storage info to gtt_storage_local_hash.
+2) Use beforeshmemexit to ensure that all files of session GTT are deleted when
+the session exits.
+
+2.3 statistics info
+1) relpages reltuples relallvisible relfilenode
+2) The statistics of each column from pg_statistic
+All the above information is stored in gtt_storage_local_hash.
+When doing vacuum or analyze, GTT's statistic is updated, which is used by
+the SQL planner.
+The statistics summarizes only data in the current session.
+
+2.3 transaction info track
+frozenxid minmulti from pg_class is stored to gtt_storage_local_hash.
+
+3 DDL
+3.1. active_gtt_shared_hash
+This is the hash table created in shared memory to trace the GTT files initialized
+in each session. Each hash entry contains a bitmap that records the backendid of
+the initialized GTT file. With this hash table, we know which backend/session
+is using this GTT. Such information is used during GTT's DDL operations.
+
+3.2 DROP GTT
+One GTT is allowed to be deleted when there is only one session using the table
+and the session is the current session. After holding the lock on GTT,
+active_gtt_shared_hash is checked to ensure that.
+
+3.3 ALTER GTT/DROP INDEX ON GTT
+Same as drop GTT.
+
+3.4 CREATE INDEX ON GTT
+1) create index on GTT statements build index based on local data in a session.
+2) After the index is created, record the index metadata to the catalog.
+3) Other sessions can enable or disable the local GTT index.
+
+3.5 TRUNCATE/REINDEX GTT
+The SQL truncate/reindex command open the GTT using AccessShareLock lock,
+not AccessExclusiveLock, because this command only cleans up local data and
+local buffers in current session. This allows these operations to be executed
+concurrently between sessions, unlike normal tables.
+
+3.6 LOCK GTT
+A lock GTT statement does not hold any relation lock.
+
+3.7 CLUSTER GTT/VACUUM FULL GTT
+The current version does not support.
+
+4 MVCC commit log(clog) cleanup
+
+The GTT storage file contains transaction information. Queries for GTT data rely
+on transaction information such as clog. The transaction information required by
+each session may be completely different. We need to ensure that the transaction
+information of the GTT data is not cleaned up during its lifetime and that
+transaction resources are recycled at the instance level.
+
+4.1 The session level GTT oldest frozenxid
+1) To manage all GTT transaction information, add session level oldest frozenxid
+in each session. When one GTT is created or removed, record the session level
+oldest frozenxid and store it in MyProc.
+2) When vacuum advances the database's frozenxid, session level oldest frozenxid
+should be considered. This is acquired by searching all of MyProc. This way,
+we can avoid the clog required by GTTs to be cleaned.
+
+4.2 vacuum GTT
+Users can perform vacuum over a GTT to clean up local data in the GTT.
+
+4.3 autovacuum GTT
+Autovacuum skips all GTTs, because the data in GTTs is only visible in current session.
+
+5 OTHERS
+5.1 Parallel query
+Planner does not produce parallel query plans for SQL related to GTT. Because
+GTT private data cannot be accessed across processes.
+
+5.2 WAL and Logical replication
+Like LTT, the DML on GTT does not record WAL and is not parsed or replay by
+the logical replication.
\ No newline at end of file
-- 
2.30.1 (Apple Git-130)

0004-gtt-v59-regress.patchapplication/octet-stream; name=0004-gtt-v59-regress.patchDownload
diff --git a/src/test/isolation/expected/gtt-sequence.out b/src/test/isolation/expected/gtt-sequence.out
new file mode 100644
index 00000000000..31db2ebd423
--- /dev/null
+++ b/src/test/isolation/expected/gtt-sequence.out
@@ -0,0 +1,48 @@
+unused step name: s1_seq_restart
+Parsed test spec with 2 sessions
+
+starting permutation: s1_seq_next s2_seq_next s1_seq_next
+step s1_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      1
+(1 row)
+
+step s2_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      1
+(1 row)
+
+step s1_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      2
+(1 row)
+
+
+starting permutation: s1_select s2_select s1_insert s2_insert s1_select s2_select
+step s1_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+(0 rows)
+
+step s2_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+(0 rows)
+
+step s1_insert: insert into gtt_with_seq values(1);
+step s2_insert: insert into gtt_with_seq values(10);
+step s1_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+ 1| 3
+(1 row)
+
+step s2_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+10| 1
+(1 row)
+
diff --git a/src/test/isolation/expected/gtt-table.out b/src/test/isolation/expected/gtt-table.out
new file mode 100644
index 00000000000..5825773aa12
--- /dev/null
+++ b/src/test/isolation/expected/gtt-table.out
@@ -0,0 +1,675 @@
+Parsed test spec with 3 sessions
+
+starting permutation: s1_update_d
+step s1_update_d: update gtt_on_commit_delete_row set b = 'update'
+
+starting permutation: s1_select_d s1_insert_d s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_begin s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+1|test1
+(1 row)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_begin s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+1|test1
+(1 row)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_select_d s1_truncate_d s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_begin s1_insert_d s1_select_d s1_truncate_d s1_select_d s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_begin s1_insert_d s1_select_d s1_truncate_d s1_select_d s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_insert_d s1_select_d s1_begin s1_insert_d s1_select_d s1_save_1 s1_truncate_d s1_insert_d s1_select_d s1_save_2 s1_truncate_d s1_insert_d s1_select_d s1_save_3 s1_rollback_to_save_2 s1_select_d s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_save_1: SAVEPOINT save1
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_save_2: SAVEPOINT save2
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+4|test1
+(1 row)
+
+step s1_save_3: SAVEPOINT save3
+step s1_rollback_to_save_2: rollback to savepoint save2
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+5|test1
+(2 rows)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_insert_d s1_select_d s1_begin s1_insert_d s1_select_d s1_save_1 s1_truncate_d s1_insert_d s1_select_d s1_save_2 s1_truncate_d s1_insert_d s1_select_d s1_save_3 s1_rollback_to_save_2 s1_select_d s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_save_1: SAVEPOINT save1
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_save_2: SAVEPOINT save2
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+4|test1
+(1 row)
+
+step s1_save_3: SAVEPOINT save3
+step s1_rollback_to_save_2: rollback to savepoint save2
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+5|test1
+(2 rows)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_update_p
+step s2_update_p: update gtt_on_commit_preserve_row set b = 'update'
+
+starting permutation: s2_select_p s2_insert_p s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_begin s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_begin s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_select_p s2_insert_p s2_select_p s2_truncate_p s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_select_p s2_insert_p s2_begin s2_insert_p s2_select_p s2_truncate_p s2_select_p s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_insert_p s2_begin s2_insert_p s2_select_p s2_truncate_p s2_select_p s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_insert_p s2_select_p s2_begin s2_insert_p s2_select_p s2_save_1 s2_truncate_p s2_insert_p s2_select_p s2_save_2 s2_truncate_p s2_insert_p s2_select_p s2_save_3 s2_rollback_to_save_2 s2_select_p s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_save_1: SAVEPOINT save1
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_save_2: SAVEPOINT save2
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+4|test10
+(1 row)
+
+step s2_save_3: SAVEPOINT save3
+step s2_rollback_to_save_2: rollback to savepoint save2
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+
+starting permutation: s2_insert_p s2_select_p s2_begin s2_insert_p s2_select_p s2_save_1 s2_truncate_p s2_insert_p s2_select_p s2_save_2 s2_truncate_p s2_insert_p s2_select_p s2_save_3 s2_rollback_to_save_2 s2_select_p s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_save_1: SAVEPOINT save1
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_save_2: SAVEPOINT save2
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+4|test10
+(1 row)
+
+step s2_save_3: SAVEPOINT save3
+step s2_rollback_to_save_2: rollback to savepoint save2
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_insert_p s2_insert_p s1_select_p s2_select_p
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_truncate_p s2_truncate_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_begin s1_insert_d s2_insert_d s1_truncate_d s2_insert_d s1_commit
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s2_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s2_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_commit: commit
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_reindex_p s2_reindex_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_reindex_p: reindex table gtt_on_commit_preserve_row
+step s2_reindex_p: reindex table gtt_on_commit_preserve_row
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_reindex_i_p s2_reindex_i_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_reindex_i_p: reindex index gtt_on_commit_preserve_row_pkey
+step s2_reindex_i_p: reindex index gtt_on_commit_preserve_row_pkey
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_insert_c s3_create_c s3_insert_c s1_insert_c s1_analyze_c s2_analyze_c s3_analyze_c s1_select_c s2_select_c s3_select_c
+step s2_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s3_create_c: create unique index idx_temp_table_a on gtt_test_createindex(a)
+step s3_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s1_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s1_analyze_c: analyze gtt_test_createindex
+step s2_analyze_c: analyze gtt_test_createindex
+step s3_analyze_c: analyze gtt_test_createindex
+step s1_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                                               
+---------------------------------------------------------
+Index Scan using idx_temp_table_a on gtt_test_createindex
+  Index Cond: (a = 1)                                    
+(2 rows)
+
+step s2_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                      
+--------------------------------
+Seq Scan on gtt_test_createindex
+  Filter: (a = 1)               
+(2 rows)
+
+step s3_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                                               
+---------------------------------------------------------
+Index Scan using idx_temp_table_a on gtt_test_createindex
+  Index Cond: (a = 1)                                    
+(2 rows)
+
+
+starting permutation: s1_begin s2_begin s1_lock_p s2_lock_p s1_truncate_p s2_truncate_p s1_insert_p s2_insert_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_lock_p: LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE
+step s2_lock_p: LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE
+step s1_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index f4c01006fc1..746a17f824c 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -96,3 +96,5 @@ test: plpgsql-toast
 test: truncate-conflict
 test: serializable-parallel
 test: serializable-parallel-2
+test: gtt-sequence
+test: gtt-table
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 88594a3cb5d..ec643aadb5f 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -80,9 +80,30 @@ disconnect_atexit(void)
 {
 	int			i;
 
-	for (i = 0; i < nconns; i++)
+	for (i = 1; i < nconns; i++)
 		if (conns[i].conn)
 			PQfinish(conns[i].conn);
+
+	if (parseresult.destroy)
+	{
+		PGresult   *res;
+
+		res = PQexec(conns[0].conn, parseresult.destroy);
+		if (PQresultStatus(res) == PGRES_TUPLES_OK)
+		{
+			printResultSet(res);
+		}
+		else if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		{
+			fprintf(stderr, "destroy failed: %s",
+					PQerrorMessage(conns[0].conn));
+			/* don't exit on teardown failure */
+		}
+		PQclear(res);
+	}
+
+	if (conns[0].conn)
+		PQfinish(conns[0].conn);
 }
 
 int
@@ -214,6 +235,24 @@ main(int argc, char **argv)
 	PQclear(res);
 	termPQExpBuffer(&wait_query);
 
+	if (parseresult.initialize)
+	{
+		PGresult   *res;
+
+		res = PQexec(conns[0].conn, parseresult.initialize);
+		if (PQresultStatus(res) == PGRES_TUPLES_OK)
+		{
+			printResultSet(res);
+		}
+		else if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		{
+			fprintf(stderr, "initialize failed: %s",
+					PQerrorMessage(conns[0].conn));
+			/* don't exit on teardown failure */
+		}
+		PQclear(res);
+	}
+
 	/*
 	 * Run the permutations specified in the spec, or all if none were
 	 * explicitly specified.
diff --git a/src/test/isolation/isolationtester.h b/src/test/isolation/isolationtester.h
index 5f300219c20..b5a29893da9 100644
--- a/src/test/isolation/isolationtester.h
+++ b/src/test/isolation/isolationtester.h
@@ -81,6 +81,8 @@ typedef struct
 	int			nsessions;
 	Permutation **permutations;
 	int			npermutations;
+	char	   *initialize;
+	char	   *destroy;
 } TestSpec;
 
 extern TestSpec parseresult;
diff --git a/src/test/isolation/specparse.y b/src/test/isolation/specparse.y
index c25aa1a73fa..2784f758ed9 100644
--- a/src/test/isolation/specparse.y
+++ b/src/test/isolation/specparse.y
@@ -39,7 +39,7 @@ TestSpec		parseresult;			/* result of parsing is left here */
 }
 
 %type <ptr_list> setup_list
-%type <str>  opt_setup opt_teardown
+%type <str>  opt_setup opt_teardown opt_initialize opt_destroy
 %type <str> setup
 %type <ptr_list> step_list session_list permutation_list opt_permutation_list
 %type <ptr_list> permutation_step_list blocker_list
@@ -51,23 +51,27 @@ TestSpec		parseresult;			/* result of parsing is left here */
 
 %token <str> sqlblock identifier
 %token <integer> INTEGER
-%token NOTICES PERMUTATION SESSION SETUP STEP TEARDOWN TEST
+%token NOTICES PERMUTATION SESSION SETUP STEP TEARDOWN TEST INITIALIZE DESTROY
 
 %%
 
 TestSpec:
+			opt_initialize
 			setup_list
 			opt_teardown
+			opt_destroy
 			session_list
 			opt_permutation_list
 			{
-				parseresult.setupsqls = (char **) $1.elements;
-				parseresult.nsetupsqls = $1.nelements;
-				parseresult.teardownsql = $2;
-				parseresult.sessions = (Session **) $3.elements;
-				parseresult.nsessions = $3.nelements;
-				parseresult.permutations = (Permutation **) $4.elements;
-				parseresult.npermutations = $4.nelements;
+				parseresult.setupsqls = (char **) $2.elements;
+				parseresult.nsetupsqls = $2.nelements;
+				parseresult.teardownsql = $3;
+				parseresult.sessions = (Session **) $5.elements;
+				parseresult.nsessions = $5.nelements;
+				parseresult.permutations = (Permutation **) $6.elements;
+				parseresult.npermutations = $6.nelements;
+				parseresult.initialize = $1;
+				parseresult.destroy = $4;
 			}
 		;
 
@@ -100,6 +104,16 @@ opt_teardown:
 			| TEARDOWN sqlblock	{ $$ = $2; }
 		;
 
+opt_initialize:
+			/* EMPTY */			{ $$ = NULL; }
+			| INITIALIZE sqlblock	{ $$ = $2; }
+		;
+
+opt_destroy:
+			/* EMPTY */			{ $$ = NULL; }
+			| DESTROY sqlblock	{ $$ = $2; }
+		;
+
 session_list:
 			session_list session
 			{
diff --git a/src/test/isolation/specs/gtt-sequence.spec b/src/test/isolation/specs/gtt-sequence.spec
new file mode 100644
index 00000000000..88eece45e29
--- /dev/null
+++ b/src/test/isolation/specs/gtt-sequence.spec
@@ -0,0 +1,39 @@
+# Tests for global temporary relations
+
+initialize
+{
+  CREATE GLOBAL TEMPORARY TABLE if not exists gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+}
+
+destroy
+{
+  /* wait other backend exit */
+  select pg_sleep(1);
+
+  DROP TABLE gtt_with_seq;
+}
+
+# Session 1
+session "s1"
+step "s1_seq_next" { select nextval('gtt_with_seq_c2_seq'); }
+step "s1_seq_restart" { alter sequence gtt_with_seq_c2_seq RESTART; }
+step "s1_insert" { insert into gtt_with_seq values(1); }
+step "s1_select" { select * from gtt_with_seq order by c1,c2; }
+teardown
+{
+  TRUNCATE gtt_with_seq;
+}
+
+# Session 2
+session "s2"
+step "s2_seq_next" { select nextval('gtt_with_seq_c2_seq'); }
+step "s2_insert" { insert into gtt_with_seq values(10); }
+step "s2_select" { select * from gtt_with_seq order by c1,c2; }
+teardown
+{
+  TRUNCATE gtt_with_seq RESTART IDENTITY;
+}
+
+permutation "s1_seq_next" "s2_seq_next" "s1_seq_next"
+permutation "s1_select" "s2_select" "s1_insert" "s2_insert" "s1_select" "s2_select"
+
diff --git a/src/test/isolation/specs/gtt-table.spec b/src/test/isolation/specs/gtt-table.spec
new file mode 100644
index 00000000000..e0396b21ef0
--- /dev/null
+++ b/src/test/isolation/specs/gtt-table.spec
@@ -0,0 +1,135 @@
+# Tests for global temporary relations
+
+initialize
+{
+  create global temp table gtt_on_commit_delete_row(a bigserial primary key, b text) on commit delete rows;
+  create global temp table gtt_on_commit_preserve_row(a bigserial primary key, b text) on commit preserve rows;
+  create global temp table gtt_test_createindex(a int, b char(1000)) on commit preserve rows;
+}
+
+destroy
+{
+  /* wait other backend exit */
+  select pg_sleep(1);
+
+  DROP TABLE gtt_on_commit_delete_row;
+  DROP TABLE gtt_on_commit_preserve_row;
+  DROP TABLE gtt_test_createindex;
+}
+
+# Session 1
+session "s1"
+step "s1_begin" {begin}
+step "s1_commit" {commit}
+step "s1_rollback" {rollback}
+step "s1_insert_d" {insert into gtt_on_commit_delete_row (b) values('test1')}
+step "s1_select_d" {select a,b from gtt_on_commit_delete_row order by a,b}
+step "s1_insert_p" {insert into gtt_on_commit_preserve_row (b) values('test20')}
+step "s1_select_p" {select a,b from gtt_on_commit_preserve_row order by a,b}
+step "s1_truncate_d" {truncate gtt_on_commit_delete_row}
+step "s1_truncate_p" {truncate gtt_on_commit_preserve_row}
+step "s1_lock_p" {LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE}
+step "s1_update_d" {update gtt_on_commit_delete_row set b = 'update'}
+step "s1_save_1" {SAVEPOINT save1}
+step "s1_save_2" {SAVEPOINT save2}
+step "s1_save_3" {SAVEPOINT save3}
+step "s1_rollback_to_save_2" {rollback to savepoint save2}
+step "s1_reindex_p" {reindex table gtt_on_commit_preserve_row}
+step "s1_reindex_i_p" {reindex index gtt_on_commit_preserve_row_pkey}
+step "s1_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s1_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s1_analyze_c" {analyze gtt_test_createindex}
+teardown
+{
+  TRUNCATE gtt_on_commit_delete_row RESTART IDENTITY;
+  TRUNCATE gtt_on_commit_preserve_row RESTART IDENTITY;
+}
+
+# Session 2
+session "s2"
+step "s2_begin" {begin}
+step "s2_commit" {commit}
+step "s2_rollback" {rollback}
+step "s2_insert_p" {insert into gtt_on_commit_preserve_row (b) values('test10')}
+step "s2_select_p" {select a,b from gtt_on_commit_preserve_row order by a,b}
+step "s2_insert_d" {insert into gtt_on_commit_delete_row (b) values('test1')}
+step "s2_truncate_p" {truncate gtt_on_commit_preserve_row}
+step "s2_update_p" {update gtt_on_commit_preserve_row set b = 'update'}
+step "s2_lock_p" {LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE}
+step "s2_save_1" {SAVEPOINT save1}
+step "s2_save_2" {SAVEPOINT save2}
+step "s2_save_3" {SAVEPOINT save3}
+step "s2_rollback_to_save_2" {rollback to savepoint save2}
+step "s2_reindex_p" {reindex table gtt_on_commit_preserve_row}
+step "s2_reindex_i_p" {reindex index gtt_on_commit_preserve_row_pkey}
+step "s2_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s2_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s2_analyze_c" {analyze gtt_test_createindex}
+teardown
+{
+  TRUNCATE gtt_on_commit_delete_row RESTART IDENTITY;
+  TRUNCATE gtt_on_commit_preserve_row RESTART IDENTITY;
+}
+
+session "s3"
+step "s3_create_c" {create unique index idx_temp_table_a on gtt_test_createindex(a)}
+step "s3_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s3_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s3_analyze_c" {analyze gtt_test_createindex}
+
+
+#
+# test on commit delete temp table
+#
+
+# test update empty temp table
+permutation "s1_update_d"
+# test insert into temp table
+permutation "s1_select_d" "s1_insert_d" "s1_select_d"
+# test temp table in transaction(commit)
+permutation "s1_select_d" "s1_begin"    "s1_insert_d" "s1_select_d"   "s1_commit"   "s1_select_d" 
+# test temp table in transaction(rollback)
+permutation "s1_select_d" "s1_begin"    "s1_insert_d" "s1_select_d"   "s1_rollback" "s1_select_d" 
+# test truncate
+permutation "s1_select_d" "s1_insert_d" "s1_select_d" "s1_truncate_d" "s1_select_d"
+# test truncate in transaction block
+permutation "s1_select_d" "s1_insert_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_truncate_d" "s1_select_d"   "s1_insert_d" "s1_select_d" "s1_commit"   "s1_select_d" 
+permutation "s1_select_d" "s1_insert_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_truncate_d" "s1_select_d"   "s1_insert_d" "s1_select_d" "s1_rollback" "s1_select_d"
+# test temp table with subtransaction or savepoint
+permutation "s1_insert_d" "s1_select_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_save_1"     "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_2"   "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_3" "s1_rollback_to_save_2" "s1_select_d" "s1_insert_d" "s1_select_d" "s1_commit" "s1_select_d"
+permutation "s1_insert_d" "s1_select_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_save_1"     "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_2"   "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_3" "s1_rollback_to_save_2" "s1_select_d" "s1_insert_d" "s1_select_d" "s1_rollback" "s1_select_d"
+
+#
+# test on commit preserve table
+#
+
+# same as test on commit delete temp table
+permutation "s2_update_p"
+permutation "s2_select_p" "s2_insert_p" "s2_select_p"
+permutation "s2_select_p" "s2_begin"    "s2_insert_p" "s2_select_p"   "s2_commit"   "s2_select_p"
+permutation "s2_select_p" "s2_begin"    "s2_insert_p" "s2_select_p"   "s2_rollback" "s2_select_p"
+permutation "s2_select_p" "s2_insert_p" "s2_select_p" "s2_truncate_p" "s2_select_p"
+permutation "s2_select_p" "s2_insert_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_truncate_p" "s2_select_p"   "s2_insert_p" "s2_select_p" "s2_commit"   "s2_select_p" 
+permutation "s2_select_p" "s2_insert_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_truncate_p" "s2_select_p"   "s2_insert_p" "s2_select_p" "s2_rollback" "s2_select_p" 
+permutation "s2_insert_p" "s2_select_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_save_1"     "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_2"   "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_3" "s2_rollback_to_save_2" "s2_select_p" "s2_insert_p" "s2_select_p" "s2_commit" "s2_select_p"
+permutation "s2_insert_p" "s2_select_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_save_1"     "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_2"   "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_3" "s2_rollback_to_save_2" "s2_select_p" "s2_insert_p" "s2_select_p" "s2_rollback" "s2_select_p"
+
+#
+# test concurrent operation on temp table
+#
+
+#  test concurrent read
+permutation "s1_insert_p" "s2_insert_p" "s1_select_p" "s2_select_p" 
+#  test concurrent truncate
+permutation "s1_begin" "s2_begin"    "s1_insert_p" "s2_insert_p"   "s1_truncate_p" "s2_truncate_p"  "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+permutation "s1_begin" "s1_insert_d" "s2_insert_d" "s1_truncate_d" "s2_insert_d"   "s1_commit" 
+#  test concurrent reindex table
+permutation "s1_begin" "s2_begin" "s1_insert_p" "s2_insert_p" "s1_reindex_p"   "s2_reindex_p"   "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+#  test concurrent reindex index
+permutation "s1_begin" "s2_begin" "s1_insert_p" "s2_insert_p" "s1_reindex_i_p" "s2_reindex_i_p" "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+
+# test create index
+permutation "s2_insert_c" "s3_create_c" "s3_insert_c" "s1_insert_c" "s1_analyze_c" "s2_analyze_c" "s3_analyze_c" "s1_select_c" "s2_select_c" "s3_select_c"
+
+# test lock gtt
+permutation "s1_begin" "s2_begin" "s1_lock_p" "s2_lock_p" "s1_truncate_p" "s2_truncate_p" "s1_insert_p" "s2_insert_p" "s1_commit" "s2_commit" "s1_select_p" "s2_select_p"
diff --git a/src/test/isolation/specscanner.l b/src/test/isolation/specscanner.l
index d9fa6a5b54a..697db975479 100644
--- a/src/test/isolation/specscanner.l
+++ b/src/test/isolation/specscanner.l
@@ -67,6 +67,8 @@ session			{ return SESSION; }
 setup			{ return SETUP; }
 step			{ return STEP; }
 teardown		{ return TEARDOWN; }
+initialize		 { return INITIALIZE; }
+destroy			 { return DESTROY; }
 
  /* Whitespace and comments */
 [\n]			{ yyline++; }
diff --git a/src/test/regress/expected/global_temporary_table.out b/src/test/regress/expected/global_temporary_table.out
new file mode 100644
index 00000000000..ec696c61ce9
--- /dev/null
+++ b/src/test/regress/expected/global_temporary_table.out
@@ -0,0 +1,556 @@
+--
+-- GLobal emparary table test case 
+--
+CREATE SCHEMA IF NOT EXISTS global_temporary_table;
+set search_path=global_temporary_table,sys;
+--
+--  test create global temp table basic syntax
+--
+create global temp table gtt_on_commit_default(a int primary key, b text);
+create global temp table gtt_on_commit_delete(a int primary key, b text) on commit delete rows;
+create global temp table gtt_on_commit_delete2(n int) with (on_commit_delete_rows='true');
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit PRESERVE rows;
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt_test_createindex(c0 tsvector,c1 varchar(100), c2 int);
+create global temp table gtt_test_alter(b text) with(on_commit_delete_rows=true);
+--
+-- test DML on global temp table
+--
+-- update empty temp table
+update gtt_on_commit_delete set b ='test';
+begin;
+insert into gtt_on_commit_delete values (1);
+update gtt_on_commit_delete set b ='test';
+-- should 1 row
+select * from gtt_on_commit_delete;
+ a |  b   
+---+------
+ 1 | test
+(1 row)
+
+commit;
+-- data delete after transaction commit
+-- should 0 row
+select * from gtt_on_commit_delete;
+ a | b 
+---+---
+(0 rows)
+
+-- update empty temp table
+update gtt_on_commit_preserve set b ='test';
+insert into gtt_on_commit_preserve values (2);
+begin;
+insert into gtt_on_commit_preserve values (3);
+update gtt_on_commit_preserve set b ='test';
+-- should 2 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 2 | test
+ 3 | test
+(2 rows)
+
+delete from gtt_on_commit_preserve where a=2;
+commit;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 3 | test
+(1 row)
+
+begin;
+insert into gtt_on_commit_preserve values (4);
+-- temp table support truncate;
+truncate gtt_on_commit_preserve;
+select * from gtt_on_commit_preserve order by a;
+ a | b 
+---+---
+(0 rows)
+
+rollback;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 3 | test
+(1 row)
+
+--
+-- test unsupported global temp partition table
+--
+-- should fail
+CREATE global temp TABLE global_temp_partition_01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+on commit delete rows;
+ERROR:  Only support global temporary regular table.
+CREATE TABLE regular_partition_01_2019 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+);
+CREATE TABLE regular_partition01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time);
+-- should fail
+CREATE global temp TABLE temp_partition01_2018
+PARTITION OF regular_partition01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+ERROR:  Not support global temporary partition table or inherit table.
+CREATE global temp TABLE global_temp_partition_01_2021 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+)on commit delete rows;
+-- should fail
+ALTER TABLE regular_partition01 ATTACH PARTITION global_temp_partition_01_2021 FOR VALUES FROM ('2021-01-01 00:00:00') TO ('2022-01-01 00:00:00');
+ERROR:  cannot attach a global temporary relation as partition of permanent relation "regular_partition01"
+--
+-- test unsupported inherit table
+--
+create table 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 inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent);
+ERROR:  Not support global temporary partition table or inherit table.
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent_global_temp) on commit delete rows;
+ERROR:  Not support global temporary partition table or inherit table.
+--
+-- test DDL on global temp table
+--
+create index idx_gtt_test_alter_b on gtt_test_alter (b);
+insert into gtt_test_alter values('test');
+alter table gtt_test_alter alter b type varchar;
+create index gtt_idx_1 on gtt_test_createindex using gin (c0);
+create index gtt_idx_2 on gtt_test_createindex using gist (c0);
+create index gtt_idx_3 on gtt_test_createindex using hash (c2);
+alter table gtt_test_rename rename to gtt_test_new;
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+create index CONCURRENTLY idx_b on gtt_on_commit_default (b);
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+insert into gtt_on_commit_default values(1,'test');
+create global temp table gtt_test_alter1 (a int primary key,b text);
+alter table gtt_test_alter1 alter a type varchar;
+-- should fail
+alter table gtt_on_commit_default alter a type varchar;
+ERROR:  Only support alter global temporary table in an empty context.
+HINT:  Please create a new connection and execute ALTER TABLE on the new connection.
+-- should fail
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+ERROR:  not support cluster global temporary table yet
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- should fail
+alter table gtt_on_commit_default SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temporary table
+-- should fail
+alter table gtt_on_commit_default set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table
+-- should fail
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- should fail
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- should fail
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit drop;
+ERROR:  global temporary table not support on commit drop clause
+-- should fail
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  could not create global temporary table with on commit and with clause at same time
+-- should fail
+CREATE MATERIALIZED VIEW mv_gtt_on_commit_default as select * from gtt_on_commit_default;
+ERROR:  materialized views must not use global temporary tables or views
+--
+-- test foreign key dependencies for global temp table
+--
+CREATE global temp TABLE temp_products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+CREATE global temp TABLE temp_orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+)on commit delete rows;
+-- should fail
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+CREATE global temp TABLE temp_orders_2 (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products(product_no),
+    quantity integer
+);
+--should fail
+insert into temp_orders values(1,1,1);
+ERROR:  insert or update on table "temp_orders" violates foreign key constraint "temp_orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "temp_products".
+insert into temp_products values(1,'test',1.0);
+begin;
+insert into temp_orders values(1,1,1);
+commit;
+-- should 1 row
+select count(*) from temp_products;
+ count 
+-------
+     1
+(1 row)
+
+-- should 0 row
+select count(*) from temp_orders;
+ count 
+-------
+     0
+(1 row)
+
+--
+-- test sequence on global temp table
+--
+create global temp table global_temp_with_serial (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+-- support insert data to temp table in read only transaction
+insert into global_temp_with_serial (b) values(1);
+select * from global_temp_with_serial;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_delete(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_preserve(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table temp_table_with_sequence_oncommit_delete add c2 int default nextval('seq_1');
+alter table temp_table_with_sequence_oncommit_preserve add c2 int default nextval('seq_1');
+begin;
+insert into temp_table_with_sequence_oncommit_delete (c1)values(1);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(2);
+insert into temp_table_with_sequence_oncommit_delete (c1)values(3);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(4);
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  3 |  3
+(2 rows)
+
+commit;
+-- should 0 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_preserve order by c1;
+ c1 | c2 
+----+----
+  2 |  2
+  4 |  4
+(2 rows)
+
+--
+-- test statistics on temp table
+--
+create global temp table temp_table_test_statistics(a int);
+insert into temp_table_test_statistics values(generate_series(1,100000));
+create index idx_test_1 on temp_table_test_statistics (a);
+create index idx_test_2 on temp_table_test_statistics((a*10));
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+                   QUERY PLAN                   
+------------------------------------------------
+ Bitmap Heap Scan on temp_table_test_statistics
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_test_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+                   QUERY PLAN                   
+------------------------------------------------
+ Bitmap Heap Scan on temp_table_test_statistics
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_test_2
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+-- test statistic for whole row
+explain (costs off) select count(*) from temp_table_test_statistics group by temp_table_test_statistics;
+                  QUERY PLAN                  
+----------------------------------------------
+ HashAggregate
+   Group Key: temp_table_test_statistics.*
+   ->  Seq Scan on temp_table_test_statistics
+(3 rows)
+
+-- test statistic for system column
+explain (costs off) select count(*) from temp_table_test_statistics group by tableoid;
+                  QUERY PLAN                  
+----------------------------------------------
+ HashAggregate
+   Group Key: tableoid
+   ->  Seq Scan on temp_table_test_statistics
+(3 rows)
+
+analyze temp_table_test_statistics;
+-- indexscan by idx_test_1
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Index Only Scan using idx_test_1 on temp_table_test_statistics
+   Index Cond: (a = 200000)
+(2 rows)
+
+-- indexscan by idx_test_2
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+                        QUERY PLAN                         
+-----------------------------------------------------------
+ Index Scan using idx_test_2 on temp_table_test_statistics
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+explain (costs off) select count(*) from temp_table_test_statistics group by temp_table_test_statistics;
+                  QUERY PLAN                  
+----------------------------------------------
+ HashAggregate
+   Group Key: temp_table_test_statistics.*
+   ->  Seq Scan on temp_table_test_statistics
+(3 rows)
+
+explain (costs off) select count(*) from temp_table_test_statistics group by tableoid;
+                  QUERY PLAN                  
+----------------------------------------------
+ HashAggregate
+   Group Key: tableoid
+   ->  Seq Scan on temp_table_test_statistics
+(3 rows)
+
+--
+-- test temp table with toast table
+--
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |                0 |        483328 |           98304 |                 581632
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            32768 |                  |         32768 |               0 |                  32768
+(3 rows)
+
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |             8192 |        499712 |          106496 |                 606208
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            40960 |                  |         40960 |               0 |                  40960
+(3 rows)
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,10),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',10);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |             8192 |                0 |         16384 |           32768 |                  49152
+ idx_gtt_t_kenyon_1 |            16384 |                  |         16384 |               0 |                  16384
+ idx_gtt_t_kenyon_2 |            16384 |                  |         16384 |               0 |                  16384
+(3 rows)
+
+rollback;
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |             8192 |        499712 |          106496 |                 606208
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            40960 |                  |         40960 |               0 |                  40960
+(3 rows)
+
+-- test analyze/vacuum on global temp table
+ANALYZE gtt_t_kenyon;
+VACUUM gtt_t_kenyon;
+--
+-- test global temp table system view
+--
+create global temp table temp_table_test_systemview(a int primary key, b text) on commit PRESERVE rows;
+-- should empty, storage not initialized
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+ tablename 
+-----------
+(0 rows)
+
+-- should empty, storage not initialized
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into temp_table_test_systemview values(generate_series(1,10000),'test');
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+         tablename          
+----------------------------
+ temp_table_test_systemview
+(1 row)
+
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+       schemaname       |            tablename            | relpages | reltuples | relallvisible 
+------------------------+---------------------------------+----------+-----------+---------------
+ global_temporary_table | temp_table_test_systemview      |        0 |         0 |             0
+ global_temporary_table | temp_table_test_systemview_pkey |        1 |         0 |             0
+(2 rows)
+
+-- should empty
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+analyze temp_table_test_systemview;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+       schemaname       |            tablename            | relpages | reltuples | relallvisible 
+------------------------+---------------------------------+----------+-----------+---------------
+ global_temporary_table | temp_table_test_systemview      |       55 |     10000 |             0
+ global_temporary_table | temp_table_test_systemview_pkey |       30 |     10000 |             0
+(2 rows)
+
+-- get data after analyze;
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+       schemaname       |         tablename          | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------------------+----------------------------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ global_temporary_table | temp_table_test_systemview | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ global_temporary_table | temp_table_test_systemview | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+-- get all object info in current schema
+select relname ,relkind, relpersistence, reloptions from pg_class c, pg_namespace n where c.relnamespace = n.oid and n.nspname = 'global_temporary_table' order by relkind,relpersistence,relname;
+                     relname                     | relkind | relpersistence |          reloptions           
+-------------------------------------------------+---------+----------------+-------------------------------
+ global_temp_partition_01_2021_id_seq            | S       | g              | {on_commit_delete_rows=false}
+ global_temp_with_serial_a_seq                   | S       | g              | {on_commit_delete_rows=false}
+ regular_partition01_id_seq                      | S       | p              | 
+ regular_partition_01_2019_id_seq                | S       | p              | 
+ seq_1                                           | S       | p              | 
+ gtt_idx_1                                       | i       | g              | 
+ gtt_idx_2                                       | i       | g              | 
+ gtt_idx_3                                       | i       | g              | 
+ gtt_on_commit_default_pkey                      | i       | g              | 
+ gtt_on_commit_delete_pkey                       | i       | g              | 
+ gtt_on_commit_preserve_pkey                     | i       | g              | 
+ gtt_test_alter1_pkey                            | i       | g              | 
+ gtt_test_rename_pkey                            | i       | g              | 
+ idx_b                                           | i       | g              | 
+ idx_gtt_t_kenyon_1                              | i       | g              | 
+ idx_gtt_t_kenyon_2                              | i       | g              | 
+ idx_gtt_test_alter_b                            | i       | g              | 
+ idx_test_1                                      | i       | g              | 
+ idx_test_2                                      | i       | g              | 
+ products_pkey                                   | i       | g              | 
+ temp_orders_2_pkey                              | i       | g              | 
+ temp_orders_pkey                                | i       | g              | 
+ temp_products_pkey                              | i       | g              | 
+ temp_table_test_systemview_pkey                 | i       | g              | 
+ temp_table_with_sequence_oncommit_delete_pkey   | i       | g              | 
+ temp_table_with_sequence_oncommit_preserve_pkey | i       | g              | 
+ regular_partition01                             | p       | p              | 
+ global_temp_partition_01_2021                   | r       | g              | {on_commit_delete_rows=true}
+ global_temp_with_serial                         | r       | g              | {on_commit_delete_rows=false}
+ gtt_on_commit_default                           | r       | g              | {on_commit_delete_rows=false}
+ gtt_on_commit_delete                            | r       | g              | {on_commit_delete_rows=true}
+ gtt_on_commit_delete2                           | r       | g              | {on_commit_delete_rows=true}
+ gtt_on_commit_preserve                          | r       | g              | {on_commit_delete_rows=false}
+ gtt_t_kenyon                                    | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_alter                                  | r       | g              | {on_commit_delete_rows=true}
+ gtt_test_alter1                                 | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_createindex                            | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_new                                    | r       | g              | {on_commit_delete_rows=false}
+ inherits_parent_global_temp                     | r       | g              | {on_commit_delete_rows=true}
+ products                                        | r       | g              | {on_commit_delete_rows=false}
+ temp_orders                                     | r       | g              | {on_commit_delete_rows=true}
+ temp_orders_2                                   | r       | g              | {on_commit_delete_rows=false}
+ temp_products                                   | r       | g              | {on_commit_delete_rows=false}
+ temp_table_test_statistics                      | r       | g              | {on_commit_delete_rows=false}
+ temp_table_test_systemview                      | r       | g              | {on_commit_delete_rows=false}
+ temp_table_with_sequence_oncommit_delete        | r       | g              | {on_commit_delete_rows=true}
+ temp_table_with_sequence_oncommit_preserve      | r       | g              | {on_commit_delete_rows=false}
+ foo                                             | r       | p              | 
+ inherits_parent                                 | r       | p              | 
+ regular_partition_01_2019                       | r       | p              | 
+(50 rows)
+
+reset search_path;
+drop schema global_temporary_table cascade;
+NOTICE:  drop cascades to 25 other objects
+DETAIL:  drop cascades to table global_temporary_table.gtt_on_commit_default
+drop cascades to table global_temporary_table.gtt_on_commit_delete
+drop cascades to table global_temporary_table.gtt_on_commit_delete2
+drop cascades to table global_temporary_table.gtt_on_commit_preserve
+drop cascades to table global_temporary_table.gtt_test_new
+drop cascades to table global_temporary_table.gtt_test_createindex
+drop cascades to table global_temporary_table.gtt_test_alter
+drop cascades to table global_temporary_table.regular_partition_01_2019
+drop cascades to table global_temporary_table.regular_partition01
+drop cascades to table global_temporary_table.global_temp_partition_01_2021
+drop cascades to table global_temporary_table.inherits_parent
+drop cascades to table global_temporary_table.inherits_parent_global_temp
+drop cascades to table global_temporary_table.gtt_test_alter1
+drop cascades to table global_temporary_table.foo
+drop cascades to table global_temporary_table.temp_products
+drop cascades to table global_temporary_table.products
+drop cascades to table global_temporary_table.temp_orders
+drop cascades to table global_temporary_table.temp_orders_2
+drop cascades to table global_temporary_table.global_temp_with_serial
+drop cascades to sequence global_temporary_table.seq_1
+drop cascades to table global_temporary_table.temp_table_with_sequence_oncommit_delete
+drop cascades to table global_temporary_table.temp_table_with_sequence_oncommit_preserve
+drop cascades to table global_temporary_table.temp_table_test_statistics
+drop cascades to table global_temporary_table.gtt_t_kenyon
+drop cascades to table global_temporary_table.temp_table_test_systemview
+-- should empty
+select * from pg_list_gtt_relfrozenxids();
+ pid | relfrozenxid 
+-----+--------------
+(0 rows)
+
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2fa00a3c29a..e0001bc3448 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1359,6 +1359,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 7be89178f0f..db8095d30bf 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -130,3 +130,6 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: global_temporary_table
diff --git a/src/test/regress/sql/global_temporary_table.sql b/src/test/regress/sql/global_temporary_table.sql
new file mode 100644
index 00000000000..1c45de23294
--- /dev/null
+++ b/src/test/regress/sql/global_temporary_table.sql
@@ -0,0 +1,299 @@
+--
+-- GLobal emparary table test case 
+--
+
+CREATE SCHEMA IF NOT EXISTS global_temporary_table;
+set search_path=global_temporary_table,sys;
+
+--
+--  test create global temp table basic syntax
+--
+create global temp table gtt_on_commit_default(a int primary key, b text);
+create global temp table gtt_on_commit_delete(a int primary key, b text) on commit delete rows;
+create global temp table gtt_on_commit_delete2(n int) with (on_commit_delete_rows='true');
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit PRESERVE rows;
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt_test_createindex(c0 tsvector,c1 varchar(100), c2 int);
+create global temp table gtt_test_alter(b text) with(on_commit_delete_rows=true);
+--
+-- test DML on global temp table
+--
+
+-- update empty temp table
+update gtt_on_commit_delete set b ='test';
+begin;
+insert into gtt_on_commit_delete values (1);
+update gtt_on_commit_delete set b ='test';
+-- should 1 row
+select * from gtt_on_commit_delete;
+commit;
+-- data delete after transaction commit
+-- should 0 row
+select * from gtt_on_commit_delete;
+
+-- update empty temp table
+update gtt_on_commit_preserve set b ='test';
+insert into gtt_on_commit_preserve values (2);
+begin;
+insert into gtt_on_commit_preserve values (3);
+update gtt_on_commit_preserve set b ='test';
+-- should 2 row
+select * from gtt_on_commit_preserve order by a;
+delete from gtt_on_commit_preserve where a=2;
+commit;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+
+begin;
+insert into gtt_on_commit_preserve values (4);
+-- temp table support truncate;
+truncate gtt_on_commit_preserve;
+select * from gtt_on_commit_preserve order by a;
+rollback;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+
+--
+-- test unsupported global temp partition table
+--
+
+-- should fail
+CREATE global temp TABLE global_temp_partition_01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+on commit delete rows;
+
+CREATE TABLE regular_partition_01_2019 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+);
+
+CREATE TABLE regular_partition01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time);
+
+-- should fail
+CREATE global temp TABLE temp_partition01_2018
+PARTITION OF regular_partition01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE global_temp_partition_01_2021 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+)on commit delete rows;
+
+-- should fail
+ALTER TABLE regular_partition01 ATTACH PARTITION global_temp_partition_01_2021 FOR VALUES FROM ('2021-01-01 00:00:00') TO ('2022-01-01 00:00:00');
+
+--
+-- test unsupported inherit table
+--
+create table 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 inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent);
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent_global_temp) on commit delete rows;
+
+--
+-- test DDL on global temp table
+--
+create index idx_gtt_test_alter_b on gtt_test_alter (b);
+insert into gtt_test_alter values('test');
+alter table gtt_test_alter alter b type varchar;
+create index gtt_idx_1 on gtt_test_createindex using gin (c0);
+create index gtt_idx_2 on gtt_test_createindex using gist (c0);
+create index gtt_idx_3 on gtt_test_createindex using hash (c2);
+alter table gtt_test_rename rename to gtt_test_new;
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+create index CONCURRENTLY idx_b on gtt_on_commit_default (b);
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+insert into gtt_on_commit_default values(1,'test');
+create global temp table gtt_test_alter1 (a int primary key,b text);
+alter table gtt_test_alter1 alter a type varchar;
+-- should fail
+alter table gtt_on_commit_default alter a type varchar;
+-- should fail
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) on commit delete rows;
+-- should fail
+alter table gtt_on_commit_default SET TABLESPACE pg_default;
+-- should fail
+alter table gtt_on_commit_default set ( on_commit_delete_rows='true');
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) with(on_commit_delete_rows=true);
+-- should fail
+create or replace global temp view gtt_v as select 5;
+create table foo();
+-- should fail
+alter table foo set (on_commit_delete_rows='true');
+-- should fail
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit drop;
+-- should fail
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+-- should fail
+CREATE MATERIALIZED VIEW mv_gtt_on_commit_default as select * from gtt_on_commit_default;
+
+--
+-- test foreign key dependencies for global temp table
+--
+CREATE global temp TABLE temp_products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+CREATE global temp TABLE temp_orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+)on commit delete rows;
+
+-- should fail
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+);
+
+CREATE global temp TABLE temp_orders_2 (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products(product_no),
+    quantity integer
+);
+
+--should fail
+insert into temp_orders values(1,1,1);
+
+insert into temp_products values(1,'test',1.0);
+begin;
+insert into temp_orders values(1,1,1);
+commit;
+-- should 1 row
+select count(*) from temp_products;
+-- should 0 row
+select count(*) from temp_orders;
+
+--
+-- test sequence on global temp table
+--
+create global temp table global_temp_with_serial (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+-- support insert data to temp table in read only transaction
+insert into global_temp_with_serial (b) values(1);
+select * from global_temp_with_serial;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_delete(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_preserve(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table temp_table_with_sequence_oncommit_delete add c2 int default nextval('seq_1');
+alter table temp_table_with_sequence_oncommit_preserve add c2 int default nextval('seq_1');
+begin;
+insert into temp_table_with_sequence_oncommit_delete (c1)values(1);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(2);
+insert into temp_table_with_sequence_oncommit_delete (c1)values(3);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(4);
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+commit;
+-- should 0 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_preserve order by c1;
+
+--
+-- test statistics on temp table
+--
+create global temp table temp_table_test_statistics(a int);
+insert into temp_table_test_statistics values(generate_series(1,100000));
+create index idx_test_1 on temp_table_test_statistics (a);
+create index idx_test_2 on temp_table_test_statistics((a*10));
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+-- test statistic for whole row
+explain (costs off) select count(*) from temp_table_test_statistics group by temp_table_test_statistics;
+-- test statistic for system column
+explain (costs off) select count(*) from temp_table_test_statistics group by tableoid;
+analyze temp_table_test_statistics;
+-- indexscan by idx_test_1
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+-- indexscan by idx_test_2
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+explain (costs off) select count(*) from temp_table_test_statistics group by temp_table_test_statistics;
+explain (costs off) select count(*) from temp_table_test_statistics group by tableoid;
+
+--
+-- test temp table with toast table
+--
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,10),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',10);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+rollback;
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+
+-- test analyze/vacuum on global temp table
+ANALYZE gtt_t_kenyon;
+VACUUM gtt_t_kenyon;
+
+--
+-- test global temp table system view
+--
+create global temp table temp_table_test_systemview(a int primary key, b text) on commit PRESERVE rows;
+-- should empty, storage not initialized
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+-- should empty, storage not initialized
+select count(*) from pg_list_gtt_relfrozenxids();
+insert into temp_table_test_systemview values(generate_series(1,10000),'test');
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+select count(*) from pg_list_gtt_relfrozenxids();
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+-- should empty
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+analyze temp_table_test_systemview;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+-- get data after analyze;
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+
+-- get all object info in current schema
+select relname ,relkind, relpersistence, reloptions from pg_class c, pg_namespace n where c.relnamespace = n.oid and n.nspname = 'global_temporary_table' order by relkind,relpersistence,relname;
+
+reset search_path;
+drop schema global_temporary_table cascade;
+-- should empty
+select * from pg_list_gtt_relfrozenxids();
+
-- 
2.30.1 (Apple Git-130)

0003-gtt-v59-implementation.patchapplication/octet-stream; name=0003-gtt-v59-implementation.patchDownload
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index e84ecd1c981..c58218b1508 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -18,6 +18,7 @@
 #include "access/toast_internals.h"
 #include "access/visibilitymap.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "funcapi.h"
 #include "miscadmin.h"
 #include "storage/bufmgr.h"
@@ -223,6 +224,8 @@ verify_heapam(PG_FUNCTION_ARGS)
 	BlockNumber last_block;
 	BlockNumber nblocks;
 	const char *skip;
+	TransactionId	relfrozenxid = InvalidTransactionId;
+	MultiXactId		relminmxid = InvalidMultiXactId;
 
 	/* Check to see if caller supports us returning a tuplestore */
 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -339,6 +342,13 @@ verify_heapam(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(ctx.rel) &&
+		!gtt_storage_attached(RelationGetRelid(ctx.rel)))
+	{
+		relation_close(ctx.rel, AccessShareLock);
+		PG_RETURN_NULL();
+	}
+
 	/* Early exit if the relation is empty */
 	nblocks = RelationGetNumberOfBlocks(ctx.rel);
 	if (!nblocks)
@@ -406,9 +416,25 @@ verify_heapam(PG_FUNCTION_ARGS)
 
 	update_cached_xid_range(&ctx);
 	update_cached_mxid_range(&ctx);
-	ctx.relfrozenxid = ctx.rel->rd_rel->relfrozenxid;
+
+	if (RELATION_IS_GLOBAL_TEMP(ctx.rel))
+	{
+		get_gtt_relstats(RelationGetRelid(ctx.rel),
+						NULL,
+						NULL,
+						NULL,
+						&relfrozenxid,
+						&relminmxid);
+	}
+	else
+	{
+		relfrozenxid = ctx.rel->rd_rel->relfrozenxid;
+		relminmxid = ctx.rel->rd_rel->relminmxid;
+	}
+
+	ctx.relfrozenxid = relfrozenxid;
 	ctx.relfrozenfxid = FullTransactionIdFromXidAndCtx(ctx.relfrozenxid, &ctx);
-	ctx.relminmxid = ctx.rel->rd_rel->relminmxid;
+	ctx.relminmxid = relminmxid;
 
 	if (TransactionIdIsNormal(ctx.relfrozenxid))
 		ctx.oldest_xid = ctx.relfrozenxid;
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index b5602f53233..21b2d2a9527 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -159,6 +159,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * In order to avoid consistency problems, the global temporary table
+	 * uses ShareUpdateExclusiveLock.
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temporary table on commit options",
+			RELOPT_KIND_HEAP,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},
 	/* list terminator */
 	{{NULL}}
 };
@@ -1834,6 +1847,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 43ba03b6eb9..49f1052fdb1 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1023,7 +1023,7 @@ gistproperty(Oid index_oid, int attno,
 XLogRecPtr
 gistGetFakeLSN(Relation rel)
 {
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	if (RELATION_IS_TEMP(rel))
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index eb3810494f2..cbd22909582 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -151,7 +151,7 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	if (!RELATION_IS_TEMP(index))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 2da2be16969..34ecef39de9 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -52,6 +52,7 @@
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/atomics.h"
@@ -5844,6 +5845,19 @@ heap_abort_speculative(Relation relation, ItemPointer tid)
 	BlockNumber block;
 	Buffer		buffer;
 	TransactionId prune_xid;
+	TransactionId relfrozenxid = InvalidTransactionId;
+
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		get_gtt_relstats(RelationGetRelid(relation),
+						NULL,
+						NULL,
+						NULL,
+						&relfrozenxid,
+						NULL);
+	}
+	else
+		relfrozenxid = relation->rd_rel->relfrozenxid;
 
 	Assert(ItemPointerIsValid(tid));
 
@@ -5896,8 +5910,8 @@ heap_abort_speculative(Relation relation, ItemPointer tid)
 	 * TransactionXmin, so there's no race here).
 	 */
 	Assert(TransactionIdIsValid(TransactionXmin));
-	if (TransactionIdPrecedes(TransactionXmin, relation->rd_rel->relfrozenxid))
-		prune_xid = relation->rd_rel->relfrozenxid;
+	if (TransactionIdPrecedes(TransactionXmin, relfrozenxid))
+		prune_xid = relfrozenxid;
 	else
 		prune_xid = TransactionXmin;
 	PageSetPrunable(page, prune_xid);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 9befe012a9e..26fce0c4b83 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -593,7 +593,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
@@ -645,7 +645,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(RelationGetSmgr(rel), dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 05221cc1d6d..984e08b4a78 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -63,6 +63,7 @@
 #include "access/xlog.h"
 #include "catalog/index.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -507,6 +508,25 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 	TransactionId OldestXmin;
 	TransactionId FreezeLimit;
 	MultiXactId MultiXactCutoff;
+	TransactionId	relfrozenxid = InvalidTransactionId;
+	MultiXactId		relminmxid = InvalidMultiXactId;
+	double			reltuples = 0;
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		get_gtt_relstats(RelationGetRelid(rel),
+						NULL,
+						&reltuples,
+						NULL,
+						&relfrozenxid,
+						&relminmxid);
+	}
+	else
+	{
+		relfrozenxid = rel->rd_rel->relfrozenxid;
+		relminmxid = rel->rd_rel->relminmxid;
+		reltuples = rel->rd_rel->reltuples;
+	}
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
@@ -542,9 +562,9 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 	 * table's minimum MultiXactId is older than or equal to the requested
 	 * mxid full-table scan limit; or if DISABLE_PAGE_SKIPPING was specified.
 	 */
-	aggressive = TransactionIdPrecedesOrEquals(rel->rd_rel->relfrozenxid,
+	aggressive = TransactionIdPrecedesOrEquals(relfrozenxid,
 											   xidFullScanLimit);
-	aggressive |= MultiXactIdPrecedesOrEquals(rel->rd_rel->relminmxid,
+	aggressive |= MultiXactIdPrecedesOrEquals(relminmxid,
 											  mxactFullScanLimit);
 	if (params->options & VACOPT_DISABLE_PAGE_SKIPPING)
 		aggressive = true;
@@ -591,9 +611,9 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 	}
 
 	vacrel->bstrategy = bstrategy;
-	vacrel->relfrozenxid = rel->rd_rel->relfrozenxid;
-	vacrel->relminmxid = rel->rd_rel->relminmxid;
-	vacrel->old_live_tuples = rel->rd_rel->reltuples;
+	vacrel->relfrozenxid = relfrozenxid;
+	vacrel->relminmxid = relminmxid;
+	vacrel->old_live_tuples = reltuples;
 
 	/* Set cutoffs for entire VACUUM */
 	vacrel->OldestXmin = OldestXmin;
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 5bc7c3616a9..0b261f723d7 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -677,6 +678,14 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		/*
+		 * If a global temporary table storage file is not initialized in the
+		 * current backend, its index does not have a root page, just returns 0.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 4e6efda97f3..ae12fd91a1b 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -44,6 +44,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index aa7d4d5456b..595cb03eb4a 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -504,6 +504,7 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 81cc39fb70e..0bf05f37a80 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -62,6 +62,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,
@@ -366,13 +368,26 @@ heap_create(const char *relname,
 			break;
 	}
 
+	/* For global temporary table, even if the storage is not initialized,
+	 * the relfilenode needs to be generated and put into the catalog.
+	 */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		create_storage = false;
+		if (OidIsValid(relfilenode))
+			elog(ERROR, "global temporary table can not reuse an existing relfilenode");
+
+		relfilenode = relid;
+	}
 	/*
 	 * Decide whether to create storage. If caller passed a valid relfilenode,
 	 * storage is already created, so don't do it here.  Also don't create it
 	 * for relkinds without physical storage.
 	 */
-	if (!RELKIND_HAS_STORAGE(relkind) || OidIsValid(relfilenode))
+	else if (!RELKIND_HAS_STORAGE(relkind) || OidIsValid(relfilenode))
+	{
 		create_storage = false;
+	}
 	else
 	{
 		create_storage = true;
@@ -427,7 +442,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:
@@ -445,7 +460,8 @@ heap_create(const char *relname,
 	 * protected by the existence of a physical file; but for relations with
 	 * no files, add a pg_shdepend entry to account for that.
 	 */
-	if (!create_storage && reltablespace != InvalidOid)
+	if (!create_storage && reltablespace != InvalidOid &&
+		relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
 		recordDependencyOnTablespace(RelationRelationId, relid,
 									 reltablespace);
 
@@ -1001,6 +1017,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -1039,8 +1056,21 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/*
+	 * The transaction information of the global temporary table is stored
+	 * in the local hash table, not in catalog.
+	 */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1413,6 +1443,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1498,8 +1529,9 @@ heap_create_with_catalog(const char *relname,
 	/*
 	 * If there's a special on-commit action, remember it
 	 */
-	if (oncommit != ONCOMMIT_NOOP)
-		register_on_commit_action(relid, oncommit);
+	if (oncommit != ONCOMMIT_NOOP &&
+		relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+		register_on_commit_action(relid, oncommit, false);
 
 	/*
 	 * ok, the relation has been cataloged, so close our relations and return
@@ -1996,6 +2028,19 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/*
+	 * Only when other sessions are not using this Global temporary table,
+	 * is it allowed to DROP it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot drop global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3280,7 +3325,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3292,7 +3337,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3328,7 +3373,7 @@ RelationTruncateIndexes(Relation heapRelation)
  * ON COMMIT truncation of temporary tables, where it doesn't matter.
  */
 void
-heap_truncate(List *relids)
+heap_truncate(List *relids, bool is_global_temp)
 {
 	List	   *relations = NIL;
 	ListCell   *cell;
@@ -3338,8 +3383,23 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode;
+
+		/*
+		 * Truncate global temporary table only clears local data,
+		 * so only low-level locks need to be held.
+		 */
+		if (is_global_temp)
+		{
+			if (!gtt_storage_attached(rid))
+				continue;
 
-		rel = table_open(rid, AccessExclusiveLock);
+			lockmode = RowExclusiveLock;
+		}
+		else
+			lockmode = AccessExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3372,6 +3432,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3380,23 +3441,39 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	/*
+	 * Truncate GTT only clears local data, so only low-level locks
+	 * need to be held.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		lockmode = AccessShareLock;
+	else
+		lockmode = AccessExclusiveLock;
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	/*
+	 * After the data is cleaned up on the GTT, the transaction information
+	 * for the data(stored in local hash table) is also need reset.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(RelationGetRelid(rel), 0, 0, 0, RecentXmin, GetOldestMultiXactId());
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 26bfa74ce75..c32b45f9673 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -54,6 +54,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"
@@ -733,6 +734,25 @@ index_create(Relation heapRelation,
 	TransactionId relfrozenxid;
 	MultiXactId relminmxid;
 
+	/* For global temporary table only */
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temporary table with concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot reindex global temporary tables concurrently")));
+
+		/*
+		 * For the case that some backend is applied relcache message to create
+		 * an index on a global temporary table, if this table in the current
+		 * backend are not initialized, the creation of index storage on the
+		 * table are also skipped.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/* constraint flags can only be set when a constraint is requested */
 	Assert((constr_flags == 0) ||
 		   ((flags & INDEX_CREATE_ADD_CONSTRAINT) != 0));
@@ -2107,7 +2127,7 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 * lock (see comments in RemoveRelations), and a non-concurrent DROP is
 	 * more efficient.
 	 */
-	Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+	Assert(!RelpersistenceTsTemp(get_rel_persistence(indexId)) ||
 		   (!concurrent && !concurrent_lock_mode));
 
 	/*
@@ -2139,6 +2159,21 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/*
+	 * Allow to drop index on global temporary table when only current
+	 * backend use it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation) &&
+		is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+	{
+		ereport(ERROR,
+			(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+			 errmsg("cannot drop index %s on global temporary table %s",
+					RelationGetRelationName(userIndexRelation), RelationGetRelationName(userHeapRelation)),
+					errdetail("Because the index is created on the global temporary table and other backend attached it."),
+					errhint("Please try detach all sessions using this temporary table and try again.")));
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2747,6 +2782,7 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = RELATION_IS_GLOBAL_TEMP(rel);
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2841,20 +2877,37 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
-		{
-			rd_rel->relpages = (int32) relpages;
-			dirty = true;
-		}
-		if (rd_rel->reltuples != (float4) reltuples)
+		/* For global temporary table */
+		if (is_gtt)
 		{
-			rd_rel->reltuples = (float4) reltuples;
-			dirty = true;
+			/* Update GTT'statistics into local relcache */
+			rel->rd_rel->relpages = (int32) relpages;
+			rel->rd_rel->reltuples = (float4) reltuples;
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+
+			/* Update GTT'statistics into local hashtable */
+			up_gtt_relstats(RelationGetRelid(rel), relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+		else
 		{
-			rd_rel->relallvisible = (int32) relallvisible;
-			dirty = true;
+			if (rd_rel->relpages != (int32) relpages)
+			{
+				rd_rel->relpages = (int32) relpages;
+				dirty = true;
+			}
+
+			if (rd_rel->reltuples != (float4) reltuples)
+			{
+				rd_rel->reltuples = (float4) reltuples;
+				dirty = true;
+			}
+
+			if (rd_rel->relallvisible != (int32) relallvisible)
+			{
+				rd_rel->relallvisible = (int32) relallvisible;
+				dirty = true;
+			}
 		}
 	}
 
@@ -2967,6 +3020,26 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, progress_index, progress_vals);
 	}
 
+	/* For build index on global temporary table */
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		/*
+		 * If the storage for the index in this session is not initialized,
+		 * it needs to be created.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			/* Before create init storage, fix the local Relcache first */
+			force_enable_gtt_index(indexRelation);
+
+			Assert(gtt_storage_attached(RelationGetRelid(heapRelation)));
+
+			/* Init storage for index */
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3508,6 +3581,8 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	PGRUsage	ru0;
 	bool		progress = ((params->options & REINDEXOPT_REPORT_PROGRESS) != 0);
 	bool		set_tablespace = false;
+	LOCKMODE	lockmode_on_heap = ShareLock;
+	LOCKMODE	lockmode_on_index = AccessExclusiveLock;
 
 	pg_rusage_init(&ru0);
 
@@ -3521,10 +3596,29 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	if (!OidIsValid(heapId))
 		return;
 
+	/*
+	 * For reindex on global temporary table, If the storage for the index
+	 * in current backend is not initialized, nothing is done.
+	 */
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		if (!gtt_storage_attached(indexId))
+		{
+			/* Suppress use of the target index while rebuilding it */
+			SetReindexProcessing(heapId, indexId);
+			/* Re-allow use of target index */
+			ResetReindexProcessing();
+			return;
+		}
+
+		lockmode_on_heap = AccessShareLock;
+		lockmode_on_index = AccessShareLock;
+	}
+
 	if ((params->options & REINDEXOPT_MISSING_OK) != 0)
-		heapRelation = try_table_open(heapId, ShareLock);
+		heapRelation = try_table_open(heapId, lockmode_on_heap);
 	else
-		heapRelation = table_open(heapId, ShareLock);
+		heapRelation = table_open(heapId, lockmode_on_heap);
 
 	/* if relation is gone, leave */
 	if (!heapRelation)
@@ -3550,7 +3644,7 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	 * Open the target index relation and get an exclusive lock on it, to
 	 * ensure that no one else is touching this particular index.
 	 */
-	iRel = index_open(indexId, AccessExclusiveLock);
+	iRel = index_open(indexId, lockmode_on_index);
 
 	if (progress)
 		pgstat_progress_update_param(PROGRESS_CREATEIDX_ACCESS_METHOD_OID,
@@ -3801,6 +3895,12 @@ reindex_relation(Oid relid, int flags, ReindexParams *params)
 	bool		result;
 	ListCell   *indexId;
 	int			i;
+	LOCKMODE	lockmode;
+
+	if (flags & REINDEX_REL_PROCESS_GLOBAL_TEMP)
+		lockmode = AccessShareLock;
+	else
+		lockmode = ShareLock;
 
 	/*
 	 * Open and lock the relation.  ShareLock is sufficient since we only need
@@ -3808,9 +3908,9 @@ reindex_relation(Oid relid, int flags, ReindexParams *params)
 	 * should match ReindexTable().
 	 */
 	if ((params->options & REINDEXOPT_MISSING_OK) != 0)
-		rel = try_table_open(relid, ShareLock);
+		rel = try_table_open(relid, lockmode);
 	else
-		rel = table_open(relid, ShareLock);
+		rel = table_open(relid, lockmode);
 
 	/* if relation is gone, leave */
 	if (!rel)
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 4de8400fd0f..fe3fcc712cb 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -656,6 +656,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/* Do not allow create global temporary table in temporary schemas */
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp table in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index c5ad28d71fe..707068a6fd8 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,6 +27,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -61,6 +62,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			temprelOid;			/* InvalidOid if not a global temporary rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -115,7 +117,7 @@ AddPendingSync(const RelFileNode *rnode)
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -126,7 +128,12 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/*
+		 * Global temporary table and local temporary table use same
+		 * design on storage module.
+		 */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -154,6 +161,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
@@ -165,6 +173,21 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		AddPendingSync(&rnode);
 	}
 
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		Assert(rel && RELATION_IS_GLOBAL_TEMP(rel));
+
+		/*
+		 * Remember the reloid of global temporary table, which is used for
+		 * transaction commit or rollback.
+		 * see smgrDoPendingDeletes.
+		 */
+		pending->temprelOid = RelationGetRelid(rel);
+
+		/* Remember global temporary table storage info to localhash */
+		remember_gtt_storage_info(rnode, rel);
+	}
+
 	return srel;
 }
 
@@ -201,11 +224,20 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/*
+	 * Remember the reloid of global temporary table, which is used for
+	 * transaction commit or rollback.
+	 * see smgrDoPendingDeletes.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->temprelOid = RelationGetRelid(rel);
+
 	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
@@ -618,6 +650,7 @@ smgrDoPendingDeletes(bool isCommit)
 	int			nrels = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -647,14 +680,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->temprelOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -664,12 +701,21 @@ smgrDoPendingDeletes(bool isCommit)
 
 	if (nrels > 0)
 	{
+		int	i;
+
 		smgrdounlinkall(srels, nrels, false);
 
-		for (int i = 0; i < nrels; i++)
+		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* Delete global temporary table info in localhash */
+			if (gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 00000000000..17b84dba29c
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1519 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  The body implementation of Global Temparary table.
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *	  See src/backend/catalog/GTT_README for Global temparary table's
+ *	  requirements and design.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_info_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+
+int		vacuum_gtt_defer_check_age = 0;
+
+/*
+ * The Global temporary table's shared hash table data structure
+ */
+typedef struct gtt_ctl_data
+{
+	LWLock		lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+/* record this global temporary table in which backends are being used */
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+/*
+ * The Global temporary table's local hash table data structure
+ */
+/* Record the storage information and statistical information of the global temporary table */
+typedef struct
+{
+	Oid			relfilenode;
+	Oid			spcnode;
+
+	/* pg_class relstat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+
+	/* pg_statistic column stat */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_free_statistics(gtt_relfilenode *rnode);
+static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok);
+static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok);
+static Bitmapset *copy_active_gtt_bitmap(Oid relid);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+/*
+ * Calculate shared hash table entry size for GTT.
+ */
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	/* hash entry header size */
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	/*
+	 * hash entry data size
+	 * this is a bitmap in shared memory, each backend have a bit.
+	 */
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+/*
+ * Calculate shared hash table max size for GTT.
+ */
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	/* shared hash header size */
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	/* hash entry size */
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	/* max size */
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+/*
+ * Initialization shared hash table for GTT.
+ */
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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();
+	}
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(gtt_fnode);
+	info.entrysize = action_gtt_shared_hash_entry_size();
+	active_gtt_shared_hash =
+		ShmemInitHash("active gtt shared hash",
+						gtt_shared_ctl->max_entry,
+						gtt_shared_ctl->max_entry,
+						&info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+/*
+ * Record GTT relid to shared hash table, which means that current backend is using this GTT.
+ */
+static void
+gtt_storage_checkin(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			found;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *)&(fnode), 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_global_temporary_table.")));
+	}
+
+	if (!found)
+	{
+		int			wordnum;
+
+		/* init bitmap */
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	/* record itself in bitmap */
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+/*
+ * Remove the GTT relid record from the shared hash table which means that current backend is
+ * not use this GTT.
+ */
+static void
+gtt_storage_checkout(Oid relid, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		if (isCommit)
+			elog(WARNING, "relid %u not exist in gtt shared hash when drop local storage", relid);
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+	/* remove itself from bitmap */
+	bms_del_member(entry->map, MyBackendId);
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return;
+}
+
+/*
+ * Gets usage information for a GTT from shared hash table.
+ * The information is in the form of bitmap.
+ * Quickly copy the entire bitmap from shared memory and return it.
+ * that to avoid holding locks for a long time.
+ */
+static Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	Bitmapset		*map_copy = NULL;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		return NULL;
+	}
+
+	Assert(entry->map);
+
+	/* copy the entire bitmap */
+	if (!bms_is_empty(entry->map))
+		map_copy = bms_copy(entry->map);
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return map_copy;
+}
+
+/*
+ * Check if there are other backends using this GTT besides the current backend.
+ */
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			in_use = false;
+	int			num_use = 0;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		return false;
+	}
+
+	Assert(entry->map);
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+	/* how many backend are using this GTT */
+	num_use = bms_num_members(entry->map);
+	if (num_use == 0)
+		in_use = false;
+	else if (num_use == 1)
+	{
+		/* check if this is itself */
+		if(bms_is_member(MyBackendId, entry->map))
+			in_use = false;
+		else
+			in_use = true;
+	}
+	else
+		in_use = true;
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return in_use;
+}
+
+/*
+ * Record GTT information to local hash.
+ * They include GTT storage info, transaction info and statistical info.
+ */
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+	gtt_local_hash_entry		*entry;
+	MemoryContext			oldcontext;
+	gtt_relfilenode			*new_node = NULL;
+	Oid				relid = RelationGetRelid(rel);
+	int				natts = 0;
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	/* First time through: initialize the hash table */
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		HASHCTL		ctl;
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_info_context =
+			AllocSetContextCreate(CacheMemoryContext,
+								"gtt info context",
+								ALLOCSET_DEFAULT_SIZES);
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		ctl.hcxt = gtt_info_context;
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+	}
+
+	Assert(CacheMemoryContext);
+	Assert(gtt_info_context);
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	entry = gtt_search_by_relid(relid, true);
+	if (!entry)
+	{
+		bool		found = false;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash,
+						(void *) &relid, HASH_ENTER, &found);
+
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			/* record the on commit clause */
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS, true);
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			gtt_storage_checkin(relid);
+		}
+	}
+
+	/* record storage info relstat columnstats and transaction info to relfilenode list */
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	new_node->natts = 0;
+	new_node->attnum = NULL;
+	new_node->att_stat_tups = NULL;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* init column stats structure */
+	natts = RelationGetNumberOfAttributes(rel);
+	new_node->attnum = palloc0(sizeof(int) * natts);
+	new_node->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+	new_node->natts = natts;
+
+	/* only heap rel or toast rel have transaction info */
+	if (entry->relkind == RELKIND_RELATION ||
+		entry->relkind == RELKIND_TOASTVALUE)
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+
+	/* Registration callbacks are used to trigger cleanup during process exit */
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+/*
+ * Remove GTT information from local hash when transaction commit/rollback.
+ */
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	gtt_relfilenode		*d_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+		return;
+	}
+
+	d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+		else
+		{
+			/* rollback transaction */
+			if (entry->relfilenode_list == NIL)
+			{
+				if (entry->relkind == RELKIND_RELATION ||
+					entry->relkind == RELKIND_SEQUENCE)
+					gtt_storage_checkout(relid, isCommit);
+
+				hash_search(gtt_storage_local_hash,
+						(void *) &(relid), HASH_REMOVE, NULL);
+			}
+
+			return;
+		}
+	}
+
+	/* Clean up transaction info from Local order list and MyProc */
+	if (entry->relkind == RELKIND_RELATION ||
+		entry->relkind == RELKIND_TOASTVALUE)
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+
+		/* this is valid relfrozenxid */
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	/* delete relfilenode from rel entry */
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	gtt_free_statistics(d_rnode);
+
+	if (entry->relfilenode_list == NIL)
+	{
+		/* tell shared hash that current backend will no longer use this GTT */
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(relid, isCommit);
+
+		hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_REMOVE, NULL);
+	}
+
+	return;
+}
+
+/*
+ * Check if current backend is using this GTT.
+ */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool			found = false;
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (!OidIsValid(relid))
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry)
+		found = true;
+
+	return found;
+}
+
+/*
+ * When backend exit, bulk cleaning all GTT storage and local buffer of this backend.
+ */
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS			status;
+	gtt_local_hash_entry	*entry;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	/* Need to ensure we have a usable transaction. */
+	AbortOutOfAnyTransaction();
+
+	/* Search all relfilenode for GTT in current backend */
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel[1];
+			RelFileNode		rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel[0] = smgropen(rnode, MyBackendId);
+			smgrdounlinkall(srel, 1, false);
+			smgrclose(srel[0]);
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(entry->relid, false);
+
+		hash_search(gtt_storage_local_hash, (void *) &(entry->relid), HASH_REMOVE, NULL);
+	}
+
+	/* set to global area */
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;
+
+	return;
+}
+
+/*
+ * Update GTT relstats(relpage/reltuple/relallvisible)
+ * to local hash.
+ */
+void
+up_gtt_relstats(Oid relid,
+					BlockNumber num_pages,
+					double num_tuples,
+					BlockNumber num_all_visible_pages,
+					TransactionId relfrozenxid,
+					TransactionId relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (!OidIsValid(relid))
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (num_pages > 0 &&
+		gtt_rnode->relpages != (int32)num_pages)
+		gtt_rnode->relpages = (int32)num_pages;
+
+	if (num_tuples > 0 &&
+		gtt_rnode->reltuples != (float4)num_tuples)
+		gtt_rnode->reltuples = (float4)num_tuples;
+
+	/* only heap contain transaction information and relallvisible */
+	if (entry->relkind == RELKIND_RELATION ||
+		entry->relkind == RELKIND_TOASTVALUE)
+	{
+		if (num_all_visible_pages > 0 &&
+			gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+		{
+			gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+		}
+
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNextTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			/* set to local order list */
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			/* set to global area */
+			set_gtt_session_relfrozenxid();
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search GTT relstats(relpage/reltuple/relallvisible)
+ * from local has.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update GTT info(definition is same as pg_statistic)
+ * to local hash.
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+	MemoryContext		oldcontext;
+	bool		found = false;
+	int			i = 0;
+
+	/* not support whole row or system column */
+	if (attnum <= 0)
+		return;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return;
+
+	Assert(entry->relid == reloid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	/* switch context to gtt_info_context for store tuple at heap_form_tuple */
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == 0)
+		{
+			Assert(gtt_rnode->att_stat_tups[i] == NULL);
+			gtt_rnode->attnum[i] = attnum;
+			gtt_rnode->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+			found = true;
+			break;
+		}
+		else if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			heap_freetuple(gtt_rnode->att_stat_tups[i]);
+			gtt_rnode->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+			found = true;
+			break;
+		}
+	}
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!found)
+		elog(WARNING, "analyze can not update relid %u column %d statistics after add or drop column, try truncate table first", reloid, attnum);
+
+	return;
+}
+
+/*
+ * Search GTT statistic info(definition is same as pg_statistic)
+ * from local hash.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	int			i = 0;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	/* not support whole row or system column */
+	if (attnum <= 0)
+		return NULL;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return NULL;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return NULL;
+
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			return gtt_rnode->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Insert a RelfrozenXID into the list and keep the list in order.
+ */
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int		i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Remove a RelfrozenXID from order list gtt_session_relfrozenxid_list.
+ */
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+/*
+ * Update of backend Level oldest relfrozenxid to MyProc.
+ * This makes each backend's oldest RelFrozenxID globally visible.
+ */
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list != NIL)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	if (MyProc->backend_gtt_frozenxid != gtt_frozenxid)
+		MyProc->backend_gtt_frozenxid = gtt_frozenxid;
+}
+
+/*
+ * Get GTT column level data statistics.
+ */
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate		*tupstore;
+	HeapTuple		tuple;
+	Relation		rel = NULL;
+	Oid			reloid = PG_GETARG_OID(0);
+	int			attnum = PG_GETARG_INT32(1);
+	char			rel_persistence;
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	/* get data from local hash */
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum		values[31];
+		bool		isnull[31];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, NoLock);
+	relation_close(pg_tatistic, AccessShareLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get GTT table level data statistics.
+ */
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate	*tupstore;
+	TupleDesc	tupdesc;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid			reloid = PG_GETARG_OID(0);
+	Oid			relnode = 0;
+	char			rel_persistence;
+	BlockNumber		relpages = 0;
+	BlockNumber		relallvisible = 0;
+	uint32			relfrozenxid = 0;
+	uint32			relminmxid = 0;
+	double			reltuples = 0;
+	Relation		rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[6];
+		bool	isnull[6];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get a list of backend pids that are currently using this GTT.
+ */
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	PGPROC			*proc = NULL;
+	Bitmapset		*map = NULL;
+	Tuplestorestate		*tupstore;
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	HeapTuple		tuple;
+	Oid			reloid = PG_GETARG_OID(0);
+	char			rel_persistence;
+	Relation		rel = NULL;
+	pid_t			pid = 0;
+	int				backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	/* get data from share hash */
+	map = copy_active_gtt_bitmap(reloid);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			/* backendid map to process pid */
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get backend level oldest relfrozenxid of each backend using GTT in current database.
+ */
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate		*tupstore;
+	int			*pids = NULL;
+	uint32			*xids = NULL;
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	HeapTuple		tuple;
+	int			num_xid = MaxBackends + 1;
+	int			i = 0;
+	int			j = 0;
+	uint32			oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+
+	/* Get backend level oldest relfrozenxid in all backend that in MyDatabaseId use GTT */
+	oldest = list_all_backend_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+/*
+ * In order to build the GTT index, force enable GTT'index.
+ */
+void
+force_enable_gtt_index(Relation index)
+{
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(RelationGetRelid(index)));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+/*
+ * Fix the local state of the GTT's index.
+ */
+void
+gtt_fix_index_backend_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid heapOid = index->rd_index->indrelid;
+
+	/* Must be GTT */
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	/*
+	 * If this GTT is not initialized in the current backend,
+	 * its index status is temporarily set to invalid(local relcache).
+	 */
+	if (gtt_storage_attached(heapOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+/*
+ * During the SQL initialization of the executor (InitPlan)
+ * Initialize storage of GTT GTT'indexes and build empty index.
+ */
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int		i;
+	Oid		toastrelid;
+
+	if (!(operation == CMD_UTILITY || operation == CMD_INSERT))
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	/* Each GTT is initialized once in each backend */
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	/* init heap storage */
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	/* rebuild all local index for global temp table */
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		index_build(relation, index, info, true, false);
+
+		/* after build index, index re-enabled */
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+		Assert(info->ii_ReadyForInserts);
+	}
+
+	/* rebuild index for global temp toast table */
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+
+		/* init index storage */
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid			indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+			/* build empty index */
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			Assert(currentIndex->rd_index->indisvalid);
+			Assert(currentIndex->rd_index->indislive);
+			Assert(currentIndex->rd_index->indisready);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+/*
+ * Release the data structure memory used to store GTT storage info.
+ */
+static void
+gtt_free_statistics(gtt_relfilenode *rnode)
+{
+	int i;
+
+	Assert(rnode);
+
+	for (i = 0; i < rnode->natts; i++)
+	{
+		if (rnode->att_stat_tups[i])
+		{
+			heap_freetuple(rnode->att_stat_tups[i]);
+			rnode->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (rnode->attnum)
+		pfree(rnode->attnum);
+
+	if (rnode->att_stat_tups)
+		pfree(rnode->att_stat_tups);
+
+	pfree(rnode);
+
+	return;
+}
+
+/*
+ * Get the current relfilenode of this GTT.
+ */
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
+/*
+ * Get a relfilenode used by this GTT during the transaction life cycle.
+ */
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok)
+{
+	gtt_relfilenode		*rnode = NULL;
+	ListCell		*lc;
+
+	Assert(entry);
+
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == relfilenode)
+		{
+			rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (!missing_ok && rnode == NULL)
+		elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+	return rnode;
+}
+
+/*
+ * Get one GTT info from local hash.
+ */
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry == NULL && !missing_ok)
+		elog(ERROR, "relid %u not found in local hash", relid);
+
+	return entry;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index eb560955cda..abfd0f6e03f 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 4928702aec0..fc6e64a79b6 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -104,7 +105,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -185,6 +186,17 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -601,14 +613,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]));
 		}
 
 		/*
@@ -1622,7 +1635,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;
@@ -1724,31 +1737,48 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/*
+		 * For global temporary table,
+		 * Update column statistic to localhash, not catalog.
+		 */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 9d22f648a84..a44eefa1f6b 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/toasting.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
@@ -390,6 +391,22 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+	{
+		if (gtt_storage_attached(RelationGetRelid(OldHeap)))
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("not support cluster global temporary table yet")));
+
+		relation_close(OldHeap, AccessExclusiveLock);
+		pgstat_progress_end_command();
+		return;
+	}
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
@@ -585,6 +602,8 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
 	TransactionId frozenXid;
 	MultiXactId cutoffMulti;
 
+	Assert(!RELATION_IS_GLOBAL_TEMP(OldHeap));
+
 	/* Mark the correct index as clustered */
 	if (OidIsValid(indexOid))
 		mark_index_clustered(OldHeap, indexOid, true);
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 53f48531419..c03191cce94 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -289,7 +289,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP_ON_CURRENT_SESSION(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, whereClause,
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index f366a818a14..56269b6e38f 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -30,6 +30,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/copyfrom_internal.h"
 #include "commands/progress.h"
@@ -659,6 +660,9 @@ CopyFrom(CopyFromState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	/* Check and init global temporary table storage in current backend */
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	/*
 	 * Set up a ModifyTableState so we can let FDW(s) init themselves for
 	 * foreign-table result relation(s).
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index c14ca27c5ed..d65ae895356 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -111,6 +111,7 @@ struct ReindexIndexCallbackState
 {
 	ReindexParams params;		/* options from statement */
 	Oid			locked_table_oid;	/* tracks previously locked table */
+	LOCKMODE	lockmode;
 };
 
 /*
@@ -570,7 +571,7 @@ DefineIndex(Oid relationId,
 	 * is more efficient.  Do this before any use of the concurrent option is
 	 * done.
 	 */
-	if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+	if (stmt->concurrent && !RelpersistenceTsTemp(get_rel_persistence(relationId)))
 		concurrent = true;
 	else
 		concurrent = false;
@@ -2581,9 +2582,9 @@ ReindexIndex(RangeVar *indexRelation, ReindexParams *params, bool isTopLevel)
 	 */
 	state.params = *params;
 	state.locked_table_oid = InvalidOid;
+	state.lockmode = AccessShareLock;
 	indOid = RangeVarGetRelidExtended(indexRelation,
-									  (params->options & REINDEXOPT_CONCURRENTLY) != 0 ?
-									  ShareUpdateExclusiveLock : AccessExclusiveLock,
+									  AccessShareLock,
 									  0,
 									  RangeVarCallbackForReindexIndex,
 									  &state);
@@ -2594,11 +2595,25 @@ ReindexIndex(RangeVar *indexRelation, ReindexParams *params, bool isTopLevel)
 	 */
 	persistence = get_rel_persistence(indOid);
 	relkind = get_rel_relkind(indOid);
+	if (persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		LOCKMODE	lockmode;
+
+		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0)
+			lockmode = ShareUpdateExclusiveLock;
+		else
+			lockmode = AccessExclusiveLock;
+
+		/* lock heap first */
+		Assert(OidIsValid(state.locked_table_oid));
+		LockRelationOid(state.locked_table_oid, lockmode);
+		LockRelationOid(indOid, lockmode);
+	}
 
 	if (relkind == RELKIND_PARTITIONED_INDEX)
 		ReindexPartitions(indOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 persistence != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(persistence))
 		ReindexRelationConcurrently(indOid, params);
 	else
 	{
@@ -2620,15 +2635,7 @@ RangeVarCallbackForReindexIndex(const RangeVar *relation,
 {
 	char		relkind;
 	struct ReindexIndexCallbackState *state = arg;
-	LOCKMODE	table_lockmode;
-
-	/*
-	 * Lock level here should match table lock in reindex_index() for
-	 * non-concurrent case and table locks used by index_concurrently_*() for
-	 * concurrent case.
-	 */
-	table_lockmode = (state->params.options & REINDEXOPT_CONCURRENTLY) != 0 ?
-		ShareUpdateExclusiveLock : ShareLock;
+	LOCKMODE	table_lockmode = state->lockmode;
 
 	/*
 	 * If we previously locked some other index's heap, and the name we're
@@ -2689,6 +2696,8 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 {
 	Oid			heapOid;
 	bool		result;
+	char		relpersistence;
+	int 		reindex_flags = 0;
 
 	/*
 	 * The lock level used here should match reindex_relation().
@@ -2699,15 +2708,27 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 	 * locks on our temporary table.
 	 */
 	heapOid = RangeVarGetRelidExtended(relation,
-									   (params->options & REINDEXOPT_CONCURRENTLY) != 0 ?
-									   ShareUpdateExclusiveLock : ShareLock,
+									   AccessShareLock,
 									   0,
 									   RangeVarCallbackOwnsTable, NULL);
 
+	relpersistence = get_rel_persistence(heapOid);
+	if (relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		LOCKMODE	lockmode;
+
+		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0)
+			lockmode = ShareUpdateExclusiveLock;
+		else
+			lockmode = ShareLock;
+
+		LockRelationOid(heapOid, lockmode);
+	}
+
 	if (get_rel_relkind(heapOid) == RELKIND_PARTITIONED_TABLE)
 		ReindexPartitions(heapOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(relpersistence))
 	{
 		result = ReindexRelationConcurrently(heapOid, params);
 
@@ -2721,9 +2742,14 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 		ReindexParams newparams = *params;
 
 		newparams.options |= REINDEXOPT_REPORT_PROGRESS;
+
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			reindex_flags |= REINDEX_REL_PROCESS_GLOBAL_TEMP;
+
+		reindex_flags |= REINDEX_REL_PROCESS_TOAST;
+		reindex_flags |= REINDEX_REL_CHECK_CONSTRAINTS;
 		result = reindex_relation(heapOid,
-								  REINDEX_REL_PROCESS_TOAST |
-								  REINDEX_REL_CHECK_CONSTRAINTS,
+								  reindex_flags,
 								  &newparams);
 		if (!result)
 			ereport(NOTICE,
@@ -3122,7 +3148,7 @@ ReindexMultipleInternal(List *relids, ReindexParams *params)
 			   relkind != RELKIND_PARTITIONED_TABLE);
 
 		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			relpersistence != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(relpersistence))
 		{
 			ReindexParams newparams = *params;
 
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 62465bacd81..ef37f79ba68 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -51,12 +51,32 @@ LockTableCommand(LockStmt *lockstmt)
 		RangeVar   *rv = (RangeVar *) lfirst(p);
 		bool		recurse = rv->inh;
 		Oid			reloid;
+		LOCKMODE	lockmode = lockstmt->mode;
+		char		relpersistence;
 
-		reloid = RangeVarGetRelidExtended(rv, lockstmt->mode,
-										  lockstmt->nowait ? RVR_NOWAIT : 0,
+		reloid = RangeVarGetRelidExtended(rv, NoLock, 0,
 										  RangeVarCallbackForLockTable,
 										  (void *) &lockstmt->mode);
 
+		relpersistence = get_rel_persistence(reloid);
+		if (relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			if (!lockstmt->nowait)
+				LockRelationOid(reloid, lockmode);
+			else if (!ConditionalLockRelationOid(reloid, lockmode))
+			{
+				/* try to throw error by name; relation could be deleted... */
+				char	   *relname = get_rel_name(reloid);
+
+				if (!relname)
+					return;		/* child concurrently dropped, just skip it */
+				ereport(ERROR,
+						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						 errmsg("could not obtain lock on relation \"%s\"",
+								relname)));
+			}
+		}
+
 		if (get_rel_relkind(reloid) == RELKIND_VIEW)
 			LockViewRecurse(reloid, lockstmt->mode, lockstmt->nowait, NIL);
 		else if (recurse)
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 72bfdc07a49..e5257f610f6 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -220,9 +223,12 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	rel = table_open(seqoid, AccessExclusiveLock);
 	tupDesc = RelationGetDescr(rel);
 
-	/* now initialize the sequence's data */
-	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	if (!RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		/* now initialize the sequence's data */
+		tuple = heap_form_tuple(tupDesc, value, null);
+		fill_seq_with_data(rel, tuple);
+	}
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -275,8 +281,6 @@ ResetSequence(Oid seq_relid)
 	Buffer		buf;
 	HeapTupleData seqdatatuple;
 	HeapTuple	tuple;
-	HeapTuple	pgstuple;
-	Form_pg_sequence pgsform;
 	int64		startv;
 
 	/*
@@ -287,12 +291,7 @@ ResetSequence(Oid seq_relid)
 	init_sequence(seq_relid, &elm, &seq_rel);
 	(void) read_seq_tuple(seq_rel, &buf, &seqdatatuple);
 
-	pgstuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seq_relid));
-	if (!HeapTupleIsValid(pgstuple))
-		elog(ERROR, "cache lookup failed for sequence %u", seq_relid);
-	pgsform = (Form_pg_sequence) GETSTRUCT(pgstuple);
-	startv = pgsform->seqstart;
-	ReleaseSysCache(pgstuple);
+	startv = get_seqence_start_value(seq_relid);
 
 	/*
 	 * Copy the existing sequence tuple.
@@ -451,6 +450,15 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 
 	init_sequence(relid, &elm, &seqrel);
 
+	if (RELATION_IS_GLOBAL_TEMP(seqrel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(seqrel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary sequence %s when other backend attached it.",
+						RelationGetRelationName(seqrel))));
+	}
+
 	rel = table_open(SequenceRelationId, RowExclusiveLock);
 	seqtuple = SearchSysCacheCopy1(SEQRELID,
 								   ObjectIdGetDatum(relid));
@@ -611,7 +619,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +944,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1153,6 +1161,14 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	/* Initializes the storage for sequence which the global temporary table belongs. */
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1927,3 +1943,58 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+/*
+ * Get the startValue of the sequence from syscache.
+ */
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+/*
+ * Initialize sequence which global temporary table belongs.
+ */
+void
+gtt_init_seq(Relation rel)
+{
+	Datum		value[SEQ_COL_LASTCOL];
+	bool		null[SEQ_COL_LASTCOL];
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL - 1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+	null[SEQ_COL_LASTVAL - 1] = false;
+
+	value[SEQ_COL_LOG - 1] = Int64GetDatum((int64)0);
+	null[SEQ_COL_LOG - 1] = false;
+
+	value[SEQ_COL_CALLED - 1] = BoolGetDatum(false);
+	null[SEQ_COL_CALLED - 1] = false;
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 857cc5ce6e2..ef14597b900 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -118,6 +119,7 @@ typedef struct OnCommitItem
 	 */
 	SubTransactionId creating_subid;
 	SubTransactionId deleting_subid;
+	bool			 is_global_temp;
 } OnCommitItem;
 
 static List *on_commits = NIL;
@@ -602,7 +604,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
 static char GetAttributeCompression(Oid atttypid, char *compression);
-
+static OnCommitAction gtt_oncommit_option(List *options);
 
 /* ----------------------------------------------------------------
  *		DefineRelation
@@ -647,6 +649,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -658,7 +661,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * Check consistency of arguments
 	 */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !RelpersistenceTsTemp(stmt->relation->relpersistence))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -688,7 +691,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	if (RelpersistenceTsTemp(stmt->relation->relpersistence)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -789,6 +792,50 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* For global temporary table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		if (!(relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE))
+			elog(ERROR, "Only support global temporary regular table.");
+
+		/* Check parent table */
+		if (inheritOids)
+			elog(ERROR, "Not support global temporary partition table or inherit table.");
+
+		/* Check oncommit clause and save to reloptions */
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temporary table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1415,7 +1462,7 @@ RemoveRelations(DropStmt *drop)
 		 * relation persistence cannot be known without its OID.
 		 */
 		if (drop->concurrent &&
-			get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(get_rel_persistence(relOid)))
 		{
 			Assert(list_length(drop->objects) == 1 &&
 				   drop->removeType == OBJECT_INDEX);
@@ -1624,9 +1671,9 @@ ExecuteTruncate(TruncateStmt *stmt)
 		Relation	rel;
 		bool		recurse = rv->inh;
 		Oid			myrelid;
-		LOCKMODE	lockmode = AccessExclusiveLock;
+		LOCKMODE	lockmode;
 
-		myrelid = RangeVarGetRelidExtended(rv, lockmode,
+		myrelid = RangeVarGetRelidExtended(rv, AccessShareLock,
 										   0, RangeVarCallbackForTruncate,
 										   NULL);
 
@@ -1634,9 +1681,21 @@ ExecuteTruncate(TruncateStmt *stmt)
 		if (list_member_oid(relids, myrelid))
 			continue;
 
-		/* open the relation, we already hold a lock on it */
+		/* open the relation, we need hold a low-level lock first */
 		rel = table_open(myrelid, NoLock);
 
+		/*
+		 * Truncate global temp table only cleans up the data in current backend,
+		 * only low-level locks are required.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel))
+			lockmode = AccessShareLock;
+		else
+		{
+			lockmode = AccessExclusiveLock;
+			LockRelationOid(myrelid, lockmode);
+		}
+
 		/*
 		 * RangeVarGetRelidExtended() has done most checks with its callback,
 		 * but other checks with the now-opened Relation remain.
@@ -1886,6 +1945,7 @@ ExecuteTruncateGuts(List *explicit_rels,
 	foreach(cell, rels)
 	{
 		Relation	rel = (Relation) lfirst(cell);
+		LOCKMODE	lockmode;
 
 		/* Skip partitioned tables as there is nothing to do */
 		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
@@ -1936,6 +1996,19 @@ ExecuteTruncateGuts(List *explicit_rels,
 			continue;
 		}
 
+		/*
+		 * Skip the global temporary table that is not initialized for storage
+		 * in current backend.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel))
+		{
+			lockmode = AccessShareLock;
+			if (!gtt_storage_attached(RelationGetRelid(rel)))
+				continue;
+		}
+		else
+			lockmode = AccessExclusiveLock;
+
 		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
@@ -1954,6 +2027,7 @@ ExecuteTruncateGuts(List *explicit_rels,
 			Oid			heap_relid;
 			Oid			toast_relid;
 			ReindexParams reindex_params = {0};
+			int			reindex_flags = 0;
 
 			/*
 			 * This effectively deletes all rows in the table, and may be done
@@ -1981,17 +2055,21 @@ ExecuteTruncateGuts(List *explicit_rels,
 			if (OidIsValid(toast_relid))
 			{
 				Relation	toastrel = relation_open(toast_relid,
-													 AccessExclusiveLock);
+													 lockmode);
 
 				RelationSetNewRelfilenode(toastrel,
 										  toastrel->rd_rel->relpersistence);
 				table_close(toastrel, NoLock);
 			}
 
+			reindex_flags = REINDEX_REL_PROCESS_TOAST;
+			if (RELATION_IS_GLOBAL_TEMP(rel))
+				reindex_flags |= REINDEX_REL_PROCESS_GLOBAL_TEMP;
+
 			/*
 			 * Reconstruct the indexes to match, and we're done.
 			 */
-			reindex_relation(heap_relid, REINDEX_REL_PROCESS_TOAST,
+			reindex_relation(heap_relid, reindex_flags,
 							 &reindex_params);
 		}
 
@@ -4032,6 +4110,16 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temporary table only current backend use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -5363,6 +5451,24 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 
 			rel = table_open(tab->relid, NoLock);
 			find_composite_type_dependencies(rel->rd_rel->reltype, rel, NULL);
+
+			if (RELATION_IS_GLOBAL_TEMP(rel) && tab->rewrite > 0)
+			{
+				if (tab->chgPersistence)
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot change global temporary table persistence setting")));
+
+				if(gtt_storage_attached(tab->relid))
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("Only support alter global temporary table in an empty context."),
+						 errhint("Please create a new connection and execute ALTER TABLE on the new connection.")));
+
+				/* There is no need to override the whole temp table */
+				tab->rewrite = 0;
+			}
+
 			table_close(rel, NoLock);
 		}
 
@@ -5414,6 +5520,8 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot rewrite temporary tables of other sessions")));
 
+			Assert(!RELATION_IS_GLOBAL_TEMP(OldHeap));
+
 			/*
 			 * Select destination tablespace (same as original unless user
 			 * requested a change)
@@ -9045,6 +9153,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -13082,7 +13196,9 @@ TryReuseIndex(Oid oldId, IndexStmt *stmt)
 		Relation	irel = index_open(oldId, NoLock);
 
 		/* If it's a partitioned index, there is no storage to share. */
-		if (irel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
+		/* multiple global temp table are not allow use same relfilenode */
+		if (irel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX &&
+			!RELATION_IS_GLOBAL_TEMP(irel))
 		{
 			stmt->oldNode = irel->rd_node.relNode;
 			stmt->oldCreateSubid = irel->rd_createSubid;
@@ -13744,6 +13860,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -13943,6 +14062,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temporary table");
+
 	/* Check first if relation can be moved to new tablespace */
 	if (!CheckRelationTableSpaceMove(rel, newTableSpace))
 	{
@@ -14246,7 +14368,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(RelationGetSmgr(rel), dstrel, MAIN_FORKNUM,
@@ -15844,6 +15966,7 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
 	switch (rel->rd_rel->relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -16285,7 +16408,7 @@ AlterSeqNamespaces(Relation classRel, Relation rel,
  * Register a newly-created relation's ON COMMIT action.
  */
 void
-register_on_commit_action(Oid relid, OnCommitAction action)
+register_on_commit_action(Oid relid, OnCommitAction action, bool is_gloal_temp)
 {
 	OnCommitItem *oc;
 	MemoryContext oldcxt;
@@ -16304,6 +16427,7 @@ register_on_commit_action(Oid relid, OnCommitAction action)
 	oc->oncommit = action;
 	oc->creating_subid = GetCurrentSubTransactionId();
 	oc->deleting_subid = InvalidSubTransactionId;
+	oc->is_global_temp = is_gloal_temp;
 
 	/*
 	 * We use lcons() here so that ON COMMIT actions are processed in reverse
@@ -16349,6 +16473,7 @@ PreCommit_on_commit_actions(void)
 	ListCell   *l;
 	List	   *oids_to_truncate = NIL;
 	List	   *oids_to_drop = NIL;
+	List	   *oids_to_truncate_gtt = NIL;
 
 	foreach(l, on_commits)
 	{
@@ -16372,7 +16497,12 @@ PreCommit_on_commit_actions(void)
 				 * tables, as they must still be empty.
 				 */
 				if ((MyXactFlags & XACT_FLAGS_ACCESSEDTEMPNAMESPACE))
-					oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid);
+				{
+					if (oc->is_global_temp)
+						oids_to_truncate_gtt = lappend_oid(oids_to_truncate_gtt, oc->relid);
+					else
+						oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid);
+				}
 				break;
 			case ONCOMMIT_DROP:
 				oids_to_drop = lappend_oid(oids_to_drop, oc->relid);
@@ -16389,7 +16519,10 @@ PreCommit_on_commit_actions(void)
 	 * exists at truncation time.
 	 */
 	if (oids_to_truncate != NIL)
-		heap_truncate(oids_to_truncate);
+		heap_truncate(oids_to_truncate, false);
+
+	if (oids_to_truncate_gtt != NIL)
+		heap_truncate(oids_to_truncate_gtt, true);
 
 	if (oids_to_drop != NIL)
 	{
@@ -17388,6 +17521,13 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot attach temporary relation of another session as partition")));
 
+	/* If the parent is permanent, so must be all of its partitions. */
+	if (attachrel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach a global temporary relation as partition of permanent relation \"%s\"",
+						RelationGetRelationName(rel))));
+
 	/* Check if there are any columns in attachrel that aren't in the parent */
 	tupleDesc = RelationGetDescr(attachrel);
 	natts = tupleDesc->natts;
@@ -18858,3 +18998,40 @@ GetAttributeCompression(Oid atttypid, char *compression)
 
 	return cmethod;
 }
+
+/*
+ * Parse the on commit clause for the temporary table
+ */
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			bool	res = false;
+			char	*sval = defGetString(def);
+
+			/* It has to be a Boolean value */
+			if (!parse_bool(sval, &res))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
+
+			if (res)
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 5c4bc15b441..3a861c47946 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"
@@ -1315,6 +1316,27 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = RELATION_IS_GLOBAL_TEMP(relation);
+
+	 /* For global temporary table */
+	if (is_gtt)
+	{
+		/* Store relation statistics and transaction information to the localhash */
+		up_gtt_relstats(RelationGetRelid(relation),
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+
+		/* Update relation statistics to local relcache */
+		relation->rd_rel->relpages = (int32) num_pages;
+		relation->rd_rel->reltuples = (float4) num_tuples;
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+		if (TransactionIdIsNormal(frozenxid))
+			relation->rd_rel->relfrozenxid = frozenxid;
+
+		if (MultiXactIdIsValid(minmulti))
+			relation->rd_rel->relminmxid = minmulti;
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1328,17 +1350,23 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (!is_gtt &&
+		pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (!is_gtt &&
+		pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (!is_gtt &&
+		pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1351,7 +1379,15 @@ vac_update_relstats(Relation relation,
 		/*
 		 * If we didn't find any indexes, reset relhasindex.
 		 */
-		if (pgcform->relhasindex && !hasindex)
+		if (is_gtt &&
+			RelationGetIndexList(relation) != NIL)
+		{
+			/*
+			 * Global temporary tables may contain indexes that are not valid locally.
+			 * The catalog should not be updated based on local invalid index.
+			 */
+		}
+		else if (pgcform->relhasindex && !hasindex)
 		{
 			pgcform->relhasindex = false;
 			dirty = true;
@@ -1383,7 +1419,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNextTransactionId(),
@@ -1394,7 +1431,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1502,6 +1540,13 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/*
+		 * The relfrozenxid for a global temporary talble is stored in localhash,
+		 * not pg_class, See list_all_session_gtt_frozenxids()
+		 */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1559,6 +1604,43 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/* If enable global temporary table */
+	if (max_active_gtt > 0)
+	{
+		TransactionId	safe_age;
+		/*  */
+		TransactionId	oldest_gtt_frozenxid =
+			list_all_backend_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid))
+		{
+			safe_age = oldest_gtt_frozenxid + vacuum_gtt_defer_check_age;
+			if (safe_age < FirstNormalTransactionId)
+				safe_age += FirstNormalTransactionId;
+
+			/*
+			 * We tolerate that the minimum age of gtt is less than
+			 * the minimum age of conventional tables, otherwise it will
+			 * throw warning message.
+			 */
+			if (TransactionIdIsNormal(safe_age) &&
+				TransactionIdPrecedes(safe_age, newFrozenXid))
+			{
+				ereport(WARNING,
+					(errmsg("global temp table oldest relfrozenxid %u is the oldest in the entire db",
+							oldest_gtt_frozenxid),
+					 errdetail("The oldest relfrozenxid in pg_class is %u", newFrozenXid),
+					 errhint("If they differ greatly, please consider cleaning up the data in global temp table.")));
+			}
+
+			/*
+			 * We need to ensure that the clog required by gtt is not cleand.
+			 */
+			if (TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+				newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1910,6 +1992,19 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	/*
+	 * Skip those global temporary table that are not initialized in
+	 * current backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel) &&
+		!gtt_storage_attached(RelationGetRelid(rel)))
+	{
+		relation_close(rel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 4df05a0b33d..4c181e2e14e 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -527,6 +527,12 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	/* Global temporary table are not sensible. */
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b3ce4bae530..611e3f18a70 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -784,6 +784,10 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		/* This is one kind of temp table */
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 5c723bc54e1..191e0f6fd21 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d328856ae5b..4e2bbb224cc 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -38,6 +38,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -633,6 +634,9 @@ ExecInsert(ModifyTableState *mtstate,
 		resultRelInfo->ri_IndexRelationDescs == NULL)
 		ExecOpenIndices(resultRelInfo, onconflict != ONCONFLICT_NONE);
 
+	/* Init storage for global temporary table in current backend */
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	/*
 	 * BEFORE ROW INSERT Triggers.
 	 *
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 296dd75c1b6..d971aea2546 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -48,7 +48,7 @@
 #include "partitioning/partprune.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/lsyscache.h"
-
+#include "utils/rel.h"
 
 /* results of subquery_is_pushdown_safe */
 typedef struct pushdown_safety_info
@@ -619,7 +619,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			if (RelpersistenceTsTemp(get_rel_persistence(rte->relid)))
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index bd01ec0526f..ff4e81ca2cf 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6071,7 +6071,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
-	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+	if (RELATION_IS_TEMP(heap) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index c5194fdbbf2..38d7c658541 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -30,6 +30,7 @@
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temporary table in current backend */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 146ee8dd1ea..2d4e9393f00 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2907,6 +2907,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 d0eb80e69cb..8dab345aad9 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3417,17 +3417,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; }
@@ -11755,19 +11749,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 c5c3f26ecf1..2a2b2789077 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -82,6 +82,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);
 
 
 /*
@@ -3665,3 +3666,53 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * Like function isQueryUsingTempRelation_walker
+ * return true if any relation underlying
+ * the query is a global temporary table.
+ */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* Check if the query uses global temporary table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 313d7b6ff02..38e0e162e82 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -447,6 +447,13 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	/*
+	 * If a sequence is bound to a global temporary table, then the sequence
+	 * must been "global temporary"
+	 */
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
@@ -3326,6 +3333,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		cxt.isforeign = false;
 	}
 	cxt.relation = stmt->relation;
+	/* Sets the table persistence to the context */
+	cxt.relation->relpersistence = RelationGetRelPersistence(rel);
 	cxt.rel = rel;
 	cxt.inhRelations = NIL;
 	cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 96332320a73..75ed4d0ae9e 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2115,6 +2115,14 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/*
+			 * Aotuvacuum cannot vacuum the private data stored in each backend
+			 * that belongs to global temporary table, so skip them.
+			 */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2181,7 +2189,7 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		if (RelpersistenceTsTemp(classForm->relpersistence))
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 08ebabfe96a..c346c59c7f4 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlogutils.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -2934,6 +2935,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * Returns 0 if this global temporary table is not initialized in current
+	 * backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 9fa3e0631e6..cc3eb928bc6 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -23,6 +23,7 @@
 #include "access/syncscan.h"
 #include "access/twophase.h"
 #include "commands/async.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
@@ -143,6 +144,7 @@ CalculateShmemSize(int *num_semaphores)
 	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
@@ -246,6 +248,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* For global temporary table shared hashtable */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index bd3c7a47fe2..22481374778 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -65,6 +65,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -5131,3 +5132,78 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temporary table.
+ */
+int
+list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct		*arrayP = procArray;
+	TransactionId		result = InvalidTransactionId;
+	int			index;
+	uint8			flags = 0;
+	int			i = 0;
+
+	/* return 0 if feature is disabled */
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+		*n = 0;
+	}
+
+	/* Disable in standby node */
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		uint8           statusFlags = ProcGlobal->statusFlags[index];
+
+		if (statusFlags & flags)
+			continue;
+
+		/* Fetch all backend that is belonging to MyDatabaseId */
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->backend_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->backend_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->backend_gtt_frozenxid, result))
+				result = proc->backend_gtt_frozenxid;
+
+			/* save backend pid and backend level oldest relfrozenxid */
+			if (max_size > 0)
+			{
+				pids[i] = proc->pid;
+				xids[i] = proc->backend_gtt_frozenxid;
+				i++;
+			}
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (max_size > 0)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 862097352bb..4edd3b31f7a 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -176,7 +176,9 @@ static const char *const BuiltinTrancheNames[] = {
 	/* LWTRANCHE_PARALLEL_APPEND: */
 	"ParallelAppend",
 	/* LWTRANCHE_PER_XACT_PREDICATE_LIST: */
-	"PerXactPredicateList"
+	"PerXactPredicateList",
+	/* LWTRANCHE_GTT_CTL */
+	"GlobalTempTableControl"
 };
 
 StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index b7d9da0aa9f..8051f2053f9 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -393,6 +393,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;	/* init backend level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
@@ -578,6 +579,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;	/* init backend level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index d5a7fb13f3c..8225cf6219f 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -982,6 +982,13 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/*
+			 * For global temporary table ,each backend has its own storage,
+			 * also only sees its own storage. Use Backendid to identify them.
+			 */
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 10895fb2876..66255eb7604 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -108,6 +108,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -5115,12 +5116,26 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								/* For global temporary table, get statistic data from localhash */
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -5368,15 +5383,28 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6820,6 +6848,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;
@@ -6837,6 +6866,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6848,6 +6885,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;
@@ -6863,6 +6902,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7781,6 +7828,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))
@@ -7793,6 +7842,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7805,6 +7863,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.
@@ -7824,6 +7884,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 4ebaa552a27..78c33d2ac87 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -3113,6 +3114,19 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		/* For global temporary table, get statistic data from localhash */
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 9fa9e671a11..65b40e58943 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -65,6 +65,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1152,6 +1153,36 @@ retry:
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+				TransactionId	relfrozenxid = InvalidTransactionId;
+				MultiXactId 	relminmxid = InvalidMultiXactId;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+
+				/* For global temporary table, get relstat data from localhash */
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								&relfrozenxid,
+								&relminmxid);
+
+				/* And put them to local relcache */
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+				if (TransactionIdIsNormal(relfrozenxid))
+					relation->rd_rel->relfrozenxid = relfrozenxid;
+
+				if (MultiXactIdIsValid(relminmxid))
+					relation->rd_rel->relminmxid = relminmxid;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1209,6 +1240,8 @@ retry:
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			/* The state of the global temporary table's index may need to be set */
+			gtt_fix_index_backend_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -1351,7 +1384,22 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+			/*
+			 * For global temporary table, get the latest relfilenode
+			 * from localhash and put it in relcache.
+			 */
+			if (OidIsValid(newrelnode) &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2302,6 +2350,9 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		/* The state of the global temporary table's index may need to be set */
+		gtt_fix_index_backend_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3582,6 +3633,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3691,28 +3746,39 @@ void
 RelationSetNewRelfilenode(Relation relation, char persistence)
 {
 	Oid			newrelfilenode;
-	Relation	pg_class;
-	HeapTuple	tuple;
+	Relation	pg_class = NULL;
+	HeapTuple	tuple = NULL;
 	Form_pg_class classform;
 	MultiXactId minmulti = InvalidMultiXactId;
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
 
+	/*
+	 * For global temporary table, storage information for the table is
+	 * maintained locally, not in catalog.
+	 */
+	bool		update_catalog = !RELATION_IS_GLOBAL_TEMP(relation);
+
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
 
-	/*
-	 * Get a writable copy of the pg_class tuple for the given relation.
-	 */
-	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+	memset(&classform, 0, sizeof(classform));
 
-	tuple = SearchSysCacheCopy1(RELOID,
-								ObjectIdGetDatum(RelationGetRelid(relation)));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "could not find tuple for relation %u",
-			 RelationGetRelid(relation));
-	classform = (Form_pg_class) GETSTRUCT(tuple);
+	if (update_catalog)
+	{
+		/*
+		 * Get a writable copy of the pg_class tuple for the given relation.
+		 */
+		pg_class = table_open(RelationRelationId, RowExclusiveLock);
+
+		tuple = SearchSysCacheCopy1(RELOID,
+									ObjectIdGetDatum(RelationGetRelid(relation)));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u",
+				 RelationGetRelid(relation));
+		classform = (Form_pg_class) GETSTRUCT(tuple);
+	}
 
 	/*
 	 * Schedule unlinking of the old storage at transaction commit.
@@ -3738,7 +3804,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;
@@ -3758,6 +3824,18 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 			break;
 	}
 
+	/* For global temporary table */
+	if (!update_catalog)
+	{
+		Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+		Assert(RELATION_IS_GLOBAL_TEMP(relation));
+		Assert(!RelationIsMapped(relation));
+
+		/* Make cache invalid and set new relnode to local cache. */
+		CacheInvalidateRelcache(relation);
+		relation->rd_node.relNode = relnode;
+	}
 	/*
 	 * If we're dealing with a mapped index, pg_class.relfilenode doesn't
 	 * change; instead we have to send the update to the relation mapper.
@@ -3767,7 +3845,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	 * possibly-inaccurate values of relpages etc, but those will be fixed up
 	 * later.
 	 */
-	if (RelationIsMapped(relation))
+	else if (RelationIsMapped(relation))
 	{
 		/* This case is only supported for indexes */
 		Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3813,9 +3891,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 		CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 	}
 
-	heap_freetuple(tuple);
+	if (update_catalog)
+	{
+		heap_freetuple(tuple);
 
-	table_close(pg_class, RowExclusiveLock);
+		table_close(pg_class, RowExclusiveLock);
+	}
 
 	/*
 	 * Make the pg_class row change or relation map change visible.  This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index e91d5a3cfda..b8ec6b3ca46 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -44,6 +44,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "commands/prepare.h"
 #include "commands/trigger.h"
@@ -153,6 +154,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temporary table feature.
+ * table schema are still saved in catalog.
+ *
+ * num > 0 means allows the database to manage multiple active tables at the same time.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2128,6 +2141,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 "
@@ -2698,6 +2720,16 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"vacuum_gtt_defer_check_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("The defer check age of GTT, used to check expired data after vacuum."),
+			NULL
+		},
+		&vacuum_gtt_defer_check_age,
+		10000, 0, 1000000,
+		NULL, NULL, NULL
+	},
+
 	/*
 	 * See also CheckRequiredParameterValues() if this parameter changes
 	 */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index d1842edde0d..7d706307297 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2504,6 +2504,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
 		dopt->no_unlogged_table_data)
 		return;
 
+	/* Don't dump data in global temporary table/sequence */
+	if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		return;
+
 	/* Check that the data is not explicitly excluded */
 	if (simple_oid_list_member(&tabledata_exclude_oids,
 							   tbinfo->dobj.catId.oid))
@@ -15815,6 +15819,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
 		char	   *foreign = "";
+		char	   *table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15868,9 +15873,15 @@ dumpTableSchema(Archive *fout, const 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);
 
@@ -16234,6 +16245,15 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 			}
 		}
 
+		/*
+		 * Transaction information for the global temporary table is not stored
+		 * in the pg_class.
+		 */
+		if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			Assert(tbinfo->frozenxid == 0);
+			Assert(tbinfo->minmxid == 0);
+		}
 		/*
 		 * In binary_upgrade mode, arrange to restore the old relfrozenxid and
 		 * relminmxid of all vacuumable relations.  (While vacuum.c processes
@@ -16241,7 +16261,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		 * of other relations; so this "if" lacks RELKIND_TOASTVALUE, and the
 		 * child toast table is handled below.)
 		 */
-		if (dopt->binary_upgrade &&
+		else if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
 			 tbinfo->relkind == RELKIND_MATVIEW))
 		{
@@ -17244,6 +17264,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	PQExpBuffer query = createPQExpBuffer();
 	PQExpBuffer delqry = createPQExpBuffer();
 	char	   *qseqname;
+	bool	   global_temp_seq = false;
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
@@ -17253,9 +17274,12 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
-						  "FROM pg_catalog.pg_sequence "
-						  "WHERE seqrelid = '%u'::oid",
+						  "seqcache, seqcycle, "
+						  "c.relpersistence "
+						  "FROM pg_catalog.pg_sequence s, "
+						  "pg_catalog.pg_class c "
+						  "WHERE seqrelid = '%u'::oid "
+						  "and s.seqrelid = c.oid",
 						  tbinfo->dobj.catId.oid);
 	}
 	else if (fout->remoteVersion >= 80400)
@@ -17300,6 +17324,9 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
 
+	if (fout->remoteVersion >= 140000)
+		global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
 	if (strcmp(seqtype, "smallint") == 0)
@@ -17377,9 +17404,13 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	}
 	else
 	{
-		appendPQExpBuffer(query,
-						  "CREATE SEQUENCE %s\n",
-						  fmtQualifiedDumpable(tbinfo));
+		appendPQExpBuffer(query, "CREATE ");
+
+		if (global_temp_seq)
+			appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+		appendPQExpBuffer(query, "SEQUENCE %s\n",
+								  fmtQualifiedDumpable(tbinfo));
 
 		if (strcmp(seqtype, "bigint") != 0)
 			appendPQExpBuffer(query, "    AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index ad5f3919956..f3819860096 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -88,7 +88,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -178,7 +178,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 5d9a26cf822..2de11d5d707 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -447,8 +449,17 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
 			 "         ON c.relnamespace = n.oid "
 			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
+			 CppAsString2(RELKIND_MATVIEW) ") AND ");
+
+	if (skip_gtt)
+	{
+		/* exclude global temp tables */
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+			"    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND ");
+	}
+
 	/* exclude possible orphaned temp tables */
+	snprintf(query + strlen(query), sizeof(query) - strlen(query),
 			 "    ((n.nspname !~ '^pg_temp_' AND "
 			 "      n.nspname !~ '^pg_toast_temp_' AND "
 			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 3628bd74a7b..bbb9b5ea13d 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -407,7 +407,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -645,7 +645,10 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+			/* exclude global temp tables */
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -656,7 +659,10 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+		/* exclude global temp tables */
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index ca0795f68ff..018a2effd4b 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -389,7 +389,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 006661412ea..ffba81acb86 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -4089,7 +4089,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 8e01f545003..90882e1cd55 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1059,6 +1059,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2554,6 +2556,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2781,6 +2786,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE", "SEQUENCE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 6ce480b49c1..dda3f3c5a60 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -85,7 +85,7 @@ extern Oid	heap_create_with_catalog(const char *relname,
 
 extern void heap_drop_with_catalog(Oid relid);
 
-extern void heap_truncate(List *relids);
+extern void heap_truncate(List *relids, bool is_global_temp);
 
 extern void heap_truncate_one_rel(Relation rel);
 
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 008f723e104..875b1003899 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -157,6 +157,7 @@ extern void reindex_index(Oid indexId, bool skip_constraint_checks,
 #define REINDEX_REL_CHECK_CONSTRAINTS		0x04
 #define REINDEX_REL_FORCE_INDEXES_UNLOGGED	0x08
 #define REINDEX_REL_FORCE_INDEXES_PERMANENT 0x10
+#define REINDEX_REL_PROCESS_GLOBAL_TEMP		0x20
 
 extern bool reindex_relation(Oid relid, int flags, ReindexParams *params);
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index fef9945ed8f..9176b7dcc07 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -172,6 +172,7 @@ DECLARE_INDEX(pg_class_tblspc_relfilenode_index, 3455, ClassTblspcRelfilenodeInd
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index d068d6532ec..fd5089388f2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5735,6 +5735,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '9874',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '9875',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '9876',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '9877',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 0ab32b44e91..92e9f8ba485 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,7 +22,7 @@
 /* GUC variables */
 extern int	wal_skip_threshold;
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationPreTruncate(Relation rel);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 00000000000..8a3d9558712
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,45 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern int		vacuum_gtt_defer_check_age;
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Oid relid,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void force_enable_gtt_index(Relation index);
+extern void gtt_fix_index_backend_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index 40544dd4c70..7b66d808fc5 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 336549cc5f0..3e8167134b7 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -86,7 +86,7 @@ extern void find_composite_type_dependencies(Oid typeOid,
 
 extern void check_of_type(HeapTuple typetuple);
 
-extern void register_on_commit_action(Oid relid, OnCommitAction action);
+extern void register_on_commit_action(Oid relid, OnCommitAction action, bool is_gloal_temp);
 extern void remove_on_commit_action(Oid relid);
 
 extern void PreCommit_on_commit_actions(void);
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 8336c2c5a29..bddcfe7256d 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index c86ccdaf608..6b395551c1d 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -399,6 +399,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index a8f052e4845..4b4ed1a13aa 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -189,6 +189,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_SHARED_TIDBITMAP,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_PER_XACT_PREDICATE_LIST,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index be67d8a8616..e2f8bb5162d 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -157,6 +157,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId backend_gtt_frozenxid;	/* backend level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index b01fa52139a..8efffa55ac5 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -94,4 +94,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index aa18d304ac0..524c9d7de3f 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -288,6 +288,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 b4faa1c1238..a74558a8383 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	SMgrRelation rd_smgr;		/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -326,6 +326,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -608,11 +609,13 @@ RelationGetSmgr(Relation rel)
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -620,6 +623,7 @@ RelationGetSmgr(Relation rel)
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -632,6 +636,30 @@ RelationGetSmgr(Relation rel)
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP_ON_CURRENT_SESSION
+ *		Test a rel is either local temp relation of this session
+ *		or global temp relation.
+ */
+#define RELATION_IS_TEMP_ON_CURRENT_SESSION(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is local temp relation or global temporary relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RelpersistenceTsTemp
+ *		Test a relpersistence is local temp relation or global temporary relation.
+ */
+#define RelpersistenceTsTemp(relpersistence) \
+	(relpersistence == RELPERSISTENCE_TEMP || \
+	 relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -677,6 +705,19 @@ RelationGetSmgr(Relation rel)
 	 (relation)->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&	\
 	 !IsCatalogRelation(relation))
 
+/* For global temporary table */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/* Get on commit clause value only for global temporary table */
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+/* Get relpersistence for relation */
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
-- 
2.30.1 (Apple Git-130)

#340wenjing
wjzeng2012@gmail.com
In reply to: wenjing (#339)
4 attachment(s)
Re: [Proposal] Global temporary tables

wenjing <wjzeng2012@gmail.com> 于2021年10月30日周六 上午1:28写道:

Andrew Bille <andrewbille@gmail.com> 于2021年10月28日周四 下午6:30写道:

Thanks, the "group by" is fixed

Yet another crash (on v58 patches), reproduced with:

psql -t -c "create global temp table t(b text)
with(on_commit_delete_rows=true); create index idx_b on t (b); insert into
t values('test'); alter table t alter b type varchar;"
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
connection to server was lost

Thank you for pointing that out.
This is due to an optimization point: ALTER Table reuses the relfilenode
of the old index.
I have banned this optimization point for GTT, I am not entirely sure it
is appropriate, maybe you can give some suggestions.
Please review the new code(v59).

Wenjing

with trace:

[New LWP 569199]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Core was generated by `postgres: andrew postgres [local] ALTER TABLE
'.
Program terminated with signal SIGABRT, Aborted.
#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
50 ../sysdeps/unix/sysv/linux/raise.c: No such file or directory.
(gdb) bt
#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
#1 0x00007f197493f859 in __GI_abort () at abort.c:79
#2 0x00005562b3306fb9 in ExceptionalCondition
(conditionName=0x5562b34dd740 "reln->md_num_open_segs[forkNum] == 0",
errorType=0x5562b34dd72c "FailedAssertion", fileName=0x5562b34dd727 "md.c",
lineNumber=187) at assert.c:69
#3 0x00005562b3148f15 in mdcreate (reln=0x5562b41abdc0,
forkNum=MAIN_FORKNUM, isRedo=false) at md.c:187
#4 0x00005562b314b73f in smgrcreate (reln=0x5562b41abdc0,
forknum=MAIN_FORKNUM, isRedo=false) at smgr.c:335
#5 0x00005562b2d88b23 in RelationCreateStorage (rnode=...,
relpersistence=103 'g', rel=0x7f196b597270) at storage.c:154
#6 0x00005562b2d5a408 in index_build (heapRelation=0x7f196b58dc40,
indexRelation=0x7f196b597270, indexInfo=0x5562b4167d60, isreindex=true,
parallel=false) at index.c:3038
#7 0x00005562b2d533c1 in RelationTruncateIndexes
(heapRelation=0x7f196b58dc40, lockmode=1) at heap.c:3354
#8 0x00005562b2d5360b in heap_truncate_one_rel (rel=0x7f196b58dc40) at
heap.c:3452
#9 0x00005562b2d53544 in heap_truncate (relids=0x5562b4167c58,
is_global_temp=true) at heap.c:3410
#10 0x00005562b2ea09fc in PreCommit_on_commit_actions () at
tablecmds.c:16495
#11 0x00005562b2d0d4ee in CommitTransaction () at xact.c:2140
#12 0x00005562b2d0e320 in CommitTransactionCommand () at xact.c:2979
#13 0x00005562b3151b7e in finish_xact_command () at postgres.c:2721
#14 0x00005562b314f340 in exec_simple_query (query_string=0x5562b40c2170
"create global temp table t(b text) with(on_commit_delete_rows=true);
create index idx_b on t (b); insert into t values('test'); alter table t
alter b type varchar;") at postgres.c:1239
#15 0x00005562b3153f0a in PostgresMain (dbname=0x5562b40ed6e8 "postgres",
username=0x5562b40ed6c8 "andrew") at postgres.c:4497
#16 0x00005562b307df6e in BackendRun (port=0x5562b40e4500) at
postmaster.c:4560
#17 0x00005562b307d853 in BackendStartup (port=0x5562b40e4500) at
postmaster.c:4288
#18 0x00005562b3079a1d in ServerLoop () at postmaster.c:1801
#19 0x00005562b30791b6 in PostmasterMain (argc=3, argv=0x5562b40bc5b0) at
postmaster.c:1473
#20 0x00005562b2f6d98e in main (argc=3, argv=0x5562b40bc5b0) at main.c:198

On Mon, Oct 25, 2021 at 7:13 PM wenjing <wjzeng2012@gmail.com> wrote:

I missed whole row and system column. It has been fixed in v58.
Please review the new code(v58) again

Hi Andrew

I fixed a problem found during testing.
GTT version updated to v60.

Wenjing.

Attachments:

0002-gtt-v60-doc.patchapplication/octet-stream; name=0002-gtt-v60-doc.patchDownload
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 473a0a4aeb..e510bde8ac 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -169,32 +169,67 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     <listitem>
      <para>
       If specified, the table is created as a temporary table.
-      Temporary tables are automatically dropped at the end of a
-      session, or optionally at the end of the current transaction
-      (see <literal>ON COMMIT</literal> below).  The default
-      search_path includes the temporary schema first and so identically
-      named existing permanent tables are not chosen for new plans
+      Optionally, <literal>GLOBAL</literal> or <literal>LOCAL</literal>
+      can be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
+      They represent two types of temporary tables supported by <productname>PostgreSQL</productname>:
+      global temporary table and local temporary table. Without specified
+      GLOBAL or LOCAL, a local temporary table is created by default.
+     </para>
+
+    <para>
+     Both types of temporary tables’ data are truncated at the
+     end of a session or optionally at the end of the current transaction.
+     (see <literal>ON COMMIT</literal> below). For global temporary table,
+     its schema is reserved and reused by future sessions or transactions.
+     For local temporary table, both its data and its schema are dropped.
+    </para>
+
+    <variablelist>
+     <varlistentry>
+      <term><literal>Global Temporary Table</literal></term>
+      <listitem>
+       <para>
+        Global temporary table are defined just once and automatically exist
+        (starting with empty contents) in every session that needs them.
+        The schema definition of temporary tables is persistent and shared among sessions.
+        However, the data in temporary tables are kept private to sessions themselves,
+        even though they use same name and same schema.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>Local Temporary Table</literal></term>
+     <listitem>
+     <para>
+      Local temporary table are automatically dropped at the end of a
+      session (include schema and data). Future sessions need to create
+      their own temporary tables when they are used.
+     </para>
+     <para>
+      The default search_path includes the temporary schema first and so
+      identically named existing permanent tables are not chosen for new plans
       while the temporary table exists, unless they are referenced
       with schema-qualified names. Any indexes created on a temporary
       table are automatically temporary as well.
      </para>
+     </listitem>
+     </varlistentry>
+    </variablelist>
 
-     <para>
-      The <link linkend="autovacuum">autovacuum daemon</link> cannot
-      access and therefore cannot vacuum or analyze temporary tables.
-      For this reason, appropriate vacuum and analyze operations should be
-      performed via session SQL commands.  For example, if a temporary
-      table is going to be used in complex queries, it is wise to run
-      <command>ANALYZE</command> on the temporary table after it is populated.
-     </para>
+    <para>
+     The <link linkend="autovacuum">autovacuum daemon</link> cannot
+     access and therefore cannot vacuum or analyze temporary tables.
+     For this reason, appropriate vacuum and analyze operations should be
+     performed via session SQL commands.  For example, if a temporary
+     table is going to be used in complex queries, it is wise to run
+     <command>ANALYZE</command> on the temporary table after it is populated.
+    </para>
+    <para>
+     The Temporary table resembles the SQL standard, but has some differences.
+     see <xref linkend="sql-createtable-compatibility"/> below.
+    </para>
 
-     <para>
-      Optionally, <literal>GLOBAL</literal> or <literal>LOCAL</literal>
-      can be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
-      This presently makes no difference in <productname>PostgreSQL</productname>
-      and is deprecated; see
-      <xref linkend="sql-createtable-compatibility"/> below.
-     </para>
     </listitem>
    </varlistentry>
 
@@ -2133,13 +2168,17 @@ CREATE TABLE cities_partdef
    <title>Temporary Tables</title>
 
    <para>
-    Although the syntax of <literal>CREATE TEMPORARY TABLE</literal>
-    resembles that of the SQL standard, the effect is not the same.  In the
-    standard,
-    temporary tables are defined just once and automatically exist (starting
-    with empty contents) in every session that needs them.
-    <productname>PostgreSQL</productname> instead
-    requires each session to issue its own <literal>CREATE TEMPORARY
+    Although the syntax of <literal>CREATE GLOBAL/LOCAL TEMPORARY TABLE</literal>
+    resembles that of the SQL standard, the effect is not the same.
+    The global temporary table follows the SQL standards while local temporary
+    table does not.
+   </para>
+
+   <para>
+    First, in the standard, both global and local temporary tables are defined just
+    once and automatically exist (starting with empty contents) in every session
+    that needs them. For local temporary tables, <productname>PostgreSQL</productname>
+    instead requires each session to issue its own <literal>CREATE LOCAL TEMPORARY
     TABLE</literal> command for each temporary table to be used.  This allows
     different sessions to use the same temporary table name for different
     purposes, whereas the standard's approach constrains all instances of a
@@ -2147,29 +2186,14 @@ CREATE TABLE cities_partdef
    </para>
 
    <para>
-    The standard's definition of the behavior of temporary tables is
-    widely ignored.  <productname>PostgreSQL</productname>'s behavior
-    on this point is similar to that of several other SQL databases.
-   </para>
-
-   <para>
-    The SQL standard also distinguishes between global and local temporary
+    Second, the SQL standard distinguishes between global and local temporary
     tables, where a local temporary table has a separate set of contents for
     each SQL module within each session, though its definition is still shared
-    across sessions.  Since <productname>PostgreSQL</productname> does not
+    across sessions. Since <productname>PostgreSQL</productname> does not
     support SQL modules, this distinction is not relevant in
     <productname>PostgreSQL</productname>.
    </para>
 
-   <para>
-    For compatibility's sake, <productname>PostgreSQL</productname> will
-    accept the <literal>GLOBAL</literal> and <literal>LOCAL</literal> keywords
-    in a temporary table declaration, but they currently have no effect.
-    Use of these keywords is discouraged, since future versions of
-    <productname>PostgreSQL</productname> might adopt a more
-    standard-compliant interpretation of their meaning.
-   </para>
-
    <para>
     The <literal>ON COMMIT</literal> clause for temporary tables
     also resembles the SQL standard, but has some differences.
@@ -2177,7 +2201,8 @@ CREATE TABLE cities_partdef
     default behavior is <literal>ON COMMIT DELETE ROWS</literal>.  However, the
     default behavior in <productname>PostgreSQL</productname> is
     <literal>ON COMMIT PRESERVE ROWS</literal>.  The <literal>ON COMMIT
-    DROP</literal> option does not exist in SQL.
+    DROP</literal> option does not exist in SQL and is not supported by
+    global temporary table.
    </para>
   </refsect2>
 
-- 
2.30.1 (Apple Git-130)

0001-gtt-v60-reademe.patchapplication/octet-stream; name=0001-gtt-v60-reademe.patchDownload
diff --git a/README.gtt.txt b/README.gtt.txt
new file mode 100644
index 00000000000..d181df9acd7
--- /dev/null
+++ b/README.gtt.txt
@@ -0,0 +1,172 @@
+Global Temporary Table(GTT)
+=========================================
+
+Feature description
+-----------------------------------------
+
+Previously, temporary tables are defined once and automatically
+exist (starting with empty contents) in every session before using them.
+
+The temporary table implementation in PostgreSQL, known as Local temp tables(LTT),
+did not fully comply with the SQL standard. This version added the support of
+Global Temporary Table .
+
+The metadata of Global Temporary Table is persistent and shared among sessions.
+The data stored in the Global temporary table is independent of sessions. This
+means, when a session creates a Global Temporary Table and writes some data.
+Other sessions cannot see those data, but they have an empty Global Temporary
+Table with same schema.
+
+Like local temporary table, Global Temporary Table supports ON COMMIT PRESERVE ROWS
+or ON COMMIT DELETE ROWS clause, so that data in the temporary table can be
+cleaned up or preserved automatically when a session exits or a transaction COMMITs.
+
+Unlike Local Temporary Table, Global Temporary Table does not support
+ON COMMIT DROP clauses.
+
+In following paragraphs, we use GTT for Global Temporary Table and LTT for
+local temporary table.
+
+Main design ideas
+-----------------------------------------
+In general, GTT and LTT use the same storage and buffer design and
+implementation. The storage files for both types of temporary tables are named
+as t_backendid_relfilenode, and the local buffer is used to cache the data.
+
+The schema of GTTs is shared among sessions while their data are not. We build
+a new mechanisms to manage those non-shared data and their statistics.
+Here is the summary of changes:
+
+1) CATALOG
+GTTs store session-specific data. The storage information of GTTs'data, their
+transaction information, and their statistics are not stored in the catalog.
+
+2) STORAGE INFO & STATISTICS INFO & TRANSACTION INFO
+In order to maintain durability and availability of GTTs'session-specific data,
+their storage information, statistics, and transaction information is managed
+in a local hash table tt_storage_local_hash.
+
+3) DDL
+Currently, GTT supports almost all table'DDL except CLUSTER/VACUUM FULL.
+Part of the DDL behavior is limited by shared definitions and multiple copies of
+local data, and we added some structures to handle this.
+
+A shared hash table active_gtt_shared_hash is added to track the state of the
+GTT in a different session. This information is recorded in the hash table
+during the DDL execution of the GTT.
+
+The data stored in a GTT can only be modified or accessed by owning session.
+The statements that only modify data in a GTT do not need a high level of
+table locking. The operations making those changes include truncate GTT,
+reindex GTT, and lock GTT.
+
+4) MVCC commit log(clog) cleanup
+Each GTT in a session has its own piece of data, and they have their own
+transaction information. We set up data structures to track and maintain
+this information. The cleaning of CLOGs also needs to consider the transaction
+information of GTT.
+
+Detailed design
+-----------------------------------------
+
+1. CATALOG
+1.1 relpersistence
+define RELPERSISTENCEGLOBALTEMP 'g'
+Mark Global Temporary Table in pg_class relpersistence to 'g'. The relpersistence
+of indexes created on the GTT, sequences on GTT and toast tables on GTT are
+also set to 'g'
+
+1.2 on commit clause
+LTT's status associated with on commit DELETE ROWS and on commit PRESERVE ROWS
+is not stored in catalog. Instead, GTTs need a bool value on_commit_delete_rows
+in reloptions which is shared among sessions.
+
+1.3 gram.y
+GTT is already supported in syntax tree. We remove the warning message
+"GLOBAL is deprecated in temporary table creation" and mark
+relpersistence = RELPERSISTENCEGLOBALTEMP.
+
+2. STORAGE INFO & STATISTICS INFO & TRANSACTION INFO
+2.1. gtt_storage_local_hash
+Each backend creates a local hashtable gtt_storage_local_hash to track a GTT's
+storage file information, statistics, and transaction information.
+
+2.2 GTT storage file info track
+1) When one session inserts data into a GTT for the first time, record the
+storage info to gtt_storage_local_hash.
+2) Use beforeshmemexit to ensure that all files of session GTT are deleted when
+the session exits.
+
+2.3 statistics info
+1) relpages reltuples relallvisible relfilenode
+2) The statistics of each column from pg_statistic
+All the above information is stored in gtt_storage_local_hash.
+When doing vacuum or analyze, GTT's statistic is updated, which is used by
+the SQL planner.
+The statistics summarizes only data in the current session.
+
+2.3 transaction info track
+frozenxid minmulti from pg_class is stored to gtt_storage_local_hash.
+
+3 DDL
+3.1. active_gtt_shared_hash
+This is the hash table created in shared memory to trace the GTT files initialized
+in each session. Each hash entry contains a bitmap that records the backendid of
+the initialized GTT file. With this hash table, we know which backend/session
+is using this GTT. Such information is used during GTT's DDL operations.
+
+3.2 DROP GTT
+One GTT is allowed to be deleted when there is only one session using the table
+and the session is the current session. After holding the lock on GTT,
+active_gtt_shared_hash is checked to ensure that.
+
+3.3 ALTER GTT/DROP INDEX ON GTT
+Same as drop GTT.
+
+3.4 CREATE INDEX ON GTT
+1) create index on GTT statements build index based on local data in a session.
+2) After the index is created, record the index metadata to the catalog.
+3) Other sessions can enable or disable the local GTT index.
+
+3.5 TRUNCATE/REINDEX GTT
+The SQL truncate/reindex command open the GTT using AccessShareLock lock,
+not AccessExclusiveLock, because this command only cleans up local data and
+local buffers in current session. This allows these operations to be executed
+concurrently between sessions, unlike normal tables.
+
+3.6 LOCK GTT
+A lock GTT statement does not hold any relation lock.
+
+3.7 CLUSTER GTT/VACUUM FULL GTT
+The current version does not support.
+
+4 MVCC commit log(clog) cleanup
+
+The GTT storage file contains transaction information. Queries for GTT data rely
+on transaction information such as clog. The transaction information required by
+each session may be completely different. We need to ensure that the transaction
+information of the GTT data is not cleaned up during its lifetime and that
+transaction resources are recycled at the instance level.
+
+4.1 The session level GTT oldest frozenxid
+1) To manage all GTT transaction information, add session level oldest frozenxid
+in each session. When one GTT is created or removed, record the session level
+oldest frozenxid and store it in MyProc.
+2) When vacuum advances the database's frozenxid, session level oldest frozenxid
+should be considered. This is acquired by searching all of MyProc. This way,
+we can avoid the clog required by GTTs to be cleaned.
+
+4.2 vacuum GTT
+Users can perform vacuum over a GTT to clean up local data in the GTT.
+
+4.3 autovacuum GTT
+Autovacuum skips all GTTs, because the data in GTTs is only visible in current session.
+
+5 OTHERS
+5.1 Parallel query
+Planner does not produce parallel query plans for SQL related to GTT. Because
+GTT private data cannot be accessed across processes.
+
+5.2 WAL and Logical replication
+Like LTT, the DML on GTT does not record WAL and is not parsed or replay by
+the logical replication.
\ No newline at end of file
-- 
2.30.1 (Apple Git-130)

0004-gtt-v60-regress.patchapplication/octet-stream; name=0004-gtt-v60-regress.patchDownload
diff --git a/src/test/isolation/expected/gtt-sequence.out b/src/test/isolation/expected/gtt-sequence.out
new file mode 100644
index 00000000000..31db2ebd423
--- /dev/null
+++ b/src/test/isolation/expected/gtt-sequence.out
@@ -0,0 +1,48 @@
+unused step name: s1_seq_restart
+Parsed test spec with 2 sessions
+
+starting permutation: s1_seq_next s2_seq_next s1_seq_next
+step s1_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      1
+(1 row)
+
+step s2_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      1
+(1 row)
+
+step s1_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      2
+(1 row)
+
+
+starting permutation: s1_select s2_select s1_insert s2_insert s1_select s2_select
+step s1_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+(0 rows)
+
+step s2_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+(0 rows)
+
+step s1_insert: insert into gtt_with_seq values(1);
+step s2_insert: insert into gtt_with_seq values(10);
+step s1_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+ 1| 3
+(1 row)
+
+step s2_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+10| 1
+(1 row)
+
diff --git a/src/test/isolation/expected/gtt-table.out b/src/test/isolation/expected/gtt-table.out
new file mode 100644
index 00000000000..5825773aa12
--- /dev/null
+++ b/src/test/isolation/expected/gtt-table.out
@@ -0,0 +1,675 @@
+Parsed test spec with 3 sessions
+
+starting permutation: s1_update_d
+step s1_update_d: update gtt_on_commit_delete_row set b = 'update'
+
+starting permutation: s1_select_d s1_insert_d s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_begin s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+1|test1
+(1 row)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_begin s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+1|test1
+(1 row)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_select_d s1_truncate_d s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_begin s1_insert_d s1_select_d s1_truncate_d s1_select_d s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_begin s1_insert_d s1_select_d s1_truncate_d s1_select_d s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_insert_d s1_select_d s1_begin s1_insert_d s1_select_d s1_save_1 s1_truncate_d s1_insert_d s1_select_d s1_save_2 s1_truncate_d s1_insert_d s1_select_d s1_save_3 s1_rollback_to_save_2 s1_select_d s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_save_1: SAVEPOINT save1
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_save_2: SAVEPOINT save2
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+4|test1
+(1 row)
+
+step s1_save_3: SAVEPOINT save3
+step s1_rollback_to_save_2: rollback to savepoint save2
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+5|test1
+(2 rows)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_insert_d s1_select_d s1_begin s1_insert_d s1_select_d s1_save_1 s1_truncate_d s1_insert_d s1_select_d s1_save_2 s1_truncate_d s1_insert_d s1_select_d s1_save_3 s1_rollback_to_save_2 s1_select_d s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_save_1: SAVEPOINT save1
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_save_2: SAVEPOINT save2
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+4|test1
+(1 row)
+
+step s1_save_3: SAVEPOINT save3
+step s1_rollback_to_save_2: rollback to savepoint save2
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+5|test1
+(2 rows)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_update_p
+step s2_update_p: update gtt_on_commit_preserve_row set b = 'update'
+
+starting permutation: s2_select_p s2_insert_p s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_begin s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_begin s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_select_p s2_insert_p s2_select_p s2_truncate_p s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_select_p s2_insert_p s2_begin s2_insert_p s2_select_p s2_truncate_p s2_select_p s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_insert_p s2_begin s2_insert_p s2_select_p s2_truncate_p s2_select_p s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_insert_p s2_select_p s2_begin s2_insert_p s2_select_p s2_save_1 s2_truncate_p s2_insert_p s2_select_p s2_save_2 s2_truncate_p s2_insert_p s2_select_p s2_save_3 s2_rollback_to_save_2 s2_select_p s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_save_1: SAVEPOINT save1
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_save_2: SAVEPOINT save2
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+4|test10
+(1 row)
+
+step s2_save_3: SAVEPOINT save3
+step s2_rollback_to_save_2: rollback to savepoint save2
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+
+starting permutation: s2_insert_p s2_select_p s2_begin s2_insert_p s2_select_p s2_save_1 s2_truncate_p s2_insert_p s2_select_p s2_save_2 s2_truncate_p s2_insert_p s2_select_p s2_save_3 s2_rollback_to_save_2 s2_select_p s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_save_1: SAVEPOINT save1
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_save_2: SAVEPOINT save2
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+4|test10
+(1 row)
+
+step s2_save_3: SAVEPOINT save3
+step s2_rollback_to_save_2: rollback to savepoint save2
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_insert_p s2_insert_p s1_select_p s2_select_p
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_truncate_p s2_truncate_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_begin s1_insert_d s2_insert_d s1_truncate_d s2_insert_d s1_commit
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s2_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s2_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_commit: commit
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_reindex_p s2_reindex_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_reindex_p: reindex table gtt_on_commit_preserve_row
+step s2_reindex_p: reindex table gtt_on_commit_preserve_row
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_reindex_i_p s2_reindex_i_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_reindex_i_p: reindex index gtt_on_commit_preserve_row_pkey
+step s2_reindex_i_p: reindex index gtt_on_commit_preserve_row_pkey
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_insert_c s3_create_c s3_insert_c s1_insert_c s1_analyze_c s2_analyze_c s3_analyze_c s1_select_c s2_select_c s3_select_c
+step s2_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s3_create_c: create unique index idx_temp_table_a on gtt_test_createindex(a)
+step s3_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s1_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s1_analyze_c: analyze gtt_test_createindex
+step s2_analyze_c: analyze gtt_test_createindex
+step s3_analyze_c: analyze gtt_test_createindex
+step s1_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                                               
+---------------------------------------------------------
+Index Scan using idx_temp_table_a on gtt_test_createindex
+  Index Cond: (a = 1)                                    
+(2 rows)
+
+step s2_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                      
+--------------------------------
+Seq Scan on gtt_test_createindex
+  Filter: (a = 1)               
+(2 rows)
+
+step s3_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                                               
+---------------------------------------------------------
+Index Scan using idx_temp_table_a on gtt_test_createindex
+  Index Cond: (a = 1)                                    
+(2 rows)
+
+
+starting permutation: s1_begin s2_begin s1_lock_p s2_lock_p s1_truncate_p s2_truncate_p s1_insert_p s2_insert_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_lock_p: LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE
+step s2_lock_p: LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE
+step s1_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index f4c01006fc1..746a17f824c 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -96,3 +96,5 @@ test: plpgsql-toast
 test: truncate-conflict
 test: serializable-parallel
 test: serializable-parallel-2
+test: gtt-sequence
+test: gtt-table
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 88594a3cb5d..ec643aadb5f 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -80,9 +80,30 @@ disconnect_atexit(void)
 {
 	int			i;
 
-	for (i = 0; i < nconns; i++)
+	for (i = 1; i < nconns; i++)
 		if (conns[i].conn)
 			PQfinish(conns[i].conn);
+
+	if (parseresult.destroy)
+	{
+		PGresult   *res;
+
+		res = PQexec(conns[0].conn, parseresult.destroy);
+		if (PQresultStatus(res) == PGRES_TUPLES_OK)
+		{
+			printResultSet(res);
+		}
+		else if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		{
+			fprintf(stderr, "destroy failed: %s",
+					PQerrorMessage(conns[0].conn));
+			/* don't exit on teardown failure */
+		}
+		PQclear(res);
+	}
+
+	if (conns[0].conn)
+		PQfinish(conns[0].conn);
 }
 
 int
@@ -214,6 +235,24 @@ main(int argc, char **argv)
 	PQclear(res);
 	termPQExpBuffer(&wait_query);
 
+	if (parseresult.initialize)
+	{
+		PGresult   *res;
+
+		res = PQexec(conns[0].conn, parseresult.initialize);
+		if (PQresultStatus(res) == PGRES_TUPLES_OK)
+		{
+			printResultSet(res);
+		}
+		else if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		{
+			fprintf(stderr, "initialize failed: %s",
+					PQerrorMessage(conns[0].conn));
+			/* don't exit on teardown failure */
+		}
+		PQclear(res);
+	}
+
 	/*
 	 * Run the permutations specified in the spec, or all if none were
 	 * explicitly specified.
diff --git a/src/test/isolation/isolationtester.h b/src/test/isolation/isolationtester.h
index 5f300219c20..b5a29893da9 100644
--- a/src/test/isolation/isolationtester.h
+++ b/src/test/isolation/isolationtester.h
@@ -81,6 +81,8 @@ typedef struct
 	int			nsessions;
 	Permutation **permutations;
 	int			npermutations;
+	char	   *initialize;
+	char	   *destroy;
 } TestSpec;
 
 extern TestSpec parseresult;
diff --git a/src/test/isolation/specparse.y b/src/test/isolation/specparse.y
index c25aa1a73fa..2784f758ed9 100644
--- a/src/test/isolation/specparse.y
+++ b/src/test/isolation/specparse.y
@@ -39,7 +39,7 @@ TestSpec		parseresult;			/* result of parsing is left here */
 }
 
 %type <ptr_list> setup_list
-%type <str>  opt_setup opt_teardown
+%type <str>  opt_setup opt_teardown opt_initialize opt_destroy
 %type <str> setup
 %type <ptr_list> step_list session_list permutation_list opt_permutation_list
 %type <ptr_list> permutation_step_list blocker_list
@@ -51,23 +51,27 @@ TestSpec		parseresult;			/* result of parsing is left here */
 
 %token <str> sqlblock identifier
 %token <integer> INTEGER
-%token NOTICES PERMUTATION SESSION SETUP STEP TEARDOWN TEST
+%token NOTICES PERMUTATION SESSION SETUP STEP TEARDOWN TEST INITIALIZE DESTROY
 
 %%
 
 TestSpec:
+			opt_initialize
 			setup_list
 			opt_teardown
+			opt_destroy
 			session_list
 			opt_permutation_list
 			{
-				parseresult.setupsqls = (char **) $1.elements;
-				parseresult.nsetupsqls = $1.nelements;
-				parseresult.teardownsql = $2;
-				parseresult.sessions = (Session **) $3.elements;
-				parseresult.nsessions = $3.nelements;
-				parseresult.permutations = (Permutation **) $4.elements;
-				parseresult.npermutations = $4.nelements;
+				parseresult.setupsqls = (char **) $2.elements;
+				parseresult.nsetupsqls = $2.nelements;
+				parseresult.teardownsql = $3;
+				parseresult.sessions = (Session **) $5.elements;
+				parseresult.nsessions = $5.nelements;
+				parseresult.permutations = (Permutation **) $6.elements;
+				parseresult.npermutations = $6.nelements;
+				parseresult.initialize = $1;
+				parseresult.destroy = $4;
 			}
 		;
 
@@ -100,6 +104,16 @@ opt_teardown:
 			| TEARDOWN sqlblock	{ $$ = $2; }
 		;
 
+opt_initialize:
+			/* EMPTY */			{ $$ = NULL; }
+			| INITIALIZE sqlblock	{ $$ = $2; }
+		;
+
+opt_destroy:
+			/* EMPTY */			{ $$ = NULL; }
+			| DESTROY sqlblock	{ $$ = $2; }
+		;
+
 session_list:
 			session_list session
 			{
diff --git a/src/test/isolation/specs/gtt-sequence.spec b/src/test/isolation/specs/gtt-sequence.spec
new file mode 100644
index 00000000000..88eece45e29
--- /dev/null
+++ b/src/test/isolation/specs/gtt-sequence.spec
@@ -0,0 +1,39 @@
+# Tests for global temporary relations
+
+initialize
+{
+  CREATE GLOBAL TEMPORARY TABLE if not exists gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+}
+
+destroy
+{
+  /* wait other backend exit */
+  select pg_sleep(1);
+
+  DROP TABLE gtt_with_seq;
+}
+
+# Session 1
+session "s1"
+step "s1_seq_next" { select nextval('gtt_with_seq_c2_seq'); }
+step "s1_seq_restart" { alter sequence gtt_with_seq_c2_seq RESTART; }
+step "s1_insert" { insert into gtt_with_seq values(1); }
+step "s1_select" { select * from gtt_with_seq order by c1,c2; }
+teardown
+{
+  TRUNCATE gtt_with_seq;
+}
+
+# Session 2
+session "s2"
+step "s2_seq_next" { select nextval('gtt_with_seq_c2_seq'); }
+step "s2_insert" { insert into gtt_with_seq values(10); }
+step "s2_select" { select * from gtt_with_seq order by c1,c2; }
+teardown
+{
+  TRUNCATE gtt_with_seq RESTART IDENTITY;
+}
+
+permutation "s1_seq_next" "s2_seq_next" "s1_seq_next"
+permutation "s1_select" "s2_select" "s1_insert" "s2_insert" "s1_select" "s2_select"
+
diff --git a/src/test/isolation/specs/gtt-table.spec b/src/test/isolation/specs/gtt-table.spec
new file mode 100644
index 00000000000..e0396b21ef0
--- /dev/null
+++ b/src/test/isolation/specs/gtt-table.spec
@@ -0,0 +1,135 @@
+# Tests for global temporary relations
+
+initialize
+{
+  create global temp table gtt_on_commit_delete_row(a bigserial primary key, b text) on commit delete rows;
+  create global temp table gtt_on_commit_preserve_row(a bigserial primary key, b text) on commit preserve rows;
+  create global temp table gtt_test_createindex(a int, b char(1000)) on commit preserve rows;
+}
+
+destroy
+{
+  /* wait other backend exit */
+  select pg_sleep(1);
+
+  DROP TABLE gtt_on_commit_delete_row;
+  DROP TABLE gtt_on_commit_preserve_row;
+  DROP TABLE gtt_test_createindex;
+}
+
+# Session 1
+session "s1"
+step "s1_begin" {begin}
+step "s1_commit" {commit}
+step "s1_rollback" {rollback}
+step "s1_insert_d" {insert into gtt_on_commit_delete_row (b) values('test1')}
+step "s1_select_d" {select a,b from gtt_on_commit_delete_row order by a,b}
+step "s1_insert_p" {insert into gtt_on_commit_preserve_row (b) values('test20')}
+step "s1_select_p" {select a,b from gtt_on_commit_preserve_row order by a,b}
+step "s1_truncate_d" {truncate gtt_on_commit_delete_row}
+step "s1_truncate_p" {truncate gtt_on_commit_preserve_row}
+step "s1_lock_p" {LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE}
+step "s1_update_d" {update gtt_on_commit_delete_row set b = 'update'}
+step "s1_save_1" {SAVEPOINT save1}
+step "s1_save_2" {SAVEPOINT save2}
+step "s1_save_3" {SAVEPOINT save3}
+step "s1_rollback_to_save_2" {rollback to savepoint save2}
+step "s1_reindex_p" {reindex table gtt_on_commit_preserve_row}
+step "s1_reindex_i_p" {reindex index gtt_on_commit_preserve_row_pkey}
+step "s1_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s1_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s1_analyze_c" {analyze gtt_test_createindex}
+teardown
+{
+  TRUNCATE gtt_on_commit_delete_row RESTART IDENTITY;
+  TRUNCATE gtt_on_commit_preserve_row RESTART IDENTITY;
+}
+
+# Session 2
+session "s2"
+step "s2_begin" {begin}
+step "s2_commit" {commit}
+step "s2_rollback" {rollback}
+step "s2_insert_p" {insert into gtt_on_commit_preserve_row (b) values('test10')}
+step "s2_select_p" {select a,b from gtt_on_commit_preserve_row order by a,b}
+step "s2_insert_d" {insert into gtt_on_commit_delete_row (b) values('test1')}
+step "s2_truncate_p" {truncate gtt_on_commit_preserve_row}
+step "s2_update_p" {update gtt_on_commit_preserve_row set b = 'update'}
+step "s2_lock_p" {LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE}
+step "s2_save_1" {SAVEPOINT save1}
+step "s2_save_2" {SAVEPOINT save2}
+step "s2_save_3" {SAVEPOINT save3}
+step "s2_rollback_to_save_2" {rollback to savepoint save2}
+step "s2_reindex_p" {reindex table gtt_on_commit_preserve_row}
+step "s2_reindex_i_p" {reindex index gtt_on_commit_preserve_row_pkey}
+step "s2_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s2_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s2_analyze_c" {analyze gtt_test_createindex}
+teardown
+{
+  TRUNCATE gtt_on_commit_delete_row RESTART IDENTITY;
+  TRUNCATE gtt_on_commit_preserve_row RESTART IDENTITY;
+}
+
+session "s3"
+step "s3_create_c" {create unique index idx_temp_table_a on gtt_test_createindex(a)}
+step "s3_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s3_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s3_analyze_c" {analyze gtt_test_createindex}
+
+
+#
+# test on commit delete temp table
+#
+
+# test update empty temp table
+permutation "s1_update_d"
+# test insert into temp table
+permutation "s1_select_d" "s1_insert_d" "s1_select_d"
+# test temp table in transaction(commit)
+permutation "s1_select_d" "s1_begin"    "s1_insert_d" "s1_select_d"   "s1_commit"   "s1_select_d" 
+# test temp table in transaction(rollback)
+permutation "s1_select_d" "s1_begin"    "s1_insert_d" "s1_select_d"   "s1_rollback" "s1_select_d" 
+# test truncate
+permutation "s1_select_d" "s1_insert_d" "s1_select_d" "s1_truncate_d" "s1_select_d"
+# test truncate in transaction block
+permutation "s1_select_d" "s1_insert_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_truncate_d" "s1_select_d"   "s1_insert_d" "s1_select_d" "s1_commit"   "s1_select_d" 
+permutation "s1_select_d" "s1_insert_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_truncate_d" "s1_select_d"   "s1_insert_d" "s1_select_d" "s1_rollback" "s1_select_d"
+# test temp table with subtransaction or savepoint
+permutation "s1_insert_d" "s1_select_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_save_1"     "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_2"   "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_3" "s1_rollback_to_save_2" "s1_select_d" "s1_insert_d" "s1_select_d" "s1_commit" "s1_select_d"
+permutation "s1_insert_d" "s1_select_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_save_1"     "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_2"   "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_3" "s1_rollback_to_save_2" "s1_select_d" "s1_insert_d" "s1_select_d" "s1_rollback" "s1_select_d"
+
+#
+# test on commit preserve table
+#
+
+# same as test on commit delete temp table
+permutation "s2_update_p"
+permutation "s2_select_p" "s2_insert_p" "s2_select_p"
+permutation "s2_select_p" "s2_begin"    "s2_insert_p" "s2_select_p"   "s2_commit"   "s2_select_p"
+permutation "s2_select_p" "s2_begin"    "s2_insert_p" "s2_select_p"   "s2_rollback" "s2_select_p"
+permutation "s2_select_p" "s2_insert_p" "s2_select_p" "s2_truncate_p" "s2_select_p"
+permutation "s2_select_p" "s2_insert_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_truncate_p" "s2_select_p"   "s2_insert_p" "s2_select_p" "s2_commit"   "s2_select_p" 
+permutation "s2_select_p" "s2_insert_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_truncate_p" "s2_select_p"   "s2_insert_p" "s2_select_p" "s2_rollback" "s2_select_p" 
+permutation "s2_insert_p" "s2_select_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_save_1"     "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_2"   "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_3" "s2_rollback_to_save_2" "s2_select_p" "s2_insert_p" "s2_select_p" "s2_commit" "s2_select_p"
+permutation "s2_insert_p" "s2_select_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_save_1"     "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_2"   "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_3" "s2_rollback_to_save_2" "s2_select_p" "s2_insert_p" "s2_select_p" "s2_rollback" "s2_select_p"
+
+#
+# test concurrent operation on temp table
+#
+
+#  test concurrent read
+permutation "s1_insert_p" "s2_insert_p" "s1_select_p" "s2_select_p" 
+#  test concurrent truncate
+permutation "s1_begin" "s2_begin"    "s1_insert_p" "s2_insert_p"   "s1_truncate_p" "s2_truncate_p"  "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+permutation "s1_begin" "s1_insert_d" "s2_insert_d" "s1_truncate_d" "s2_insert_d"   "s1_commit" 
+#  test concurrent reindex table
+permutation "s1_begin" "s2_begin" "s1_insert_p" "s2_insert_p" "s1_reindex_p"   "s2_reindex_p"   "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+#  test concurrent reindex index
+permutation "s1_begin" "s2_begin" "s1_insert_p" "s2_insert_p" "s1_reindex_i_p" "s2_reindex_i_p" "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+
+# test create index
+permutation "s2_insert_c" "s3_create_c" "s3_insert_c" "s1_insert_c" "s1_analyze_c" "s2_analyze_c" "s3_analyze_c" "s1_select_c" "s2_select_c" "s3_select_c"
+
+# test lock gtt
+permutation "s1_begin" "s2_begin" "s1_lock_p" "s2_lock_p" "s1_truncate_p" "s2_truncate_p" "s1_insert_p" "s2_insert_p" "s1_commit" "s2_commit" "s1_select_p" "s2_select_p"
diff --git a/src/test/isolation/specscanner.l b/src/test/isolation/specscanner.l
index d9fa6a5b54a..697db975479 100644
--- a/src/test/isolation/specscanner.l
+++ b/src/test/isolation/specscanner.l
@@ -67,6 +67,8 @@ session			{ return SESSION; }
 setup			{ return SETUP; }
 step			{ return STEP; }
 teardown		{ return TEARDOWN; }
+initialize		 { return INITIALIZE; }
+destroy			 { return DESTROY; }
 
  /* Whitespace and comments */
 [\n]			{ yyline++; }
diff --git a/src/test/regress/expected/global_temporary_table.out b/src/test/regress/expected/global_temporary_table.out
new file mode 100644
index 00000000000..ec696c61ce9
--- /dev/null
+++ b/src/test/regress/expected/global_temporary_table.out
@@ -0,0 +1,556 @@
+--
+-- GLobal emparary table test case 
+--
+CREATE SCHEMA IF NOT EXISTS global_temporary_table;
+set search_path=global_temporary_table,sys;
+--
+--  test create global temp table basic syntax
+--
+create global temp table gtt_on_commit_default(a int primary key, b text);
+create global temp table gtt_on_commit_delete(a int primary key, b text) on commit delete rows;
+create global temp table gtt_on_commit_delete2(n int) with (on_commit_delete_rows='true');
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit PRESERVE rows;
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt_test_createindex(c0 tsvector,c1 varchar(100), c2 int);
+create global temp table gtt_test_alter(b text) with(on_commit_delete_rows=true);
+--
+-- test DML on global temp table
+--
+-- update empty temp table
+update gtt_on_commit_delete set b ='test';
+begin;
+insert into gtt_on_commit_delete values (1);
+update gtt_on_commit_delete set b ='test';
+-- should 1 row
+select * from gtt_on_commit_delete;
+ a |  b   
+---+------
+ 1 | test
+(1 row)
+
+commit;
+-- data delete after transaction commit
+-- should 0 row
+select * from gtt_on_commit_delete;
+ a | b 
+---+---
+(0 rows)
+
+-- update empty temp table
+update gtt_on_commit_preserve set b ='test';
+insert into gtt_on_commit_preserve values (2);
+begin;
+insert into gtt_on_commit_preserve values (3);
+update gtt_on_commit_preserve set b ='test';
+-- should 2 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 2 | test
+ 3 | test
+(2 rows)
+
+delete from gtt_on_commit_preserve where a=2;
+commit;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 3 | test
+(1 row)
+
+begin;
+insert into gtt_on_commit_preserve values (4);
+-- temp table support truncate;
+truncate gtt_on_commit_preserve;
+select * from gtt_on_commit_preserve order by a;
+ a | b 
+---+---
+(0 rows)
+
+rollback;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 3 | test
+(1 row)
+
+--
+-- test unsupported global temp partition table
+--
+-- should fail
+CREATE global temp TABLE global_temp_partition_01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+on commit delete rows;
+ERROR:  Only support global temporary regular table.
+CREATE TABLE regular_partition_01_2019 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+);
+CREATE TABLE regular_partition01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time);
+-- should fail
+CREATE global temp TABLE temp_partition01_2018
+PARTITION OF regular_partition01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+ERROR:  Not support global temporary partition table or inherit table.
+CREATE global temp TABLE global_temp_partition_01_2021 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+)on commit delete rows;
+-- should fail
+ALTER TABLE regular_partition01 ATTACH PARTITION global_temp_partition_01_2021 FOR VALUES FROM ('2021-01-01 00:00:00') TO ('2022-01-01 00:00:00');
+ERROR:  cannot attach a global temporary relation as partition of permanent relation "regular_partition01"
+--
+-- test unsupported inherit table
+--
+create table 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 inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent);
+ERROR:  Not support global temporary partition table or inherit table.
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent_global_temp) on commit delete rows;
+ERROR:  Not support global temporary partition table or inherit table.
+--
+-- test DDL on global temp table
+--
+create index idx_gtt_test_alter_b on gtt_test_alter (b);
+insert into gtt_test_alter values('test');
+alter table gtt_test_alter alter b type varchar;
+create index gtt_idx_1 on gtt_test_createindex using gin (c0);
+create index gtt_idx_2 on gtt_test_createindex using gist (c0);
+create index gtt_idx_3 on gtt_test_createindex using hash (c2);
+alter table gtt_test_rename rename to gtt_test_new;
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+create index CONCURRENTLY idx_b on gtt_on_commit_default (b);
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+insert into gtt_on_commit_default values(1,'test');
+create global temp table gtt_test_alter1 (a int primary key,b text);
+alter table gtt_test_alter1 alter a type varchar;
+-- should fail
+alter table gtt_on_commit_default alter a type varchar;
+ERROR:  Only support alter global temporary table in an empty context.
+HINT:  Please create a new connection and execute ALTER TABLE on the new connection.
+-- should fail
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+ERROR:  not support cluster global temporary table yet
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- should fail
+alter table gtt_on_commit_default SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temporary table
+-- should fail
+alter table gtt_on_commit_default set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table
+-- should fail
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- should fail
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- should fail
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit drop;
+ERROR:  global temporary table not support on commit drop clause
+-- should fail
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  could not create global temporary table with on commit and with clause at same time
+-- should fail
+CREATE MATERIALIZED VIEW mv_gtt_on_commit_default as select * from gtt_on_commit_default;
+ERROR:  materialized views must not use global temporary tables or views
+--
+-- test foreign key dependencies for global temp table
+--
+CREATE global temp TABLE temp_products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+CREATE global temp TABLE temp_orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+)on commit delete rows;
+-- should fail
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+CREATE global temp TABLE temp_orders_2 (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products(product_no),
+    quantity integer
+);
+--should fail
+insert into temp_orders values(1,1,1);
+ERROR:  insert or update on table "temp_orders" violates foreign key constraint "temp_orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "temp_products".
+insert into temp_products values(1,'test',1.0);
+begin;
+insert into temp_orders values(1,1,1);
+commit;
+-- should 1 row
+select count(*) from temp_products;
+ count 
+-------
+     1
+(1 row)
+
+-- should 0 row
+select count(*) from temp_orders;
+ count 
+-------
+     0
+(1 row)
+
+--
+-- test sequence on global temp table
+--
+create global temp table global_temp_with_serial (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+-- support insert data to temp table in read only transaction
+insert into global_temp_with_serial (b) values(1);
+select * from global_temp_with_serial;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_delete(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_preserve(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table temp_table_with_sequence_oncommit_delete add c2 int default nextval('seq_1');
+alter table temp_table_with_sequence_oncommit_preserve add c2 int default nextval('seq_1');
+begin;
+insert into temp_table_with_sequence_oncommit_delete (c1)values(1);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(2);
+insert into temp_table_with_sequence_oncommit_delete (c1)values(3);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(4);
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  3 |  3
+(2 rows)
+
+commit;
+-- should 0 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_preserve order by c1;
+ c1 | c2 
+----+----
+  2 |  2
+  4 |  4
+(2 rows)
+
+--
+-- test statistics on temp table
+--
+create global temp table temp_table_test_statistics(a int);
+insert into temp_table_test_statistics values(generate_series(1,100000));
+create index idx_test_1 on temp_table_test_statistics (a);
+create index idx_test_2 on temp_table_test_statistics((a*10));
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+                   QUERY PLAN                   
+------------------------------------------------
+ Bitmap Heap Scan on temp_table_test_statistics
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_test_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+                   QUERY PLAN                   
+------------------------------------------------
+ Bitmap Heap Scan on temp_table_test_statistics
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_test_2
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+-- test statistic for whole row
+explain (costs off) select count(*) from temp_table_test_statistics group by temp_table_test_statistics;
+                  QUERY PLAN                  
+----------------------------------------------
+ HashAggregate
+   Group Key: temp_table_test_statistics.*
+   ->  Seq Scan on temp_table_test_statistics
+(3 rows)
+
+-- test statistic for system column
+explain (costs off) select count(*) from temp_table_test_statistics group by tableoid;
+                  QUERY PLAN                  
+----------------------------------------------
+ HashAggregate
+   Group Key: tableoid
+   ->  Seq Scan on temp_table_test_statistics
+(3 rows)
+
+analyze temp_table_test_statistics;
+-- indexscan by idx_test_1
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Index Only Scan using idx_test_1 on temp_table_test_statistics
+   Index Cond: (a = 200000)
+(2 rows)
+
+-- indexscan by idx_test_2
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+                        QUERY PLAN                         
+-----------------------------------------------------------
+ Index Scan using idx_test_2 on temp_table_test_statistics
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+explain (costs off) select count(*) from temp_table_test_statistics group by temp_table_test_statistics;
+                  QUERY PLAN                  
+----------------------------------------------
+ HashAggregate
+   Group Key: temp_table_test_statistics.*
+   ->  Seq Scan on temp_table_test_statistics
+(3 rows)
+
+explain (costs off) select count(*) from temp_table_test_statistics group by tableoid;
+                  QUERY PLAN                  
+----------------------------------------------
+ HashAggregate
+   Group Key: tableoid
+   ->  Seq Scan on temp_table_test_statistics
+(3 rows)
+
+--
+-- test temp table with toast table
+--
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |                0 |        483328 |           98304 |                 581632
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            32768 |                  |         32768 |               0 |                  32768
+(3 rows)
+
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |             8192 |        499712 |          106496 |                 606208
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            40960 |                  |         40960 |               0 |                  40960
+(3 rows)
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,10),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',10);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |             8192 |                0 |         16384 |           32768 |                  49152
+ idx_gtt_t_kenyon_1 |            16384 |                  |         16384 |               0 |                  16384
+ idx_gtt_t_kenyon_2 |            16384 |                  |         16384 |               0 |                  16384
+(3 rows)
+
+rollback;
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |             8192 |        499712 |          106496 |                 606208
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            40960 |                  |         40960 |               0 |                  40960
+(3 rows)
+
+-- test analyze/vacuum on global temp table
+ANALYZE gtt_t_kenyon;
+VACUUM gtt_t_kenyon;
+--
+-- test global temp table system view
+--
+create global temp table temp_table_test_systemview(a int primary key, b text) on commit PRESERVE rows;
+-- should empty, storage not initialized
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+ tablename 
+-----------
+(0 rows)
+
+-- should empty, storage not initialized
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into temp_table_test_systemview values(generate_series(1,10000),'test');
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+         tablename          
+----------------------------
+ temp_table_test_systemview
+(1 row)
+
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+       schemaname       |            tablename            | relpages | reltuples | relallvisible 
+------------------------+---------------------------------+----------+-----------+---------------
+ global_temporary_table | temp_table_test_systemview      |        0 |         0 |             0
+ global_temporary_table | temp_table_test_systemview_pkey |        1 |         0 |             0
+(2 rows)
+
+-- should empty
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+analyze temp_table_test_systemview;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+       schemaname       |            tablename            | relpages | reltuples | relallvisible 
+------------------------+---------------------------------+----------+-----------+---------------
+ global_temporary_table | temp_table_test_systemview      |       55 |     10000 |             0
+ global_temporary_table | temp_table_test_systemview_pkey |       30 |     10000 |             0
+(2 rows)
+
+-- get data after analyze;
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+       schemaname       |         tablename          | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------------------+----------------------------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ global_temporary_table | temp_table_test_systemview | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ global_temporary_table | temp_table_test_systemview | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+-- get all object info in current schema
+select relname ,relkind, relpersistence, reloptions from pg_class c, pg_namespace n where c.relnamespace = n.oid and n.nspname = 'global_temporary_table' order by relkind,relpersistence,relname;
+                     relname                     | relkind | relpersistence |          reloptions           
+-------------------------------------------------+---------+----------------+-------------------------------
+ global_temp_partition_01_2021_id_seq            | S       | g              | {on_commit_delete_rows=false}
+ global_temp_with_serial_a_seq                   | S       | g              | {on_commit_delete_rows=false}
+ regular_partition01_id_seq                      | S       | p              | 
+ regular_partition_01_2019_id_seq                | S       | p              | 
+ seq_1                                           | S       | p              | 
+ gtt_idx_1                                       | i       | g              | 
+ gtt_idx_2                                       | i       | g              | 
+ gtt_idx_3                                       | i       | g              | 
+ gtt_on_commit_default_pkey                      | i       | g              | 
+ gtt_on_commit_delete_pkey                       | i       | g              | 
+ gtt_on_commit_preserve_pkey                     | i       | g              | 
+ gtt_test_alter1_pkey                            | i       | g              | 
+ gtt_test_rename_pkey                            | i       | g              | 
+ idx_b                                           | i       | g              | 
+ idx_gtt_t_kenyon_1                              | i       | g              | 
+ idx_gtt_t_kenyon_2                              | i       | g              | 
+ idx_gtt_test_alter_b                            | i       | g              | 
+ idx_test_1                                      | i       | g              | 
+ idx_test_2                                      | i       | g              | 
+ products_pkey                                   | i       | g              | 
+ temp_orders_2_pkey                              | i       | g              | 
+ temp_orders_pkey                                | i       | g              | 
+ temp_products_pkey                              | i       | g              | 
+ temp_table_test_systemview_pkey                 | i       | g              | 
+ temp_table_with_sequence_oncommit_delete_pkey   | i       | g              | 
+ temp_table_with_sequence_oncommit_preserve_pkey | i       | g              | 
+ regular_partition01                             | p       | p              | 
+ global_temp_partition_01_2021                   | r       | g              | {on_commit_delete_rows=true}
+ global_temp_with_serial                         | r       | g              | {on_commit_delete_rows=false}
+ gtt_on_commit_default                           | r       | g              | {on_commit_delete_rows=false}
+ gtt_on_commit_delete                            | r       | g              | {on_commit_delete_rows=true}
+ gtt_on_commit_delete2                           | r       | g              | {on_commit_delete_rows=true}
+ gtt_on_commit_preserve                          | r       | g              | {on_commit_delete_rows=false}
+ gtt_t_kenyon                                    | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_alter                                  | r       | g              | {on_commit_delete_rows=true}
+ gtt_test_alter1                                 | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_createindex                            | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_new                                    | r       | g              | {on_commit_delete_rows=false}
+ inherits_parent_global_temp                     | r       | g              | {on_commit_delete_rows=true}
+ products                                        | r       | g              | {on_commit_delete_rows=false}
+ temp_orders                                     | r       | g              | {on_commit_delete_rows=true}
+ temp_orders_2                                   | r       | g              | {on_commit_delete_rows=false}
+ temp_products                                   | r       | g              | {on_commit_delete_rows=false}
+ temp_table_test_statistics                      | r       | g              | {on_commit_delete_rows=false}
+ temp_table_test_systemview                      | r       | g              | {on_commit_delete_rows=false}
+ temp_table_with_sequence_oncommit_delete        | r       | g              | {on_commit_delete_rows=true}
+ temp_table_with_sequence_oncommit_preserve      | r       | g              | {on_commit_delete_rows=false}
+ foo                                             | r       | p              | 
+ inherits_parent                                 | r       | p              | 
+ regular_partition_01_2019                       | r       | p              | 
+(50 rows)
+
+reset search_path;
+drop schema global_temporary_table cascade;
+NOTICE:  drop cascades to 25 other objects
+DETAIL:  drop cascades to table global_temporary_table.gtt_on_commit_default
+drop cascades to table global_temporary_table.gtt_on_commit_delete
+drop cascades to table global_temporary_table.gtt_on_commit_delete2
+drop cascades to table global_temporary_table.gtt_on_commit_preserve
+drop cascades to table global_temporary_table.gtt_test_new
+drop cascades to table global_temporary_table.gtt_test_createindex
+drop cascades to table global_temporary_table.gtt_test_alter
+drop cascades to table global_temporary_table.regular_partition_01_2019
+drop cascades to table global_temporary_table.regular_partition01
+drop cascades to table global_temporary_table.global_temp_partition_01_2021
+drop cascades to table global_temporary_table.inherits_parent
+drop cascades to table global_temporary_table.inherits_parent_global_temp
+drop cascades to table global_temporary_table.gtt_test_alter1
+drop cascades to table global_temporary_table.foo
+drop cascades to table global_temporary_table.temp_products
+drop cascades to table global_temporary_table.products
+drop cascades to table global_temporary_table.temp_orders
+drop cascades to table global_temporary_table.temp_orders_2
+drop cascades to table global_temporary_table.global_temp_with_serial
+drop cascades to sequence global_temporary_table.seq_1
+drop cascades to table global_temporary_table.temp_table_with_sequence_oncommit_delete
+drop cascades to table global_temporary_table.temp_table_with_sequence_oncommit_preserve
+drop cascades to table global_temporary_table.temp_table_test_statistics
+drop cascades to table global_temporary_table.gtt_t_kenyon
+drop cascades to table global_temporary_table.temp_table_test_systemview
+-- should empty
+select * from pg_list_gtt_relfrozenxids();
+ pid | relfrozenxid 
+-----+--------------
+(0 rows)
+
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2fa00a3c29a..e0001bc3448 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1359,6 +1359,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 7be89178f0f..db8095d30bf 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -130,3 +130,6 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: global_temporary_table
diff --git a/src/test/regress/sql/global_temporary_table.sql b/src/test/regress/sql/global_temporary_table.sql
new file mode 100644
index 00000000000..1c45de23294
--- /dev/null
+++ b/src/test/regress/sql/global_temporary_table.sql
@@ -0,0 +1,299 @@
+--
+-- GLobal emparary table test case 
+--
+
+CREATE SCHEMA IF NOT EXISTS global_temporary_table;
+set search_path=global_temporary_table,sys;
+
+--
+--  test create global temp table basic syntax
+--
+create global temp table gtt_on_commit_default(a int primary key, b text);
+create global temp table gtt_on_commit_delete(a int primary key, b text) on commit delete rows;
+create global temp table gtt_on_commit_delete2(n int) with (on_commit_delete_rows='true');
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit PRESERVE rows;
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt_test_createindex(c0 tsvector,c1 varchar(100), c2 int);
+create global temp table gtt_test_alter(b text) with(on_commit_delete_rows=true);
+--
+-- test DML on global temp table
+--
+
+-- update empty temp table
+update gtt_on_commit_delete set b ='test';
+begin;
+insert into gtt_on_commit_delete values (1);
+update gtt_on_commit_delete set b ='test';
+-- should 1 row
+select * from gtt_on_commit_delete;
+commit;
+-- data delete after transaction commit
+-- should 0 row
+select * from gtt_on_commit_delete;
+
+-- update empty temp table
+update gtt_on_commit_preserve set b ='test';
+insert into gtt_on_commit_preserve values (2);
+begin;
+insert into gtt_on_commit_preserve values (3);
+update gtt_on_commit_preserve set b ='test';
+-- should 2 row
+select * from gtt_on_commit_preserve order by a;
+delete from gtt_on_commit_preserve where a=2;
+commit;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+
+begin;
+insert into gtt_on_commit_preserve values (4);
+-- temp table support truncate;
+truncate gtt_on_commit_preserve;
+select * from gtt_on_commit_preserve order by a;
+rollback;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+
+--
+-- test unsupported global temp partition table
+--
+
+-- should fail
+CREATE global temp TABLE global_temp_partition_01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+on commit delete rows;
+
+CREATE TABLE regular_partition_01_2019 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+);
+
+CREATE TABLE regular_partition01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time);
+
+-- should fail
+CREATE global temp TABLE temp_partition01_2018
+PARTITION OF regular_partition01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE global_temp_partition_01_2021 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+)on commit delete rows;
+
+-- should fail
+ALTER TABLE regular_partition01 ATTACH PARTITION global_temp_partition_01_2021 FOR VALUES FROM ('2021-01-01 00:00:00') TO ('2022-01-01 00:00:00');
+
+--
+-- test unsupported inherit table
+--
+create table 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 inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent);
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent_global_temp) on commit delete rows;
+
+--
+-- test DDL on global temp table
+--
+create index idx_gtt_test_alter_b on gtt_test_alter (b);
+insert into gtt_test_alter values('test');
+alter table gtt_test_alter alter b type varchar;
+create index gtt_idx_1 on gtt_test_createindex using gin (c0);
+create index gtt_idx_2 on gtt_test_createindex using gist (c0);
+create index gtt_idx_3 on gtt_test_createindex using hash (c2);
+alter table gtt_test_rename rename to gtt_test_new;
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+create index CONCURRENTLY idx_b on gtt_on_commit_default (b);
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+insert into gtt_on_commit_default values(1,'test');
+create global temp table gtt_test_alter1 (a int primary key,b text);
+alter table gtt_test_alter1 alter a type varchar;
+-- should fail
+alter table gtt_on_commit_default alter a type varchar;
+-- should fail
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) on commit delete rows;
+-- should fail
+alter table gtt_on_commit_default SET TABLESPACE pg_default;
+-- should fail
+alter table gtt_on_commit_default set ( on_commit_delete_rows='true');
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) with(on_commit_delete_rows=true);
+-- should fail
+create or replace global temp view gtt_v as select 5;
+create table foo();
+-- should fail
+alter table foo set (on_commit_delete_rows='true');
+-- should fail
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit drop;
+-- should fail
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+-- should fail
+CREATE MATERIALIZED VIEW mv_gtt_on_commit_default as select * from gtt_on_commit_default;
+
+--
+-- test foreign key dependencies for global temp table
+--
+CREATE global temp TABLE temp_products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+CREATE global temp TABLE temp_orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+)on commit delete rows;
+
+-- should fail
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+);
+
+CREATE global temp TABLE temp_orders_2 (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products(product_no),
+    quantity integer
+);
+
+--should fail
+insert into temp_orders values(1,1,1);
+
+insert into temp_products values(1,'test',1.0);
+begin;
+insert into temp_orders values(1,1,1);
+commit;
+-- should 1 row
+select count(*) from temp_products;
+-- should 0 row
+select count(*) from temp_orders;
+
+--
+-- test sequence on global temp table
+--
+create global temp table global_temp_with_serial (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+-- support insert data to temp table in read only transaction
+insert into global_temp_with_serial (b) values(1);
+select * from global_temp_with_serial;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_delete(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_preserve(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table temp_table_with_sequence_oncommit_delete add c2 int default nextval('seq_1');
+alter table temp_table_with_sequence_oncommit_preserve add c2 int default nextval('seq_1');
+begin;
+insert into temp_table_with_sequence_oncommit_delete (c1)values(1);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(2);
+insert into temp_table_with_sequence_oncommit_delete (c1)values(3);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(4);
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+commit;
+-- should 0 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_preserve order by c1;
+
+--
+-- test statistics on temp table
+--
+create global temp table temp_table_test_statistics(a int);
+insert into temp_table_test_statistics values(generate_series(1,100000));
+create index idx_test_1 on temp_table_test_statistics (a);
+create index idx_test_2 on temp_table_test_statistics((a*10));
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+-- test statistic for whole row
+explain (costs off) select count(*) from temp_table_test_statistics group by temp_table_test_statistics;
+-- test statistic for system column
+explain (costs off) select count(*) from temp_table_test_statistics group by tableoid;
+analyze temp_table_test_statistics;
+-- indexscan by idx_test_1
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+-- indexscan by idx_test_2
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+explain (costs off) select count(*) from temp_table_test_statistics group by temp_table_test_statistics;
+explain (costs off) select count(*) from temp_table_test_statistics group by tableoid;
+
+--
+-- test temp table with toast table
+--
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,10),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',10);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+rollback;
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+
+-- test analyze/vacuum on global temp table
+ANALYZE gtt_t_kenyon;
+VACUUM gtt_t_kenyon;
+
+--
+-- test global temp table system view
+--
+create global temp table temp_table_test_systemview(a int primary key, b text) on commit PRESERVE rows;
+-- should empty, storage not initialized
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+-- should empty, storage not initialized
+select count(*) from pg_list_gtt_relfrozenxids();
+insert into temp_table_test_systemview values(generate_series(1,10000),'test');
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+select count(*) from pg_list_gtt_relfrozenxids();
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+-- should empty
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+analyze temp_table_test_systemview;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+-- get data after analyze;
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+
+-- get all object info in current schema
+select relname ,relkind, relpersistence, reloptions from pg_class c, pg_namespace n where c.relnamespace = n.oid and n.nspname = 'global_temporary_table' order by relkind,relpersistence,relname;
+
+reset search_path;
+drop schema global_temporary_table cascade;
+-- should empty
+select * from pg_list_gtt_relfrozenxids();
+
-- 
2.30.1 (Apple Git-130)

0003-gtt-v60-implementation.patchapplication/octet-stream; name=0003-gtt-v60-implementation.patchDownload
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 774a70f63df..692455ead0a 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -18,6 +18,7 @@
 #include "access/toast_internals.h"
 #include "access/visibilitymap.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "funcapi.h"
 #include "miscadmin.h"
 #include "storage/bufmgr.h"
@@ -226,6 +227,8 @@ verify_heapam(PG_FUNCTION_ARGS)
 	BlockNumber last_block;
 	BlockNumber nblocks;
 	const char *skip;
+	TransactionId	relfrozenxid = InvalidTransactionId;
+	MultiXactId		relminmxid = InvalidMultiXactId;
 
 	/* Check to see if caller supports us returning a tuplestore */
 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -342,6 +345,13 @@ verify_heapam(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(ctx.rel) &&
+		!gtt_storage_attached(RelationGetRelid(ctx.rel)))
+	{
+		relation_close(ctx.rel, AccessShareLock);
+		PG_RETURN_NULL();
+	}
+
 	/* Early exit if the relation is empty */
 	nblocks = RelationGetNumberOfBlocks(ctx.rel);
 	if (!nblocks)
@@ -409,9 +419,25 @@ verify_heapam(PG_FUNCTION_ARGS)
 
 	update_cached_xid_range(&ctx);
 	update_cached_mxid_range(&ctx);
-	ctx.relfrozenxid = ctx.rel->rd_rel->relfrozenxid;
+
+	if (RELATION_IS_GLOBAL_TEMP(ctx.rel))
+	{
+		get_gtt_relstats(RelationGetRelid(ctx.rel),
+						NULL,
+						NULL,
+						NULL,
+						&relfrozenxid,
+						&relminmxid);
+	}
+	else
+	{
+		relfrozenxid = ctx.rel->rd_rel->relfrozenxid;
+		relminmxid = ctx.rel->rd_rel->relminmxid;
+	}
+
+	ctx.relfrozenxid = relfrozenxid;
 	ctx.relfrozenfxid = FullTransactionIdFromXidAndCtx(ctx.relfrozenxid, &ctx);
-	ctx.relminmxid = ctx.rel->rd_rel->relminmxid;
+	ctx.relminmxid = relminmxid;
 
 	if (TransactionIdIsNormal(ctx.relfrozenxid))
 		ctx.oldest_xid = ctx.relfrozenxid;
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index b5602f53233..21b2d2a9527 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -159,6 +159,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * In order to avoid consistency problems, the global temporary table
+	 * uses ShareUpdateExclusiveLock.
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temporary table on commit options",
+			RELOPT_KIND_HEAP,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},
 	/* list terminator */
 	{{NULL}}
 };
@@ -1834,6 +1847,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 43ba03b6eb9..49f1052fdb1 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1023,7 +1023,7 @@ gistproperty(Oid index_oid, int attno,
 XLogRecPtr
 gistGetFakeLSN(Relation rel)
 {
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	if (RELATION_IS_TEMP(rel))
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index eb3810494f2..cbd22909582 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -151,7 +151,7 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	if (!RELATION_IS_TEMP(index))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index ec234a5e595..eb9f7f0eb48 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -52,6 +52,7 @@
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/atomics.h"
@@ -5844,6 +5845,19 @@ heap_abort_speculative(Relation relation, ItemPointer tid)
 	BlockNumber block;
 	Buffer		buffer;
 	TransactionId prune_xid;
+	TransactionId relfrozenxid = InvalidTransactionId;
+
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		get_gtt_relstats(RelationGetRelid(relation),
+						NULL,
+						NULL,
+						NULL,
+						&relfrozenxid,
+						NULL);
+	}
+	else
+		relfrozenxid = relation->rd_rel->relfrozenxid;
 
 	Assert(ItemPointerIsValid(tid));
 
@@ -5896,8 +5910,8 @@ heap_abort_speculative(Relation relation, ItemPointer tid)
 	 * TransactionXmin, so there's no race here).
 	 */
 	Assert(TransactionIdIsValid(TransactionXmin));
-	if (TransactionIdPrecedes(TransactionXmin, relation->rd_rel->relfrozenxid))
-		prune_xid = relation->rd_rel->relfrozenxid;
+	if (TransactionIdPrecedes(TransactionXmin, relfrozenxid))
+		prune_xid = relfrozenxid;
 	else
 		prune_xid = TransactionXmin;
 	PageSetPrunable(page, prune_xid);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 9befe012a9e..26fce0c4b83 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -593,7 +593,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
@@ -645,7 +645,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(RelationGetSmgr(rel), dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 558cc88a08a..0076666971e 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -63,6 +63,7 @@
 #include "access/xlog.h"
 #include "catalog/index.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -507,6 +508,25 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 	TransactionId OldestXmin;
 	TransactionId FreezeLimit;
 	MultiXactId MultiXactCutoff;
+	TransactionId	relfrozenxid = InvalidTransactionId;
+	MultiXactId		relminmxid = InvalidMultiXactId;
+	double			reltuples = 0;
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		get_gtt_relstats(RelationGetRelid(rel),
+						NULL,
+						&reltuples,
+						NULL,
+						&relfrozenxid,
+						&relminmxid);
+	}
+	else
+	{
+		relfrozenxid = rel->rd_rel->relfrozenxid;
+		relminmxid = rel->rd_rel->relminmxid;
+		reltuples = rel->rd_rel->reltuples;
+	}
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
@@ -542,9 +562,9 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 	 * table's minimum MultiXactId is older than or equal to the requested
 	 * mxid full-table scan limit; or if DISABLE_PAGE_SKIPPING was specified.
 	 */
-	aggressive = TransactionIdPrecedesOrEquals(rel->rd_rel->relfrozenxid,
+	aggressive = TransactionIdPrecedesOrEquals(relfrozenxid,
 											   xidFullScanLimit);
-	aggressive |= MultiXactIdPrecedesOrEquals(rel->rd_rel->relminmxid,
+	aggressive |= MultiXactIdPrecedesOrEquals(relminmxid,
 											  mxactFullScanLimit);
 	if (params->options & VACOPT_DISABLE_PAGE_SKIPPING)
 		aggressive = true;
@@ -591,9 +611,9 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 	}
 
 	vacrel->bstrategy = bstrategy;
-	vacrel->relfrozenxid = rel->rd_rel->relfrozenxid;
-	vacrel->relminmxid = rel->rd_rel->relminmxid;
-	vacrel->old_live_tuples = rel->rd_rel->reltuples;
+	vacrel->relfrozenxid = relfrozenxid;
+	vacrel->relminmxid = relminmxid;
+	vacrel->old_live_tuples = reltuples;
 
 	/* Set cutoffs for entire VACUUM */
 	vacrel->OldestXmin = OldestXmin;
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 5bc7c3616a9..0b261f723d7 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -677,6 +678,14 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		/*
+		 * If a global temporary table storage file is not initialized in the
+		 * current backend, its index does not have a root page, just returns 0.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 4e6efda97f3..ae12fd91a1b 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -44,6 +44,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index aa7d4d5456b..595cb03eb4a 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -504,6 +504,7 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 81cc39fb70e..0bf05f37a80 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -62,6 +62,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,
@@ -366,13 +368,26 @@ heap_create(const char *relname,
 			break;
 	}
 
+	/* For global temporary table, even if the storage is not initialized,
+	 * the relfilenode needs to be generated and put into the catalog.
+	 */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		create_storage = false;
+		if (OidIsValid(relfilenode))
+			elog(ERROR, "global temporary table can not reuse an existing relfilenode");
+
+		relfilenode = relid;
+	}
 	/*
 	 * Decide whether to create storage. If caller passed a valid relfilenode,
 	 * storage is already created, so don't do it here.  Also don't create it
 	 * for relkinds without physical storage.
 	 */
-	if (!RELKIND_HAS_STORAGE(relkind) || OidIsValid(relfilenode))
+	else if (!RELKIND_HAS_STORAGE(relkind) || OidIsValid(relfilenode))
+	{
 		create_storage = false;
+	}
 	else
 	{
 		create_storage = true;
@@ -427,7 +442,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:
@@ -445,7 +460,8 @@ heap_create(const char *relname,
 	 * protected by the existence of a physical file; but for relations with
 	 * no files, add a pg_shdepend entry to account for that.
 	 */
-	if (!create_storage && reltablespace != InvalidOid)
+	if (!create_storage && reltablespace != InvalidOid &&
+		relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
 		recordDependencyOnTablespace(RelationRelationId, relid,
 									 reltablespace);
 
@@ -1001,6 +1017,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -1039,8 +1056,21 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/*
+	 * The transaction information of the global temporary table is stored
+	 * in the local hash table, not in catalog.
+	 */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1413,6 +1443,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1498,8 +1529,9 @@ heap_create_with_catalog(const char *relname,
 	/*
 	 * If there's a special on-commit action, remember it
 	 */
-	if (oncommit != ONCOMMIT_NOOP)
-		register_on_commit_action(relid, oncommit);
+	if (oncommit != ONCOMMIT_NOOP &&
+		relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+		register_on_commit_action(relid, oncommit, false);
 
 	/*
 	 * ok, the relation has been cataloged, so close our relations and return
@@ -1996,6 +2028,19 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/*
+	 * Only when other sessions are not using this Global temporary table,
+	 * is it allowed to DROP it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot drop global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3280,7 +3325,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3292,7 +3337,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3328,7 +3373,7 @@ RelationTruncateIndexes(Relation heapRelation)
  * ON COMMIT truncation of temporary tables, where it doesn't matter.
  */
 void
-heap_truncate(List *relids)
+heap_truncate(List *relids, bool is_global_temp)
 {
 	List	   *relations = NIL;
 	ListCell   *cell;
@@ -3338,8 +3383,23 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode;
+
+		/*
+		 * Truncate global temporary table only clears local data,
+		 * so only low-level locks need to be held.
+		 */
+		if (is_global_temp)
+		{
+			if (!gtt_storage_attached(rid))
+				continue;
 
-		rel = table_open(rid, AccessExclusiveLock);
+			lockmode = RowExclusiveLock;
+		}
+		else
+			lockmode = AccessExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3372,6 +3432,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3380,23 +3441,39 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	/*
+	 * Truncate GTT only clears local data, so only low-level locks
+	 * need to be held.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		lockmode = AccessShareLock;
+	else
+		lockmode = AccessExclusiveLock;
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	/*
+	 * After the data is cleaned up on the GTT, the transaction information
+	 * for the data(stored in local hash table) is also need reset.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(RelationGetRelid(rel), 0, 0, 0, RecentXmin, GetOldestMultiXactId());
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index c255806e38c..7a52322ede4 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -54,6 +54,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"
@@ -734,6 +735,25 @@ index_create(Relation heapRelation,
 	TransactionId relfrozenxid;
 	MultiXactId relminmxid;
 
+	/* For global temporary table only */
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temporary table with concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot reindex global temporary tables concurrently")));
+
+		/*
+		 * For the case that some backend is applied relcache message to create
+		 * an index on a global temporary table, if this table in the current
+		 * backend are not initialized, the creation of index storage on the
+		 * table are also skipped.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/* constraint flags can only be set when a constraint is requested */
 	Assert((constr_flags == 0) ||
 		   ((flags & INDEX_CREATE_ADD_CONSTRAINT) != 0));
@@ -2117,7 +2137,7 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 * lock (see comments in RemoveRelations), and a non-concurrent DROP is
 	 * more efficient.
 	 */
-	Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+	Assert(!RelpersistenceTsTemp(get_rel_persistence(indexId)) ||
 		   (!concurrent && !concurrent_lock_mode));
 
 	/*
@@ -2149,6 +2169,21 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/*
+	 * Allow to drop index on global temporary table when only current
+	 * backend use it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation) &&
+		is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+	{
+		ereport(ERROR,
+			(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+			 errmsg("cannot drop index %s on global temporary table %s",
+					RelationGetRelationName(userIndexRelation), RelationGetRelationName(userHeapRelation)),
+					errdetail("Because the index is created on the global temporary table and other backend attached it."),
+					errhint("Please try detach all sessions using this temporary table and try again.")));
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2757,6 +2792,7 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = RELATION_IS_GLOBAL_TEMP(rel);
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2851,20 +2887,37 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
-		{
-			rd_rel->relpages = (int32) relpages;
-			dirty = true;
-		}
-		if (rd_rel->reltuples != (float4) reltuples)
+		/* For global temporary table */
+		if (is_gtt)
 		{
-			rd_rel->reltuples = (float4) reltuples;
-			dirty = true;
+			/* Update GTT'statistics into local relcache */
+			rel->rd_rel->relpages = (int32) relpages;
+			rel->rd_rel->reltuples = (float4) reltuples;
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+
+			/* Update GTT'statistics into local hashtable */
+			up_gtt_relstats(RelationGetRelid(rel), relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+		else
 		{
-			rd_rel->relallvisible = (int32) relallvisible;
-			dirty = true;
+			if (rd_rel->relpages != (int32) relpages)
+			{
+				rd_rel->relpages = (int32) relpages;
+				dirty = true;
+			}
+
+			if (rd_rel->reltuples != (float4) reltuples)
+			{
+				rd_rel->reltuples = (float4) reltuples;
+				dirty = true;
+			}
+
+			if (rd_rel->relallvisible != (int32) relallvisible)
+			{
+				rd_rel->relallvisible = (int32) relallvisible;
+				dirty = true;
+			}
 		}
 	}
 
@@ -2977,6 +3030,26 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, progress_index, progress_vals);
 	}
 
+	/* For build index on global temporary table */
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		/*
+		 * If the storage for the index in this session is not initialized,
+		 * it needs to be created.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			/* Before create init storage, fix the local Relcache first */
+			force_enable_gtt_index(indexRelation);
+
+			Assert(gtt_storage_attached(RelationGetRelid(heapRelation)));
+
+			/* Init storage for index */
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3518,6 +3591,8 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	PGRUsage	ru0;
 	bool		progress = ((params->options & REINDEXOPT_REPORT_PROGRESS) != 0);
 	bool		set_tablespace = false;
+	LOCKMODE	lockmode_on_heap = ShareLock;
+	LOCKMODE	lockmode_on_index = AccessExclusiveLock;
 
 	pg_rusage_init(&ru0);
 
@@ -3531,10 +3606,29 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	if (!OidIsValid(heapId))
 		return;
 
+	/*
+	 * For reindex on global temporary table, If the storage for the index
+	 * in current backend is not initialized, nothing is done.
+	 */
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		if (!gtt_storage_attached(indexId))
+		{
+			/* Suppress use of the target index while rebuilding it */
+			SetReindexProcessing(heapId, indexId);
+			/* Re-allow use of target index */
+			ResetReindexProcessing();
+			return;
+		}
+
+		lockmode_on_heap = AccessShareLock;
+		lockmode_on_index = AccessShareLock;
+	}
+
 	if ((params->options & REINDEXOPT_MISSING_OK) != 0)
-		heapRelation = try_table_open(heapId, ShareLock);
+		heapRelation = try_table_open(heapId, lockmode_on_heap);
 	else
-		heapRelation = table_open(heapId, ShareLock);
+		heapRelation = table_open(heapId, lockmode_on_heap);
 
 	/* if relation is gone, leave */
 	if (!heapRelation)
@@ -3560,7 +3654,7 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	 * Open the target index relation and get an exclusive lock on it, to
 	 * ensure that no one else is touching this particular index.
 	 */
-	iRel = index_open(indexId, AccessExclusiveLock);
+	iRel = index_open(indexId, lockmode_on_index);
 
 	if (progress)
 		pgstat_progress_update_param(PROGRESS_CREATEIDX_ACCESS_METHOD_OID,
@@ -3811,6 +3905,12 @@ reindex_relation(Oid relid, int flags, ReindexParams *params)
 	bool		result;
 	ListCell   *indexId;
 	int			i;
+	LOCKMODE	lockmode;
+
+	if (flags & REINDEX_REL_PROCESS_GLOBAL_TEMP)
+		lockmode = AccessShareLock;
+	else
+		lockmode = ShareLock;
 
 	/*
 	 * Open and lock the relation.  ShareLock is sufficient since we only need
@@ -3818,9 +3918,9 @@ reindex_relation(Oid relid, int flags, ReindexParams *params)
 	 * should match ReindexTable().
 	 */
 	if ((params->options & REINDEXOPT_MISSING_OK) != 0)
-		rel = try_table_open(relid, ShareLock);
+		rel = try_table_open(relid, lockmode);
 	else
-		rel = table_open(relid, ShareLock);
+		rel = table_open(relid, lockmode);
 
 	/* if relation is gone, leave */
 	if (!rel)
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 4de8400fd0f..fe3fcc712cb 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -656,6 +656,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/* Do not allow create global temporary table in temporary schemas */
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp table in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index c5ad28d71fe..707068a6fd8 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,6 +27,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -61,6 +62,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			temprelOid;			/* InvalidOid if not a global temporary rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -115,7 +117,7 @@ AddPendingSync(const RelFileNode *rnode)
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -126,7 +128,12 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/*
+		 * Global temporary table and local temporary table use same
+		 * design on storage module.
+		 */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -154,6 +161,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
@@ -165,6 +173,21 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		AddPendingSync(&rnode);
 	}
 
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		Assert(rel && RELATION_IS_GLOBAL_TEMP(rel));
+
+		/*
+		 * Remember the reloid of global temporary table, which is used for
+		 * transaction commit or rollback.
+		 * see smgrDoPendingDeletes.
+		 */
+		pending->temprelOid = RelationGetRelid(rel);
+
+		/* Remember global temporary table storage info to localhash */
+		remember_gtt_storage_info(rnode, rel);
+	}
+
 	return srel;
 }
 
@@ -201,11 +224,20 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/*
+	 * Remember the reloid of global temporary table, which is used for
+	 * transaction commit or rollback.
+	 * see smgrDoPendingDeletes.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->temprelOid = RelationGetRelid(rel);
+
 	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
@@ -618,6 +650,7 @@ smgrDoPendingDeletes(bool isCommit)
 	int			nrels = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -647,14 +680,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->temprelOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -664,12 +701,21 @@ smgrDoPendingDeletes(bool isCommit)
 
 	if (nrels > 0)
 	{
+		int	i;
+
 		smgrdounlinkall(srels, nrels, false);
 
-		for (int i = 0; i < nrels; i++)
+		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* Delete global temporary table info in localhash */
+			if (gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 00000000000..2011e4cc86e
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1509 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  The body implementation of Global Temparary table.
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *	  See src/backend/catalog/GTT_README for Global temparary table's
+ *	  requirements and design.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_info_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+
+int		vacuum_gtt_defer_check_age = 0;
+
+/*
+ * The Global temporary table's shared hash table data structure
+ */
+typedef struct gtt_ctl_data
+{
+	LWLock		lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+/* record this global temporary table in which backends are being used */
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+/*
+ * The Global temporary table's local hash table data structure
+ */
+/* Record the storage information and statistical information of the global temporary table */
+typedef struct
+{
+	Oid			relfilenode;
+	Oid			spcnode;
+
+	/* pg_class relstat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+
+	/* pg_statistic column stat */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_free_statistics(gtt_relfilenode *rnode);
+static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok);
+static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok);
+static Bitmapset *copy_active_gtt_bitmap(Oid relid);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+/*
+ * Calculate shared hash table entry size for GTT.
+ */
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	/* hash entry header size */
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	/*
+	 * hash entry data size
+	 * this is a bitmap in shared memory, each backend have a bit.
+	 */
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+/*
+ * Calculate shared hash table max size for GTT.
+ */
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	/* shared hash header size */
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	/* hash entry size */
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	/* max size */
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+/*
+ * Initialization shared hash table for GTT.
+ */
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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();
+	}
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(gtt_fnode);
+	info.entrysize = action_gtt_shared_hash_entry_size();
+	active_gtt_shared_hash =
+		ShmemInitHash("active gtt shared hash",
+						gtt_shared_ctl->max_entry,
+						gtt_shared_ctl->max_entry,
+						&info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+/*
+ * Record GTT relid to shared hash table, which means that current backend is using this GTT.
+ */
+static void
+gtt_storage_checkin(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			found;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *)&(fnode), 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_global_temporary_table.")));
+	}
+
+	if (!found)
+	{
+		int			wordnum;
+
+		/* init bitmap */
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	/* record itself in bitmap */
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+/*
+ * Remove the GTT relid record from the shared hash table which means that current backend is
+ * not use this GTT.
+ */
+static void
+gtt_storage_checkout(Oid relid, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		if (isCommit)
+			elog(WARNING, "relid %u not exist in gtt shared hash when drop local storage", relid);
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+	/* remove itself from bitmap */
+	bms_del_member(entry->map, MyBackendId);
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return;
+}
+
+/*
+ * Gets usage information for a GTT from shared hash table.
+ * The information is in the form of bitmap.
+ * Quickly copy the entire bitmap from shared memory and return it.
+ * that to avoid holding locks for a long time.
+ */
+static Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	Bitmapset		*map_copy = NULL;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry)
+	{
+		Assert(entry->map);
+		/* copy the entire bitmap */
+		if (!bms_is_empty(entry->map))
+			map_copy = bms_copy(entry->map);
+	}
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return map_copy;
+}
+
+/*
+ * Check if there are other backends using this GTT besides the current backend.
+ */
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			in_use = false;
+	int			num_use = 0;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		return false;
+	}
+
+	Assert(entry->map);
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+	/* how many backend are using this GTT */
+	num_use = bms_num_members(entry->map);
+	if (num_use == 0)
+		in_use = false;
+	else if (num_use == 1)
+	{
+		/* check if this is itself */
+		if(bms_is_member(MyBackendId, entry->map))
+			in_use = false;
+		else
+			in_use = true;
+	}
+	else
+		in_use = true;
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return in_use;
+}
+
+/*
+ * Record GTT information to local hash.
+ * They include GTT storage info, transaction info and statistical info.
+ */
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+	gtt_local_hash_entry		*entry;
+	MemoryContext			oldcontext;
+	gtt_relfilenode			*new_node = NULL;
+	Oid				relid = RelationGetRelid(rel);
+	int				natts = 0;
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	/* First time through: initialize the hash table */
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		HASHCTL		ctl;
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_info_context =
+			AllocSetContextCreate(CacheMemoryContext,
+								"gtt info context",
+								ALLOCSET_DEFAULT_SIZES);
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		ctl.hcxt = gtt_info_context;
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+	}
+
+	Assert(CacheMemoryContext);
+	Assert(gtt_info_context);
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	entry = gtt_search_by_relid(relid, true);
+	if (!entry)
+	{
+		bool		found = false;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash,
+						(void *) &relid, HASH_ENTER, &found);
+
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			/* record the on commit clause */
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS, true);
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			gtt_storage_checkin(relid);
+		}
+	}
+
+	/* record storage info relstat columnstats and transaction info to relfilenode list */
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	new_node->natts = 0;
+	new_node->attnum = NULL;
+	new_node->att_stat_tups = NULL;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* init column stats structure */
+	natts = RelationGetNumberOfAttributes(rel);
+	new_node->attnum = palloc0(sizeof(int) * natts);
+	new_node->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+	new_node->natts = natts;
+
+	/* only heap rel or toast rel have transaction info */
+	if (entry->relkind == RELKIND_RELATION ||
+		entry->relkind == RELKIND_TOASTVALUE)
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+
+	/* Registration callbacks are used to trigger cleanup during process exit */
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+/*
+ * Remove GTT information from local hash when transaction commit/rollback.
+ */
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	gtt_relfilenode		*d_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+		return;
+	}
+
+	d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+		else
+		{
+			/* rollback transaction */
+			if (entry->relfilenode_list == NIL)
+			{
+				if (entry->relkind == RELKIND_RELATION ||
+					entry->relkind == RELKIND_SEQUENCE)
+					gtt_storage_checkout(relid, isCommit);
+
+				hash_search(gtt_storage_local_hash,
+						(void *) &(relid), HASH_REMOVE, NULL);
+			}
+
+			return;
+		}
+	}
+
+	/* Clean up transaction info from Local order list and MyProc */
+	if (entry->relkind == RELKIND_RELATION ||
+		entry->relkind == RELKIND_TOASTVALUE)
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+
+		/* this is valid relfrozenxid */
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	/* delete relfilenode from rel entry */
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	gtt_free_statistics(d_rnode);
+
+	if (entry->relfilenode_list == NIL)
+	{
+		/* tell shared hash that current backend will no longer use this GTT */
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(relid, isCommit);
+
+		hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_REMOVE, NULL);
+	}
+
+	return;
+}
+
+/*
+ * Check if current backend is using this GTT.
+ */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool			found = false;
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (!OidIsValid(relid))
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry)
+		found = true;
+
+	return found;
+}
+
+/*
+ * When backend exit, bulk cleaning all GTT storage and local buffer of this backend.
+ */
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS			status;
+	gtt_local_hash_entry	*entry;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	/* Need to ensure we have a usable transaction. */
+	AbortOutOfAnyTransaction();
+
+	/* Search all relfilenode for GTT in current backend */
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel[1];
+			RelFileNode		rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel[0] = smgropen(rnode, MyBackendId);
+			smgrdounlinkall(srel, 1, false);
+			smgrclose(srel[0]);
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(entry->relid, false);
+
+		hash_search(gtt_storage_local_hash, (void *) &(entry->relid), HASH_REMOVE, NULL);
+	}
+
+	/* set to global area */
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;
+
+	return;
+}
+
+/*
+ * Update GTT relstats(relpage/reltuple/relallvisible)
+ * to local hash.
+ */
+void
+up_gtt_relstats(Oid relid,
+					BlockNumber num_pages,
+					double num_tuples,
+					BlockNumber num_all_visible_pages,
+					TransactionId relfrozenxid,
+					TransactionId relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (!OidIsValid(relid))
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (num_pages > 0 &&
+		gtt_rnode->relpages != (int32)num_pages)
+		gtt_rnode->relpages = (int32)num_pages;
+
+	if (num_tuples > 0 &&
+		gtt_rnode->reltuples != (float4)num_tuples)
+		gtt_rnode->reltuples = (float4)num_tuples;
+
+	/* only heap contain transaction information and relallvisible */
+	if (entry->relkind == RELKIND_RELATION ||
+		entry->relkind == RELKIND_TOASTVALUE)
+	{
+		if (num_all_visible_pages > 0 &&
+			gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+		{
+			gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+		}
+
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNextTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			/* set to local order list */
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			/* set to global area */
+			set_gtt_session_relfrozenxid();
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search GTT relstats(relpage/reltuple/relallvisible)
+ * from local has.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update GTT info(definition is same as pg_statistic)
+ * to local hash.
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+	MemoryContext		oldcontext;
+	bool		found = false;
+	int			i = 0;
+
+	/* not support whole row or system column */
+	if (attnum <= 0)
+		return;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return;
+
+	Assert(entry->relid == reloid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	/* switch context to gtt_info_context for store tuple at heap_form_tuple */
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == 0)
+		{
+			Assert(gtt_rnode->att_stat_tups[i] == NULL);
+			gtt_rnode->attnum[i] = attnum;
+			gtt_rnode->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+			found = true;
+			break;
+		}
+		else if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			heap_freetuple(gtt_rnode->att_stat_tups[i]);
+			gtt_rnode->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+			found = true;
+			break;
+		}
+	}
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!found)
+		elog(WARNING, "analyze can not update relid %u column %d statistics after add or drop column, try truncate table first", reloid, attnum);
+
+	return;
+}
+
+/*
+ * Search GTT statistic info(definition is same as pg_statistic)
+ * from local hash.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	int			i = 0;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	/* not support whole row or system column */
+	if (attnum <= 0)
+		return NULL;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return NULL;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return NULL;
+
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			return gtt_rnode->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Insert a RelfrozenXID into the list and keep the list in order.
+ */
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int		i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Remove a RelfrozenXID from order list gtt_session_relfrozenxid_list.
+ */
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+/*
+ * Update of backend Level oldest relfrozenxid to MyProc.
+ * This makes each backend's oldest RelFrozenxID globally visible.
+ */
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list != NIL)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	if (MyProc->backend_gtt_frozenxid != gtt_frozenxid)
+		MyProc->backend_gtt_frozenxid = gtt_frozenxid;
+}
+
+/*
+ * Get GTT column level data statistics.
+ */
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate		*tupstore;
+	HeapTuple		tuple;
+	Relation		rel = NULL;
+	Oid			reloid = PG_GETARG_OID(0);
+	int			attnum = PG_GETARG_INT32(1);
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	if (!RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	/* get data from local hash */
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum		values[Natts_pg_statistic];
+		bool		isnull[Natts_pg_statistic];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, AccessShareLock);
+	relation_close(pg_tatistic, AccessShareLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get GTT table level data statistics.
+ */
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate	*tupstore;
+	TupleDesc	tupdesc;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid			reloid = PG_GETARG_OID(0);
+	Oid			relnode = 0;
+	BlockNumber		relpages = 0;
+	BlockNumber		relallvisible = 0;
+	uint32			relfrozenxid = 0;
+	uint32			relminmxid = 0;
+	double			reltuples = 0;
+	Relation		rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	if (!RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[Natts_pg_class];
+		bool	isnull[Natts_pg_class];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get a list of backend pids that are currently using this GTT.
+ */
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	PGPROC			*proc = NULL;
+	Bitmapset		*map = NULL;
+	Tuplestorestate		*tupstore;
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	HeapTuple		tuple;
+	Oid			reloid = PG_GETARG_OID(0);
+	Relation		rel = NULL;
+	pid_t			pid = 0;
+	int				backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	if (!RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	/* get data from share hash */
+	map = copy_active_gtt_bitmap(reloid);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			/* backendid map to process pid */
+			proc = BackendIdGetProc(backendid);
+			if (proc && proc->pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, AccessShareLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get backend level oldest relfrozenxid of each backend using GTT in current database.
+ */
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate		*tupstore;
+	int			*pids = NULL;
+	uint32			*xids = NULL;
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	HeapTuple		tuple;
+	int			num_xid = MaxBackends + 1;
+	int			i = 0;
+	int			j = 0;
+	uint32			oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	if (pids == NULL || xids == NULL)
+		elog(ERROR, "out of memory");
+
+	/* Get backend level oldest relfrozenxid in all backend that in MyDatabaseId use GTT */
+	oldest = list_all_backend_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		/* save oldest relfrozenxid */
+		pids[i] = 0;
+		xids[i] = oldest;
+		i++;
+
+		/* save relfrozenxid for each session */
+		for (j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+/*
+ * In order to build the GTT index, force enable GTT'index.
+ */
+void
+force_enable_gtt_index(Relation index)
+{
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(RelationGetRelid(index)));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+/*
+ * Fix the local state of the GTT's index.
+ */
+void
+gtt_fix_index_backend_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid heapOid = index->rd_index->indrelid;
+
+	/* Must be GTT */
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	/*
+	 * If this GTT is not initialized in the current backend,
+	 * its index status is temporarily set to invalid(local relcache).
+	 */
+	if (gtt_storage_attached(heapOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+/*
+ * During the SQL initialization of the executor (InitPlan)
+ * Initialize storage of GTT GTT'indexes and build empty index.
+ */
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int		i;
+	Oid		toastrelid;
+
+	if (!(operation == CMD_UTILITY || operation == CMD_INSERT))
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	/* Each GTT is initialized once in each backend */
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	/* init heap storage */
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	/* rebuild all local index for global temp table */
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		index_build(relation, index, info, true, false);
+
+		/* after build index, index re-enabled */
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+		Assert(info->ii_ReadyForInserts);
+	}
+
+	/* rebuild index for global temp toast table */
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+
+		/* init index storage */
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid			indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+			/* build empty index */
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			Assert(currentIndex->rd_index->indisvalid);
+			Assert(currentIndex->rd_index->indislive);
+			Assert(currentIndex->rd_index->indisready);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+/*
+ * Release the data structure memory used to store GTT storage info.
+ */
+static void
+gtt_free_statistics(gtt_relfilenode *rnode)
+{
+	int i;
+
+	Assert(rnode);
+
+	for (i = 0; i < rnode->natts; i++)
+	{
+		if (rnode->att_stat_tups[i])
+		{
+			heap_freetuple(rnode->att_stat_tups[i]);
+			rnode->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (rnode->attnum)
+		pfree(rnode->attnum);
+
+	if (rnode->att_stat_tups)
+		pfree(rnode->att_stat_tups);
+
+	pfree(rnode);
+
+	return;
+}
+
+/*
+ * Get the current relfilenode of this GTT.
+ */
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
+/*
+ * Get a relfilenode used by this GTT during the transaction life cycle.
+ */
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok)
+{
+	gtt_relfilenode		*rnode = NULL;
+	ListCell		*lc;
+
+	Assert(entry);
+
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == relfilenode)
+		{
+			rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (!missing_ok && rnode == NULL)
+		elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+	return rnode;
+}
+
+/*
+ * Get one GTT info from local hash.
+ */
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry == NULL && !missing_ok)
+		elog(ERROR, "relid %u not found in local hash", relid);
+
+	return entry;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index eb560955cda..abfd0f6e03f 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 4928702aec0..fc6e64a79b6 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -104,7 +105,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -185,6 +186,17 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -601,14 +613,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]));
 		}
 
 		/*
@@ -1622,7 +1635,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;
@@ -1724,31 +1737,48 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/*
+		 * For global temporary table,
+		 * Update column statistic to localhash, not catalog.
+		 */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 9d22f648a84..a44eefa1f6b 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/toasting.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
@@ -390,6 +391,22 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+	{
+		if (gtt_storage_attached(RelationGetRelid(OldHeap)))
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("not support cluster global temporary table yet")));
+
+		relation_close(OldHeap, AccessExclusiveLock);
+		pgstat_progress_end_command();
+		return;
+	}
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
@@ -585,6 +602,8 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
 	TransactionId frozenXid;
 	MultiXactId cutoffMulti;
 
+	Assert(!RELATION_IS_GLOBAL_TEMP(OldHeap));
+
 	/* Mark the correct index as clustered */
 	if (OidIsValid(indexOid))
 		mark_index_clustered(OldHeap, indexOid, true);
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 53f48531419..c03191cce94 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -289,7 +289,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP_ON_CURRENT_SESSION(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, whereClause,
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index f366a818a14..56269b6e38f 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -30,6 +30,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/copyfrom_internal.h"
 #include "commands/progress.h"
@@ -659,6 +660,9 @@ CopyFrom(CopyFromState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	/* Check and init global temporary table storage in current backend */
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	/*
 	 * Set up a ModifyTableState so we can let FDW(s) init themselves for
 	 * foreign-table result relation(s).
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index c14ca27c5ed..d65ae895356 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -111,6 +111,7 @@ struct ReindexIndexCallbackState
 {
 	ReindexParams params;		/* options from statement */
 	Oid			locked_table_oid;	/* tracks previously locked table */
+	LOCKMODE	lockmode;
 };
 
 /*
@@ -570,7 +571,7 @@ DefineIndex(Oid relationId,
 	 * is more efficient.  Do this before any use of the concurrent option is
 	 * done.
 	 */
-	if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+	if (stmt->concurrent && !RelpersistenceTsTemp(get_rel_persistence(relationId)))
 		concurrent = true;
 	else
 		concurrent = false;
@@ -2581,9 +2582,9 @@ ReindexIndex(RangeVar *indexRelation, ReindexParams *params, bool isTopLevel)
 	 */
 	state.params = *params;
 	state.locked_table_oid = InvalidOid;
+	state.lockmode = AccessShareLock;
 	indOid = RangeVarGetRelidExtended(indexRelation,
-									  (params->options & REINDEXOPT_CONCURRENTLY) != 0 ?
-									  ShareUpdateExclusiveLock : AccessExclusiveLock,
+									  AccessShareLock,
 									  0,
 									  RangeVarCallbackForReindexIndex,
 									  &state);
@@ -2594,11 +2595,25 @@ ReindexIndex(RangeVar *indexRelation, ReindexParams *params, bool isTopLevel)
 	 */
 	persistence = get_rel_persistence(indOid);
 	relkind = get_rel_relkind(indOid);
+	if (persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		LOCKMODE	lockmode;
+
+		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0)
+			lockmode = ShareUpdateExclusiveLock;
+		else
+			lockmode = AccessExclusiveLock;
+
+		/* lock heap first */
+		Assert(OidIsValid(state.locked_table_oid));
+		LockRelationOid(state.locked_table_oid, lockmode);
+		LockRelationOid(indOid, lockmode);
+	}
 
 	if (relkind == RELKIND_PARTITIONED_INDEX)
 		ReindexPartitions(indOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 persistence != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(persistence))
 		ReindexRelationConcurrently(indOid, params);
 	else
 	{
@@ -2620,15 +2635,7 @@ RangeVarCallbackForReindexIndex(const RangeVar *relation,
 {
 	char		relkind;
 	struct ReindexIndexCallbackState *state = arg;
-	LOCKMODE	table_lockmode;
-
-	/*
-	 * Lock level here should match table lock in reindex_index() for
-	 * non-concurrent case and table locks used by index_concurrently_*() for
-	 * concurrent case.
-	 */
-	table_lockmode = (state->params.options & REINDEXOPT_CONCURRENTLY) != 0 ?
-		ShareUpdateExclusiveLock : ShareLock;
+	LOCKMODE	table_lockmode = state->lockmode;
 
 	/*
 	 * If we previously locked some other index's heap, and the name we're
@@ -2689,6 +2696,8 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 {
 	Oid			heapOid;
 	bool		result;
+	char		relpersistence;
+	int 		reindex_flags = 0;
 
 	/*
 	 * The lock level used here should match reindex_relation().
@@ -2699,15 +2708,27 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 	 * locks on our temporary table.
 	 */
 	heapOid = RangeVarGetRelidExtended(relation,
-									   (params->options & REINDEXOPT_CONCURRENTLY) != 0 ?
-									   ShareUpdateExclusiveLock : ShareLock,
+									   AccessShareLock,
 									   0,
 									   RangeVarCallbackOwnsTable, NULL);
 
+	relpersistence = get_rel_persistence(heapOid);
+	if (relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		LOCKMODE	lockmode;
+
+		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0)
+			lockmode = ShareUpdateExclusiveLock;
+		else
+			lockmode = ShareLock;
+
+		LockRelationOid(heapOid, lockmode);
+	}
+
 	if (get_rel_relkind(heapOid) == RELKIND_PARTITIONED_TABLE)
 		ReindexPartitions(heapOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(relpersistence))
 	{
 		result = ReindexRelationConcurrently(heapOid, params);
 
@@ -2721,9 +2742,14 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 		ReindexParams newparams = *params;
 
 		newparams.options |= REINDEXOPT_REPORT_PROGRESS;
+
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			reindex_flags |= REINDEX_REL_PROCESS_GLOBAL_TEMP;
+
+		reindex_flags |= REINDEX_REL_PROCESS_TOAST;
+		reindex_flags |= REINDEX_REL_CHECK_CONSTRAINTS;
 		result = reindex_relation(heapOid,
-								  REINDEX_REL_PROCESS_TOAST |
-								  REINDEX_REL_CHECK_CONSTRAINTS,
+								  reindex_flags,
 								  &newparams);
 		if (!result)
 			ereport(NOTICE,
@@ -3122,7 +3148,7 @@ ReindexMultipleInternal(List *relids, ReindexParams *params)
 			   relkind != RELKIND_PARTITIONED_TABLE);
 
 		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			relpersistence != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(relpersistence))
 		{
 			ReindexParams newparams = *params;
 
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 62465bacd81..ef37f79ba68 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -51,12 +51,32 @@ LockTableCommand(LockStmt *lockstmt)
 		RangeVar   *rv = (RangeVar *) lfirst(p);
 		bool		recurse = rv->inh;
 		Oid			reloid;
+		LOCKMODE	lockmode = lockstmt->mode;
+		char		relpersistence;
 
-		reloid = RangeVarGetRelidExtended(rv, lockstmt->mode,
-										  lockstmt->nowait ? RVR_NOWAIT : 0,
+		reloid = RangeVarGetRelidExtended(rv, NoLock, 0,
 										  RangeVarCallbackForLockTable,
 										  (void *) &lockstmt->mode);
 
+		relpersistence = get_rel_persistence(reloid);
+		if (relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			if (!lockstmt->nowait)
+				LockRelationOid(reloid, lockmode);
+			else if (!ConditionalLockRelationOid(reloid, lockmode))
+			{
+				/* try to throw error by name; relation could be deleted... */
+				char	   *relname = get_rel_name(reloid);
+
+				if (!relname)
+					return;		/* child concurrently dropped, just skip it */
+				ereport(ERROR,
+						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						 errmsg("could not obtain lock on relation \"%s\"",
+								relname)));
+			}
+		}
+
 		if (get_rel_relkind(reloid) == RELKIND_VIEW)
 			LockViewRecurse(reloid, lockstmt->mode, lockstmt->nowait, NIL);
 		else if (recurse)
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 72bfdc07a49..e5257f610f6 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -220,9 +223,12 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	rel = table_open(seqoid, AccessExclusiveLock);
 	tupDesc = RelationGetDescr(rel);
 
-	/* now initialize the sequence's data */
-	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	if (!RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		/* now initialize the sequence's data */
+		tuple = heap_form_tuple(tupDesc, value, null);
+		fill_seq_with_data(rel, tuple);
+	}
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -275,8 +281,6 @@ ResetSequence(Oid seq_relid)
 	Buffer		buf;
 	HeapTupleData seqdatatuple;
 	HeapTuple	tuple;
-	HeapTuple	pgstuple;
-	Form_pg_sequence pgsform;
 	int64		startv;
 
 	/*
@@ -287,12 +291,7 @@ ResetSequence(Oid seq_relid)
 	init_sequence(seq_relid, &elm, &seq_rel);
 	(void) read_seq_tuple(seq_rel, &buf, &seqdatatuple);
 
-	pgstuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seq_relid));
-	if (!HeapTupleIsValid(pgstuple))
-		elog(ERROR, "cache lookup failed for sequence %u", seq_relid);
-	pgsform = (Form_pg_sequence) GETSTRUCT(pgstuple);
-	startv = pgsform->seqstart;
-	ReleaseSysCache(pgstuple);
+	startv = get_seqence_start_value(seq_relid);
 
 	/*
 	 * Copy the existing sequence tuple.
@@ -451,6 +450,15 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 
 	init_sequence(relid, &elm, &seqrel);
 
+	if (RELATION_IS_GLOBAL_TEMP(seqrel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(seqrel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary sequence %s when other backend attached it.",
+						RelationGetRelationName(seqrel))));
+	}
+
 	rel = table_open(SequenceRelationId, RowExclusiveLock);
 	seqtuple = SearchSysCacheCopy1(SEQRELID,
 								   ObjectIdGetDatum(relid));
@@ -611,7 +619,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +944,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1153,6 +1161,14 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	/* Initializes the storage for sequence which the global temporary table belongs. */
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1927,3 +1943,58 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+/*
+ * Get the startValue of the sequence from syscache.
+ */
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+/*
+ * Initialize sequence which global temporary table belongs.
+ */
+void
+gtt_init_seq(Relation rel)
+{
+	Datum		value[SEQ_COL_LASTCOL];
+	bool		null[SEQ_COL_LASTCOL];
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL - 1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+	null[SEQ_COL_LASTVAL - 1] = false;
+
+	value[SEQ_COL_LOG - 1] = Int64GetDatum((int64)0);
+	null[SEQ_COL_LOG - 1] = false;
+
+	value[SEQ_COL_CALLED - 1] = BoolGetDatum(false);
+	null[SEQ_COL_CALLED - 1] = false;
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 857cc5ce6e2..ef14597b900 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -118,6 +119,7 @@ typedef struct OnCommitItem
 	 */
 	SubTransactionId creating_subid;
 	SubTransactionId deleting_subid;
+	bool			 is_global_temp;
 } OnCommitItem;
 
 static List *on_commits = NIL;
@@ -602,7 +604,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
 static char GetAttributeCompression(Oid atttypid, char *compression);
-
+static OnCommitAction gtt_oncommit_option(List *options);
 
 /* ----------------------------------------------------------------
  *		DefineRelation
@@ -647,6 +649,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -658,7 +661,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * Check consistency of arguments
 	 */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !RelpersistenceTsTemp(stmt->relation->relpersistence))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -688,7 +691,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	if (RelpersistenceTsTemp(stmt->relation->relpersistence)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -789,6 +792,50 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* For global temporary table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		if (!(relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE))
+			elog(ERROR, "Only support global temporary regular table.");
+
+		/* Check parent table */
+		if (inheritOids)
+			elog(ERROR, "Not support global temporary partition table or inherit table.");
+
+		/* Check oncommit clause and save to reloptions */
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temporary table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1415,7 +1462,7 @@ RemoveRelations(DropStmt *drop)
 		 * relation persistence cannot be known without its OID.
 		 */
 		if (drop->concurrent &&
-			get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(get_rel_persistence(relOid)))
 		{
 			Assert(list_length(drop->objects) == 1 &&
 				   drop->removeType == OBJECT_INDEX);
@@ -1624,9 +1671,9 @@ ExecuteTruncate(TruncateStmt *stmt)
 		Relation	rel;
 		bool		recurse = rv->inh;
 		Oid			myrelid;
-		LOCKMODE	lockmode = AccessExclusiveLock;
+		LOCKMODE	lockmode;
 
-		myrelid = RangeVarGetRelidExtended(rv, lockmode,
+		myrelid = RangeVarGetRelidExtended(rv, AccessShareLock,
 										   0, RangeVarCallbackForTruncate,
 										   NULL);
 
@@ -1634,9 +1681,21 @@ ExecuteTruncate(TruncateStmt *stmt)
 		if (list_member_oid(relids, myrelid))
 			continue;
 
-		/* open the relation, we already hold a lock on it */
+		/* open the relation, we need hold a low-level lock first */
 		rel = table_open(myrelid, NoLock);
 
+		/*
+		 * Truncate global temp table only cleans up the data in current backend,
+		 * only low-level locks are required.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel))
+			lockmode = AccessShareLock;
+		else
+		{
+			lockmode = AccessExclusiveLock;
+			LockRelationOid(myrelid, lockmode);
+		}
+
 		/*
 		 * RangeVarGetRelidExtended() has done most checks with its callback,
 		 * but other checks with the now-opened Relation remain.
@@ -1886,6 +1945,7 @@ ExecuteTruncateGuts(List *explicit_rels,
 	foreach(cell, rels)
 	{
 		Relation	rel = (Relation) lfirst(cell);
+		LOCKMODE	lockmode;
 
 		/* Skip partitioned tables as there is nothing to do */
 		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
@@ -1936,6 +1996,19 @@ ExecuteTruncateGuts(List *explicit_rels,
 			continue;
 		}
 
+		/*
+		 * Skip the global temporary table that is not initialized for storage
+		 * in current backend.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel))
+		{
+			lockmode = AccessShareLock;
+			if (!gtt_storage_attached(RelationGetRelid(rel)))
+				continue;
+		}
+		else
+			lockmode = AccessExclusiveLock;
+
 		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
@@ -1954,6 +2027,7 @@ ExecuteTruncateGuts(List *explicit_rels,
 			Oid			heap_relid;
 			Oid			toast_relid;
 			ReindexParams reindex_params = {0};
+			int			reindex_flags = 0;
 
 			/*
 			 * This effectively deletes all rows in the table, and may be done
@@ -1981,17 +2055,21 @@ ExecuteTruncateGuts(List *explicit_rels,
 			if (OidIsValid(toast_relid))
 			{
 				Relation	toastrel = relation_open(toast_relid,
-													 AccessExclusiveLock);
+													 lockmode);
 
 				RelationSetNewRelfilenode(toastrel,
 										  toastrel->rd_rel->relpersistence);
 				table_close(toastrel, NoLock);
 			}
 
+			reindex_flags = REINDEX_REL_PROCESS_TOAST;
+			if (RELATION_IS_GLOBAL_TEMP(rel))
+				reindex_flags |= REINDEX_REL_PROCESS_GLOBAL_TEMP;
+
 			/*
 			 * Reconstruct the indexes to match, and we're done.
 			 */
-			reindex_relation(heap_relid, REINDEX_REL_PROCESS_TOAST,
+			reindex_relation(heap_relid, reindex_flags,
 							 &reindex_params);
 		}
 
@@ -4032,6 +4110,16 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temporary table only current backend use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -5363,6 +5451,24 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 
 			rel = table_open(tab->relid, NoLock);
 			find_composite_type_dependencies(rel->rd_rel->reltype, rel, NULL);
+
+			if (RELATION_IS_GLOBAL_TEMP(rel) && tab->rewrite > 0)
+			{
+				if (tab->chgPersistence)
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot change global temporary table persistence setting")));
+
+				if(gtt_storage_attached(tab->relid))
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("Only support alter global temporary table in an empty context."),
+						 errhint("Please create a new connection and execute ALTER TABLE on the new connection.")));
+
+				/* There is no need to override the whole temp table */
+				tab->rewrite = 0;
+			}
+
 			table_close(rel, NoLock);
 		}
 
@@ -5414,6 +5520,8 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot rewrite temporary tables of other sessions")));
 
+			Assert(!RELATION_IS_GLOBAL_TEMP(OldHeap));
+
 			/*
 			 * Select destination tablespace (same as original unless user
 			 * requested a change)
@@ -9045,6 +9153,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -13082,7 +13196,9 @@ TryReuseIndex(Oid oldId, IndexStmt *stmt)
 		Relation	irel = index_open(oldId, NoLock);
 
 		/* If it's a partitioned index, there is no storage to share. */
-		if (irel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
+		/* multiple global temp table are not allow use same relfilenode */
+		if (irel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX &&
+			!RELATION_IS_GLOBAL_TEMP(irel))
 		{
 			stmt->oldNode = irel->rd_node.relNode;
 			stmt->oldCreateSubid = irel->rd_createSubid;
@@ -13744,6 +13860,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -13943,6 +14062,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temporary table");
+
 	/* Check first if relation can be moved to new tablespace */
 	if (!CheckRelationTableSpaceMove(rel, newTableSpace))
 	{
@@ -14246,7 +14368,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(RelationGetSmgr(rel), dstrel, MAIN_FORKNUM,
@@ -15844,6 +15966,7 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
 	switch (rel->rd_rel->relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -16285,7 +16408,7 @@ AlterSeqNamespaces(Relation classRel, Relation rel,
  * Register a newly-created relation's ON COMMIT action.
  */
 void
-register_on_commit_action(Oid relid, OnCommitAction action)
+register_on_commit_action(Oid relid, OnCommitAction action, bool is_gloal_temp)
 {
 	OnCommitItem *oc;
 	MemoryContext oldcxt;
@@ -16304,6 +16427,7 @@ register_on_commit_action(Oid relid, OnCommitAction action)
 	oc->oncommit = action;
 	oc->creating_subid = GetCurrentSubTransactionId();
 	oc->deleting_subid = InvalidSubTransactionId;
+	oc->is_global_temp = is_gloal_temp;
 
 	/*
 	 * We use lcons() here so that ON COMMIT actions are processed in reverse
@@ -16349,6 +16473,7 @@ PreCommit_on_commit_actions(void)
 	ListCell   *l;
 	List	   *oids_to_truncate = NIL;
 	List	   *oids_to_drop = NIL;
+	List	   *oids_to_truncate_gtt = NIL;
 
 	foreach(l, on_commits)
 	{
@@ -16372,7 +16497,12 @@ PreCommit_on_commit_actions(void)
 				 * tables, as they must still be empty.
 				 */
 				if ((MyXactFlags & XACT_FLAGS_ACCESSEDTEMPNAMESPACE))
-					oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid);
+				{
+					if (oc->is_global_temp)
+						oids_to_truncate_gtt = lappend_oid(oids_to_truncate_gtt, oc->relid);
+					else
+						oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid);
+				}
 				break;
 			case ONCOMMIT_DROP:
 				oids_to_drop = lappend_oid(oids_to_drop, oc->relid);
@@ -16389,7 +16519,10 @@ PreCommit_on_commit_actions(void)
 	 * exists at truncation time.
 	 */
 	if (oids_to_truncate != NIL)
-		heap_truncate(oids_to_truncate);
+		heap_truncate(oids_to_truncate, false);
+
+	if (oids_to_truncate_gtt != NIL)
+		heap_truncate(oids_to_truncate_gtt, true);
 
 	if (oids_to_drop != NIL)
 	{
@@ -17388,6 +17521,13 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot attach temporary relation of another session as partition")));
 
+	/* If the parent is permanent, so must be all of its partitions. */
+	if (attachrel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach a global temporary relation as partition of permanent relation \"%s\"",
+						RelationGetRelationName(rel))));
+
 	/* Check if there are any columns in attachrel that aren't in the parent */
 	tupleDesc = RelationGetDescr(attachrel);
 	natts = tupleDesc->natts;
@@ -18858,3 +18998,40 @@ GetAttributeCompression(Oid atttypid, char *compression)
 
 	return cmethod;
 }
+
+/*
+ * Parse the on commit clause for the temporary table
+ */
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			bool	res = false;
+			char	*sval = defGetString(def);
+
+			/* It has to be a Boolean value */
+			if (!parse_bool(sval, &res))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
+
+			if (res)
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 5c4bc15b441..3a861c47946 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"
@@ -1315,6 +1316,27 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = RELATION_IS_GLOBAL_TEMP(relation);
+
+	 /* For global temporary table */
+	if (is_gtt)
+	{
+		/* Store relation statistics and transaction information to the localhash */
+		up_gtt_relstats(RelationGetRelid(relation),
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+
+		/* Update relation statistics to local relcache */
+		relation->rd_rel->relpages = (int32) num_pages;
+		relation->rd_rel->reltuples = (float4) num_tuples;
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+		if (TransactionIdIsNormal(frozenxid))
+			relation->rd_rel->relfrozenxid = frozenxid;
+
+		if (MultiXactIdIsValid(minmulti))
+			relation->rd_rel->relminmxid = minmulti;
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1328,17 +1350,23 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (!is_gtt &&
+		pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (!is_gtt &&
+		pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (!is_gtt &&
+		pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1351,7 +1379,15 @@ vac_update_relstats(Relation relation,
 		/*
 		 * If we didn't find any indexes, reset relhasindex.
 		 */
-		if (pgcform->relhasindex && !hasindex)
+		if (is_gtt &&
+			RelationGetIndexList(relation) != NIL)
+		{
+			/*
+			 * Global temporary tables may contain indexes that are not valid locally.
+			 * The catalog should not be updated based on local invalid index.
+			 */
+		}
+		else if (pgcform->relhasindex && !hasindex)
 		{
 			pgcform->relhasindex = false;
 			dirty = true;
@@ -1383,7 +1419,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNextTransactionId(),
@@ -1394,7 +1431,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1502,6 +1540,13 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/*
+		 * The relfrozenxid for a global temporary talble is stored in localhash,
+		 * not pg_class, See list_all_session_gtt_frozenxids()
+		 */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1559,6 +1604,43 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/* If enable global temporary table */
+	if (max_active_gtt > 0)
+	{
+		TransactionId	safe_age;
+		/*  */
+		TransactionId	oldest_gtt_frozenxid =
+			list_all_backend_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid))
+		{
+			safe_age = oldest_gtt_frozenxid + vacuum_gtt_defer_check_age;
+			if (safe_age < FirstNormalTransactionId)
+				safe_age += FirstNormalTransactionId;
+
+			/*
+			 * We tolerate that the minimum age of gtt is less than
+			 * the minimum age of conventional tables, otherwise it will
+			 * throw warning message.
+			 */
+			if (TransactionIdIsNormal(safe_age) &&
+				TransactionIdPrecedes(safe_age, newFrozenXid))
+			{
+				ereport(WARNING,
+					(errmsg("global temp table oldest relfrozenxid %u is the oldest in the entire db",
+							oldest_gtt_frozenxid),
+					 errdetail("The oldest relfrozenxid in pg_class is %u", newFrozenXid),
+					 errhint("If they differ greatly, please consider cleaning up the data in global temp table.")));
+			}
+
+			/*
+			 * We need to ensure that the clog required by gtt is not cleand.
+			 */
+			if (TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+				newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1910,6 +1992,19 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	/*
+	 * Skip those global temporary table that are not initialized in
+	 * current backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel) &&
+		!gtt_storage_attached(RelationGetRelid(rel)))
+	{
+		relation_close(rel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 4df05a0b33d..4c181e2e14e 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -527,6 +527,12 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	/* Global temporary table are not sensible. */
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b3ce4bae530..611e3f18a70 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -784,6 +784,10 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		/* This is one kind of temp table */
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 5c723bc54e1..191e0f6fd21 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d328856ae5b..4e2bbb224cc 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -38,6 +38,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -633,6 +634,9 @@ ExecInsert(ModifyTableState *mtstate,
 		resultRelInfo->ri_IndexRelationDescs == NULL)
 		ExecOpenIndices(resultRelInfo, onconflict != ONCONFLICT_NONE);
 
+	/* Init storage for global temporary table in current backend */
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	/*
 	 * BEFORE ROW INSERT Triggers.
 	 *
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 296dd75c1b6..d971aea2546 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -48,7 +48,7 @@
 #include "partitioning/partprune.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/lsyscache.h"
-
+#include "utils/rel.h"
 
 /* results of subquery_is_pushdown_safe */
 typedef struct pushdown_safety_info
@@ -619,7 +619,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			if (RelpersistenceTsTemp(get_rel_persistence(rte->relid)))
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index bd01ec0526f..ff4e81ca2cf 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6071,7 +6071,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
-	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+	if (RELATION_IS_TEMP(heap) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index c5194fdbbf2..38d7c658541 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -30,6 +30,7 @@
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temporary table in current backend */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 146ee8dd1ea..2d4e9393f00 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2907,6 +2907,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 d0eb80e69cb..8dab345aad9 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3417,17 +3417,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; }
@@ -11755,19 +11749,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 c5c3f26ecf1..2a2b2789077 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -82,6 +82,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);
 
 
 /*
@@ -3665,3 +3666,53 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * Like function isQueryUsingTempRelation_walker
+ * return true if any relation underlying
+ * the query is a global temporary table.
+ */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* Check if the query uses global temporary table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 313d7b6ff02..38e0e162e82 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -447,6 +447,13 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	/*
+	 * If a sequence is bound to a global temporary table, then the sequence
+	 * must been "global temporary"
+	 */
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
@@ -3326,6 +3333,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		cxt.isforeign = false;
 	}
 	cxt.relation = stmt->relation;
+	/* Sets the table persistence to the context */
+	cxt.relation->relpersistence = RelationGetRelPersistence(rel);
 	cxt.rel = rel;
 	cxt.inhRelations = NIL;
 	cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 96332320a73..75ed4d0ae9e 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2115,6 +2115,14 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/*
+			 * Aotuvacuum cannot vacuum the private data stored in each backend
+			 * that belongs to global temporary table, so skip them.
+			 */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2181,7 +2189,7 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		if (RelpersistenceTsTemp(classForm->relpersistence))
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 08ebabfe96a..c346c59c7f4 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlogutils.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -2934,6 +2935,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * Returns 0 if this global temporary table is not initialized in current
+	 * backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 9fa3e0631e6..cc3eb928bc6 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -23,6 +23,7 @@
 #include "access/syncscan.h"
 #include "access/twophase.h"
 #include "commands/async.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
@@ -143,6 +144,7 @@ CalculateShmemSize(int *num_semaphores)
 	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
@@ -246,6 +248,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* For global temporary table shared hashtable */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 892f0f67998..bdc6b478ca8 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -65,6 +65,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -5148,3 +5149,82 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temporary table.
+ */
+int
+list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct		*arrayP = NULL;
+	TransactionId		result = InvalidTransactionId;
+	int			index = 0;
+	int			i = 0;
+	uint8		flags = 0;
+
+	if (n)
+		*n = 0;
+
+	/* return 0 if feature is disabled */
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+	}
+
+	/* Disable in standby node */
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	arrayP = procArray;
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		uint8           statusFlags = ProcGlobal->statusFlags[index];
+
+		if (statusFlags & flags)
+			continue;
+
+		/* Fetch all backend that is belonging to MyDatabaseId */
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->backend_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->backend_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->backend_gtt_frozenxid, result))
+				result = proc->backend_gtt_frozenxid;
+
+			/* save backend pid and backend level oldest relfrozenxid */
+			if (pids)
+				pids[i] = proc->pid;
+
+			if (xids)
+				xids[i] = proc->backend_gtt_frozenxid;
+
+			i++;
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (n)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 862097352bb..4edd3b31f7a 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -176,7 +176,9 @@ static const char *const BuiltinTrancheNames[] = {
 	/* LWTRANCHE_PARALLEL_APPEND: */
 	"ParallelAppend",
 	/* LWTRANCHE_PER_XACT_PREDICATE_LIST: */
-	"PerXactPredicateList"
+	"PerXactPredicateList",
+	/* LWTRANCHE_GTT_CTL */
+	"GlobalTempTableControl"
 };
 
 StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index b7d9da0aa9f..8051f2053f9 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -393,6 +393,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;	/* init backend level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
@@ -578,6 +579,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;	/* init backend level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index d5a7fb13f3c..8225cf6219f 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -982,6 +982,13 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/*
+			 * For global temporary table ,each backend has its own storage,
+			 * also only sees its own storage. Use Backendid to identify them.
+			 */
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 10895fb2876..66255eb7604 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -108,6 +108,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -5115,12 +5116,26 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								/* For global temporary table, get statistic data from localhash */
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -5368,15 +5383,28 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6820,6 +6848,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;
@@ -6837,6 +6866,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6848,6 +6885,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;
@@ -6863,6 +6902,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7781,6 +7828,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))
@@ -7793,6 +7842,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7805,6 +7863,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.
@@ -7824,6 +7884,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 4ebaa552a27..78c33d2ac87 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -3113,6 +3114,19 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		/* For global temporary table, get statistic data from localhash */
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 9fa9e671a11..65b40e58943 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -65,6 +65,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1152,6 +1153,36 @@ retry:
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+				TransactionId	relfrozenxid = InvalidTransactionId;
+				MultiXactId 	relminmxid = InvalidMultiXactId;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+
+				/* For global temporary table, get relstat data from localhash */
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								&relfrozenxid,
+								&relminmxid);
+
+				/* And put them to local relcache */
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+				if (TransactionIdIsNormal(relfrozenxid))
+					relation->rd_rel->relfrozenxid = relfrozenxid;
+
+				if (MultiXactIdIsValid(relminmxid))
+					relation->rd_rel->relminmxid = relminmxid;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1209,6 +1240,8 @@ retry:
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			/* The state of the global temporary table's index may need to be set */
+			gtt_fix_index_backend_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -1351,7 +1384,22 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+			/*
+			 * For global temporary table, get the latest relfilenode
+			 * from localhash and put it in relcache.
+			 */
+			if (OidIsValid(newrelnode) &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2302,6 +2350,9 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		/* The state of the global temporary table's index may need to be set */
+		gtt_fix_index_backend_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3582,6 +3633,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3691,28 +3746,39 @@ void
 RelationSetNewRelfilenode(Relation relation, char persistence)
 {
 	Oid			newrelfilenode;
-	Relation	pg_class;
-	HeapTuple	tuple;
+	Relation	pg_class = NULL;
+	HeapTuple	tuple = NULL;
 	Form_pg_class classform;
 	MultiXactId minmulti = InvalidMultiXactId;
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
 
+	/*
+	 * For global temporary table, storage information for the table is
+	 * maintained locally, not in catalog.
+	 */
+	bool		update_catalog = !RELATION_IS_GLOBAL_TEMP(relation);
+
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
 
-	/*
-	 * Get a writable copy of the pg_class tuple for the given relation.
-	 */
-	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+	memset(&classform, 0, sizeof(classform));
 
-	tuple = SearchSysCacheCopy1(RELOID,
-								ObjectIdGetDatum(RelationGetRelid(relation)));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "could not find tuple for relation %u",
-			 RelationGetRelid(relation));
-	classform = (Form_pg_class) GETSTRUCT(tuple);
+	if (update_catalog)
+	{
+		/*
+		 * Get a writable copy of the pg_class tuple for the given relation.
+		 */
+		pg_class = table_open(RelationRelationId, RowExclusiveLock);
+
+		tuple = SearchSysCacheCopy1(RELOID,
+									ObjectIdGetDatum(RelationGetRelid(relation)));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u",
+				 RelationGetRelid(relation));
+		classform = (Form_pg_class) GETSTRUCT(tuple);
+	}
 
 	/*
 	 * Schedule unlinking of the old storage at transaction commit.
@@ -3738,7 +3804,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;
@@ -3758,6 +3824,18 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 			break;
 	}
 
+	/* For global temporary table */
+	if (!update_catalog)
+	{
+		Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+		Assert(RELATION_IS_GLOBAL_TEMP(relation));
+		Assert(!RelationIsMapped(relation));
+
+		/* Make cache invalid and set new relnode to local cache. */
+		CacheInvalidateRelcache(relation);
+		relation->rd_node.relNode = relnode;
+	}
 	/*
 	 * If we're dealing with a mapped index, pg_class.relfilenode doesn't
 	 * change; instead we have to send the update to the relation mapper.
@@ -3767,7 +3845,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	 * possibly-inaccurate values of relpages etc, but those will be fixed up
 	 * later.
 	 */
-	if (RelationIsMapped(relation))
+	else if (RelationIsMapped(relation))
 	{
 		/* This case is only supported for indexes */
 		Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3813,9 +3891,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 		CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 	}
 
-	heap_freetuple(tuple);
+	if (update_catalog)
+	{
+		heap_freetuple(tuple);
 
-	table_close(pg_class, RowExclusiveLock);
+		table_close(pg_class, RowExclusiveLock);
+	}
 
 	/*
 	 * Make the pg_class row change or relation map change visible.  This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index e91d5a3cfda..b8ec6b3ca46 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -44,6 +44,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "commands/prepare.h"
 #include "commands/trigger.h"
@@ -153,6 +154,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temporary table feature.
+ * table schema are still saved in catalog.
+ *
+ * num > 0 means allows the database to manage multiple active tables at the same time.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2128,6 +2141,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 "
@@ -2698,6 +2720,16 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"vacuum_gtt_defer_check_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("The defer check age of GTT, used to check expired data after vacuum."),
+			NULL
+		},
+		&vacuum_gtt_defer_check_age,
+		10000, 0, 1000000,
+		NULL, NULL, NULL
+	},
+
 	/*
 	 * See also CheckRequiredParameterValues() if this parameter changes
 	 */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index b9635a95b6f..c08ef663aca 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2504,6 +2504,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
 		dopt->no_unlogged_table_data)
 		return;
 
+	/* Don't dump data in global temporary table/sequence */
+	if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		return;
+
 	/* Check that the data is not explicitly excluded */
 	if (simple_oid_list_member(&tabledata_exclude_oids,
 							   tbinfo->dobj.catId.oid))
@@ -15815,6 +15819,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
 		char	   *foreign = "";
+		char	   *table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15868,9 +15873,15 @@ dumpTableSchema(Archive *fout, const 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);
 
@@ -16234,6 +16245,15 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 			}
 		}
 
+		/*
+		 * Transaction information for the global temporary table is not stored
+		 * in the pg_class.
+		 */
+		if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			Assert(tbinfo->frozenxid == 0);
+			Assert(tbinfo->minmxid == 0);
+		}
 		/*
 		 * In binary_upgrade mode, arrange to restore the old relfrozenxid and
 		 * relminmxid of all vacuumable relations.  (While vacuum.c processes
@@ -16241,7 +16261,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		 * of other relations; so this "if" lacks RELKIND_TOASTVALUE, and the
 		 * child toast table is handled below.)
 		 */
-		if (dopt->binary_upgrade &&
+		else if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
 			 tbinfo->relkind == RELKIND_MATVIEW))
 		{
@@ -17244,6 +17264,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	PQExpBuffer query = createPQExpBuffer();
 	PQExpBuffer delqry = createPQExpBuffer();
 	char	   *qseqname;
+	bool	   global_temp_seq = false;
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
@@ -17253,9 +17274,12 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
-						  "FROM pg_catalog.pg_sequence "
-						  "WHERE seqrelid = '%u'::oid",
+						  "seqcache, seqcycle, "
+						  "c.relpersistence "
+						  "FROM pg_catalog.pg_sequence s, "
+						  "pg_catalog.pg_class c "
+						  "WHERE seqrelid = '%u'::oid "
+						  "and s.seqrelid = c.oid",
 						  tbinfo->dobj.catId.oid);
 	}
 	else if (fout->remoteVersion >= 80400)
@@ -17300,6 +17324,9 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
 
+	if (fout->remoteVersion >= 140000)
+		global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
 	if (strcmp(seqtype, "smallint") == 0)
@@ -17377,9 +17404,13 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	}
 	else
 	{
-		appendPQExpBuffer(query,
-						  "CREATE SEQUENCE %s\n",
-						  fmtQualifiedDumpable(tbinfo));
+		appendPQExpBuffer(query, "CREATE ");
+
+		if (global_temp_seq)
+			appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+		appendPQExpBuffer(query, "SEQUENCE %s\n",
+								  fmtQualifiedDumpable(tbinfo));
 
 		if (strcmp(seqtype, "bigint") != 0)
 			appendPQExpBuffer(query, "    AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index ad5f3919956..f3819860096 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -88,7 +88,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -178,7 +178,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 5d9a26cf822..2de11d5d707 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -447,8 +449,17 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
 			 "         ON c.relnamespace = n.oid "
 			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
+			 CppAsString2(RELKIND_MATVIEW) ") AND ");
+
+	if (skip_gtt)
+	{
+		/* exclude global temp tables */
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+			"    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND ");
+	}
+
 	/* exclude possible orphaned temp tables */
+	snprintf(query + strlen(query), sizeof(query) - strlen(query),
 			 "    ((n.nspname !~ '^pg_temp_' AND "
 			 "      n.nspname !~ '^pg_toast_temp_' AND "
 			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 3628bd74a7b..bbb9b5ea13d 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -407,7 +407,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -645,7 +645,10 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+			/* exclude global temp tables */
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -656,7 +659,10 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+		/* exclude global temp tables */
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index ca0795f68ff..018a2effd4b 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -389,7 +389,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 006661412ea..ffba81acb86 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -4089,7 +4089,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 4f724e4428b..ab7eb31cd42 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1059,6 +1059,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2603,6 +2605,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2830,6 +2835,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE", "SEQUENCE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 6ce480b49c1..dda3f3c5a60 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -85,7 +85,7 @@ extern Oid	heap_create_with_catalog(const char *relname,
 
 extern void heap_drop_with_catalog(Oid relid);
 
-extern void heap_truncate(List *relids);
+extern void heap_truncate(List *relids, bool is_global_temp);
 
 extern void heap_truncate_one_rel(Relation rel);
 
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 008f723e104..875b1003899 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -157,6 +157,7 @@ extern void reindex_index(Oid indexId, bool skip_constraint_checks,
 #define REINDEX_REL_CHECK_CONSTRAINTS		0x04
 #define REINDEX_REL_FORCE_INDEXES_UNLOGGED	0x08
 #define REINDEX_REL_FORCE_INDEXES_PERMANENT 0x10
+#define REINDEX_REL_PROCESS_GLOBAL_TEMP		0x20
 
 extern bool reindex_relation(Oid relid, int flags, ReindexParams *params);
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index fef9945ed8f..9176b7dcc07 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -172,6 +172,7 @@ DECLARE_INDEX(pg_class_tblspc_relfilenode_index, 3455, ClassTblspcRelfilenodeInd
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index d068d6532ec..fd5089388f2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5735,6 +5735,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '9874',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '9875',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '9876',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '9877',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 0ab32b44e91..92e9f8ba485 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,7 +22,7 @@
 /* GUC variables */
 extern int	wal_skip_threshold;
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationPreTruncate(Relation rel);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 00000000000..8a3d9558712
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,45 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern int		vacuum_gtt_defer_check_age;
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Oid relid,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void force_enable_gtt_index(Relation index);
+extern void gtt_fix_index_backend_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index 40544dd4c70..7b66d808fc5 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 336549cc5f0..3e8167134b7 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -86,7 +86,7 @@ extern void find_composite_type_dependencies(Oid typeOid,
 
 extern void check_of_type(HeapTuple typetuple);
 
-extern void register_on_commit_action(Oid relid, OnCommitAction action);
+extern void register_on_commit_action(Oid relid, OnCommitAction action, bool is_gloal_temp);
 extern void remove_on_commit_action(Oid relid);
 
 extern void PreCommit_on_commit_actions(void);
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 8336c2c5a29..bddcfe7256d 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index c86ccdaf608..6b395551c1d 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -399,6 +399,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index a8f052e4845..4b4ed1a13aa 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -189,6 +189,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_SHARED_TIDBITMAP,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_PER_XACT_PREDICATE_LIST,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index be67d8a8616..e2f8bb5162d 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -157,6 +157,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId backend_gtt_frozenxid;	/* backend level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index b01fa52139a..8efffa55ac5 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -94,4 +94,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index aa18d304ac0..524c9d7de3f 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -288,6 +288,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 b4faa1c1238..a74558a8383 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	SMgrRelation rd_smgr;		/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -326,6 +326,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -608,11 +609,13 @@ RelationGetSmgr(Relation rel)
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -620,6 +623,7 @@ RelationGetSmgr(Relation rel)
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -632,6 +636,30 @@ RelationGetSmgr(Relation rel)
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP_ON_CURRENT_SESSION
+ *		Test a rel is either local temp relation of this session
+ *		or global temp relation.
+ */
+#define RELATION_IS_TEMP_ON_CURRENT_SESSION(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is local temp relation or global temporary relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RelpersistenceTsTemp
+ *		Test a relpersistence is local temp relation or global temporary relation.
+ */
+#define RelpersistenceTsTemp(relpersistence) \
+	(relpersistence == RELPERSISTENCE_TEMP || \
+	 relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -677,6 +705,19 @@ RelationGetSmgr(Relation rel)
 	 (relation)->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&	\
 	 !IsCatalogRelation(relation))
 
+/* For global temporary table */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/* Get on commit clause value only for global temporary table */
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+/* Get relpersistence for relation */
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
-- 
2.30.1 (Apple Git-130)

#341wenjing
wjzeng2012@gmail.com
In reply to: wenjing (#340)
4 attachment(s)
Re: [Proposal] Global temporary tables

wenjing <wjzeng2012@gmail.com> 于2021年11月9日周二 下午4:51写道:

wenjing <wjzeng2012@gmail.com> 于2021年10月30日周六 上午1:28写道:

Andrew Bille <andrewbille@gmail.com> 于2021年10月28日周四 下午6:30写道:

Thanks, the "group by" is fixed

Yet another crash (on v58 patches), reproduced with:

psql -t -c "create global temp table t(b text)
with(on_commit_delete_rows=true); create index idx_b on t (b); insert into
t values('test'); alter table t alter b type varchar;"
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
connection to server was lost

Thank you for pointing that out.
This is due to an optimization point: ALTER Table reuses the relfilenode
of the old index.
I have banned this optimization point for GTT, I am not entirely sure it
is appropriate, maybe you can give some suggestions.
Please review the new code(v59).

Wenjing

with trace:

[New LWP 569199]
[Thread debugging using libthread_db enabled]
Using host libthread_db library
"/lib/x86_64-linux-gnu/libthread_db.so.1".
Core was generated by `postgres: andrew postgres [local] ALTER TABLE
'.
Program terminated with signal SIGABRT, Aborted.
#0 __GI_raise (sig=sig@entry=6) at
../sysdeps/unix/sysv/linux/raise.c:50
50 ../sysdeps/unix/sysv/linux/raise.c: No such file or directory.
(gdb) bt
#0 __GI_raise (sig=sig@entry=6) at
../sysdeps/unix/sysv/linux/raise.c:50
#1 0x00007f197493f859 in __GI_abort () at abort.c:79
#2 0x00005562b3306fb9 in ExceptionalCondition
(conditionName=0x5562b34dd740 "reln->md_num_open_segs[forkNum] == 0",
errorType=0x5562b34dd72c "FailedAssertion", fileName=0x5562b34dd727 "md.c",
lineNumber=187) at assert.c:69
#3 0x00005562b3148f15 in mdcreate (reln=0x5562b41abdc0,
forkNum=MAIN_FORKNUM, isRedo=false) at md.c:187
#4 0x00005562b314b73f in smgrcreate (reln=0x5562b41abdc0,
forknum=MAIN_FORKNUM, isRedo=false) at smgr.c:335
#5 0x00005562b2d88b23 in RelationCreateStorage (rnode=...,
relpersistence=103 'g', rel=0x7f196b597270) at storage.c:154
#6 0x00005562b2d5a408 in index_build (heapRelation=0x7f196b58dc40,
indexRelation=0x7f196b597270, indexInfo=0x5562b4167d60, isreindex=true,
parallel=false) at index.c:3038
#7 0x00005562b2d533c1 in RelationTruncateIndexes
(heapRelation=0x7f196b58dc40, lockmode=1) at heap.c:3354
#8 0x00005562b2d5360b in heap_truncate_one_rel (rel=0x7f196b58dc40) at
heap.c:3452
#9 0x00005562b2d53544 in heap_truncate (relids=0x5562b4167c58,
is_global_temp=true) at heap.c:3410
#10 0x00005562b2ea09fc in PreCommit_on_commit_actions () at
tablecmds.c:16495
#11 0x00005562b2d0d4ee in CommitTransaction () at xact.c:2140
#12 0x00005562b2d0e320 in CommitTransactionCommand () at xact.c:2979
#13 0x00005562b3151b7e in finish_xact_command () at postgres.c:2721
#14 0x00005562b314f340 in exec_simple_query (query_string=0x5562b40c2170
"create global temp table t(b text) with(on_commit_delete_rows=true);
create index idx_b on t (b); insert into t values('test'); alter table t
alter b type varchar;") at postgres.c:1239
#15 0x00005562b3153f0a in PostgresMain (dbname=0x5562b40ed6e8
"postgres", username=0x5562b40ed6c8 "andrew") at postgres.c:4497
#16 0x00005562b307df6e in BackendRun (port=0x5562b40e4500) at
postmaster.c:4560
#17 0x00005562b307d853 in BackendStartup (port=0x5562b40e4500) at
postmaster.c:4288
#18 0x00005562b3079a1d in ServerLoop () at postmaster.c:1801
#19 0x00005562b30791b6 in PostmasterMain (argc=3, argv=0x5562b40bc5b0)
at postmaster.c:1473
#20 0x00005562b2f6d98e in main (argc=3, argv=0x5562b40bc5b0) at
main.c:198

On Mon, Oct 25, 2021 at 7:13 PM wenjing <wjzeng2012@gmail.com> wrote:

I missed whole row and system column. It has been fixed in v58.
Please review the new code(v58) again

Hi Andrew

I fixed a problem found during testing.
GTT version updated to v60.

Wenjing.

Fixed a bug in function pg_gtt_attached_pid.
Looking forward to your reply.

Wenjing

Attachments:

0002-gtt-v61-doc.patchapplication/octet-stream; name=0002-gtt-v61-doc.patchDownload
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 473a0a4aeb..e510bde8ac 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -169,32 +169,67 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     <listitem>
      <para>
       If specified, the table is created as a temporary table.
-      Temporary tables are automatically dropped at the end of a
-      session, or optionally at the end of the current transaction
-      (see <literal>ON COMMIT</literal> below).  The default
-      search_path includes the temporary schema first and so identically
-      named existing permanent tables are not chosen for new plans
+      Optionally, <literal>GLOBAL</literal> or <literal>LOCAL</literal>
+      can be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
+      They represent two types of temporary tables supported by <productname>PostgreSQL</productname>:
+      global temporary table and local temporary table. Without specified
+      GLOBAL or LOCAL, a local temporary table is created by default.
+     </para>
+
+    <para>
+     Both types of temporary tables’ data are truncated at the
+     end of a session or optionally at the end of the current transaction.
+     (see <literal>ON COMMIT</literal> below). For global temporary table,
+     its schema is reserved and reused by future sessions or transactions.
+     For local temporary table, both its data and its schema are dropped.
+    </para>
+
+    <variablelist>
+     <varlistentry>
+      <term><literal>Global Temporary Table</literal></term>
+      <listitem>
+       <para>
+        Global temporary table are defined just once and automatically exist
+        (starting with empty contents) in every session that needs them.
+        The schema definition of temporary tables is persistent and shared among sessions.
+        However, the data in temporary tables are kept private to sessions themselves,
+        even though they use same name and same schema.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>Local Temporary Table</literal></term>
+     <listitem>
+     <para>
+      Local temporary table are automatically dropped at the end of a
+      session (include schema and data). Future sessions need to create
+      their own temporary tables when they are used.
+     </para>
+     <para>
+      The default search_path includes the temporary schema first and so
+      identically named existing permanent tables are not chosen for new plans
       while the temporary table exists, unless they are referenced
       with schema-qualified names. Any indexes created on a temporary
       table are automatically temporary as well.
      </para>
+     </listitem>
+     </varlistentry>
+    </variablelist>
 
-     <para>
-      The <link linkend="autovacuum">autovacuum daemon</link> cannot
-      access and therefore cannot vacuum or analyze temporary tables.
-      For this reason, appropriate vacuum and analyze operations should be
-      performed via session SQL commands.  For example, if a temporary
-      table is going to be used in complex queries, it is wise to run
-      <command>ANALYZE</command> on the temporary table after it is populated.
-     </para>
+    <para>
+     The <link linkend="autovacuum">autovacuum daemon</link> cannot
+     access and therefore cannot vacuum or analyze temporary tables.
+     For this reason, appropriate vacuum and analyze operations should be
+     performed via session SQL commands.  For example, if a temporary
+     table is going to be used in complex queries, it is wise to run
+     <command>ANALYZE</command> on the temporary table after it is populated.
+    </para>
+    <para>
+     The Temporary table resembles the SQL standard, but has some differences.
+     see <xref linkend="sql-createtable-compatibility"/> below.
+    </para>
 
-     <para>
-      Optionally, <literal>GLOBAL</literal> or <literal>LOCAL</literal>
-      can be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
-      This presently makes no difference in <productname>PostgreSQL</productname>
-      and is deprecated; see
-      <xref linkend="sql-createtable-compatibility"/> below.
-     </para>
     </listitem>
    </varlistentry>
 
@@ -2133,13 +2168,17 @@ CREATE TABLE cities_partdef
    <title>Temporary Tables</title>
 
    <para>
-    Although the syntax of <literal>CREATE TEMPORARY TABLE</literal>
-    resembles that of the SQL standard, the effect is not the same.  In the
-    standard,
-    temporary tables are defined just once and automatically exist (starting
-    with empty contents) in every session that needs them.
-    <productname>PostgreSQL</productname> instead
-    requires each session to issue its own <literal>CREATE TEMPORARY
+    Although the syntax of <literal>CREATE GLOBAL/LOCAL TEMPORARY TABLE</literal>
+    resembles that of the SQL standard, the effect is not the same.
+    The global temporary table follows the SQL standards while local temporary
+    table does not.
+   </para>
+
+   <para>
+    First, in the standard, both global and local temporary tables are defined just
+    once and automatically exist (starting with empty contents) in every session
+    that needs them. For local temporary tables, <productname>PostgreSQL</productname>
+    instead requires each session to issue its own <literal>CREATE LOCAL TEMPORARY
     TABLE</literal> command for each temporary table to be used.  This allows
     different sessions to use the same temporary table name for different
     purposes, whereas the standard's approach constrains all instances of a
@@ -2147,29 +2186,14 @@ CREATE TABLE cities_partdef
    </para>
 
    <para>
-    The standard's definition of the behavior of temporary tables is
-    widely ignored.  <productname>PostgreSQL</productname>'s behavior
-    on this point is similar to that of several other SQL databases.
-   </para>
-
-   <para>
-    The SQL standard also distinguishes between global and local temporary
+    Second, the SQL standard distinguishes between global and local temporary
     tables, where a local temporary table has a separate set of contents for
     each SQL module within each session, though its definition is still shared
-    across sessions.  Since <productname>PostgreSQL</productname> does not
+    across sessions. Since <productname>PostgreSQL</productname> does not
     support SQL modules, this distinction is not relevant in
     <productname>PostgreSQL</productname>.
    </para>
 
-   <para>
-    For compatibility's sake, <productname>PostgreSQL</productname> will
-    accept the <literal>GLOBAL</literal> and <literal>LOCAL</literal> keywords
-    in a temporary table declaration, but they currently have no effect.
-    Use of these keywords is discouraged, since future versions of
-    <productname>PostgreSQL</productname> might adopt a more
-    standard-compliant interpretation of their meaning.
-   </para>
-
    <para>
     The <literal>ON COMMIT</literal> clause for temporary tables
     also resembles the SQL standard, but has some differences.
@@ -2177,7 +2201,8 @@ CREATE TABLE cities_partdef
     default behavior is <literal>ON COMMIT DELETE ROWS</literal>.  However, the
     default behavior in <productname>PostgreSQL</productname> is
     <literal>ON COMMIT PRESERVE ROWS</literal>.  The <literal>ON COMMIT
-    DROP</literal> option does not exist in SQL.
+    DROP</literal> option does not exist in SQL and is not supported by
+    global temporary table.
    </para>
   </refsect2>
 
-- 
2.30.1 (Apple Git-130)

0001-gtt-v61-reademe.patchapplication/octet-stream; name=0001-gtt-v61-reademe.patchDownload
diff --git a/README.gtt.txt b/README.gtt.txt
new file mode 100644
index 00000000000..d181df9acd7
--- /dev/null
+++ b/README.gtt.txt
@@ -0,0 +1,172 @@
+Global Temporary Table(GTT)
+=========================================
+
+Feature description
+-----------------------------------------
+
+Previously, temporary tables are defined once and automatically
+exist (starting with empty contents) in every session before using them.
+
+The temporary table implementation in PostgreSQL, known as Local temp tables(LTT),
+did not fully comply with the SQL standard. This version added the support of
+Global Temporary Table .
+
+The metadata of Global Temporary Table is persistent and shared among sessions.
+The data stored in the Global temporary table is independent of sessions. This
+means, when a session creates a Global Temporary Table and writes some data.
+Other sessions cannot see those data, but they have an empty Global Temporary
+Table with same schema.
+
+Like local temporary table, Global Temporary Table supports ON COMMIT PRESERVE ROWS
+or ON COMMIT DELETE ROWS clause, so that data in the temporary table can be
+cleaned up or preserved automatically when a session exits or a transaction COMMITs.
+
+Unlike Local Temporary Table, Global Temporary Table does not support
+ON COMMIT DROP clauses.
+
+In following paragraphs, we use GTT for Global Temporary Table and LTT for
+local temporary table.
+
+Main design ideas
+-----------------------------------------
+In general, GTT and LTT use the same storage and buffer design and
+implementation. The storage files for both types of temporary tables are named
+as t_backendid_relfilenode, and the local buffer is used to cache the data.
+
+The schema of GTTs is shared among sessions while their data are not. We build
+a new mechanisms to manage those non-shared data and their statistics.
+Here is the summary of changes:
+
+1) CATALOG
+GTTs store session-specific data. The storage information of GTTs'data, their
+transaction information, and their statistics are not stored in the catalog.
+
+2) STORAGE INFO & STATISTICS INFO & TRANSACTION INFO
+In order to maintain durability and availability of GTTs'session-specific data,
+their storage information, statistics, and transaction information is managed
+in a local hash table tt_storage_local_hash.
+
+3) DDL
+Currently, GTT supports almost all table'DDL except CLUSTER/VACUUM FULL.
+Part of the DDL behavior is limited by shared definitions and multiple copies of
+local data, and we added some structures to handle this.
+
+A shared hash table active_gtt_shared_hash is added to track the state of the
+GTT in a different session. This information is recorded in the hash table
+during the DDL execution of the GTT.
+
+The data stored in a GTT can only be modified or accessed by owning session.
+The statements that only modify data in a GTT do not need a high level of
+table locking. The operations making those changes include truncate GTT,
+reindex GTT, and lock GTT.
+
+4) MVCC commit log(clog) cleanup
+Each GTT in a session has its own piece of data, and they have their own
+transaction information. We set up data structures to track and maintain
+this information. The cleaning of CLOGs also needs to consider the transaction
+information of GTT.
+
+Detailed design
+-----------------------------------------
+
+1. CATALOG
+1.1 relpersistence
+define RELPERSISTENCEGLOBALTEMP 'g'
+Mark Global Temporary Table in pg_class relpersistence to 'g'. The relpersistence
+of indexes created on the GTT, sequences on GTT and toast tables on GTT are
+also set to 'g'
+
+1.2 on commit clause
+LTT's status associated with on commit DELETE ROWS and on commit PRESERVE ROWS
+is not stored in catalog. Instead, GTTs need a bool value on_commit_delete_rows
+in reloptions which is shared among sessions.
+
+1.3 gram.y
+GTT is already supported in syntax tree. We remove the warning message
+"GLOBAL is deprecated in temporary table creation" and mark
+relpersistence = RELPERSISTENCEGLOBALTEMP.
+
+2. STORAGE INFO & STATISTICS INFO & TRANSACTION INFO
+2.1. gtt_storage_local_hash
+Each backend creates a local hashtable gtt_storage_local_hash to track a GTT's
+storage file information, statistics, and transaction information.
+
+2.2 GTT storage file info track
+1) When one session inserts data into a GTT for the first time, record the
+storage info to gtt_storage_local_hash.
+2) Use beforeshmemexit to ensure that all files of session GTT are deleted when
+the session exits.
+
+2.3 statistics info
+1) relpages reltuples relallvisible relfilenode
+2) The statistics of each column from pg_statistic
+All the above information is stored in gtt_storage_local_hash.
+When doing vacuum or analyze, GTT's statistic is updated, which is used by
+the SQL planner.
+The statistics summarizes only data in the current session.
+
+2.3 transaction info track
+frozenxid minmulti from pg_class is stored to gtt_storage_local_hash.
+
+3 DDL
+3.1. active_gtt_shared_hash
+This is the hash table created in shared memory to trace the GTT files initialized
+in each session. Each hash entry contains a bitmap that records the backendid of
+the initialized GTT file. With this hash table, we know which backend/session
+is using this GTT. Such information is used during GTT's DDL operations.
+
+3.2 DROP GTT
+One GTT is allowed to be deleted when there is only one session using the table
+and the session is the current session. After holding the lock on GTT,
+active_gtt_shared_hash is checked to ensure that.
+
+3.3 ALTER GTT/DROP INDEX ON GTT
+Same as drop GTT.
+
+3.4 CREATE INDEX ON GTT
+1) create index on GTT statements build index based on local data in a session.
+2) After the index is created, record the index metadata to the catalog.
+3) Other sessions can enable or disable the local GTT index.
+
+3.5 TRUNCATE/REINDEX GTT
+The SQL truncate/reindex command open the GTT using AccessShareLock lock,
+not AccessExclusiveLock, because this command only cleans up local data and
+local buffers in current session. This allows these operations to be executed
+concurrently between sessions, unlike normal tables.
+
+3.6 LOCK GTT
+A lock GTT statement does not hold any relation lock.
+
+3.7 CLUSTER GTT/VACUUM FULL GTT
+The current version does not support.
+
+4 MVCC commit log(clog) cleanup
+
+The GTT storage file contains transaction information. Queries for GTT data rely
+on transaction information such as clog. The transaction information required by
+each session may be completely different. We need to ensure that the transaction
+information of the GTT data is not cleaned up during its lifetime and that
+transaction resources are recycled at the instance level.
+
+4.1 The session level GTT oldest frozenxid
+1) To manage all GTT transaction information, add session level oldest frozenxid
+in each session. When one GTT is created or removed, record the session level
+oldest frozenxid and store it in MyProc.
+2) When vacuum advances the database's frozenxid, session level oldest frozenxid
+should be considered. This is acquired by searching all of MyProc. This way,
+we can avoid the clog required by GTTs to be cleaned.
+
+4.2 vacuum GTT
+Users can perform vacuum over a GTT to clean up local data in the GTT.
+
+4.3 autovacuum GTT
+Autovacuum skips all GTTs, because the data in GTTs is only visible in current session.
+
+5 OTHERS
+5.1 Parallel query
+Planner does not produce parallel query plans for SQL related to GTT. Because
+GTT private data cannot be accessed across processes.
+
+5.2 WAL and Logical replication
+Like LTT, the DML on GTT does not record WAL and is not parsed or replay by
+the logical replication.
\ No newline at end of file
-- 
2.30.1 (Apple Git-130)

0003-gtt-v61-implementation.patchapplication/octet-stream; name=0003-gtt-v61-implementation.patchDownload
From 0e93616ef2229faeae48583d5ac5ab671a64ae8e Mon Sep 17 00:00:00 2001
From: "wenjing.zwj" <wenjing.zwj@alibaba-inc.com>
Date: Tue, 9 Nov 2021 10:45:31 +0800
Subject: [PATCH] gtt v60 1109

---
 contrib/amcheck/verify_heapam.c          |   30 +-
 src/backend/access/common/reloptions.c   |   15 +
 src/backend/access/gist/gistutil.c       |    2 +-
 src/backend/access/hash/hash.c           |    2 +-
 src/backend/access/heap/heapam.c         |   18 +-
 src/backend/access/heap/heapam_handler.c |    4 +-
 src/backend/access/heap/vacuumlazy.c     |   30 +-
 src/backend/access/nbtree/nbtpage.c      |    9 +
 src/backend/catalog/Makefile             |    1 +
 src/backend/catalog/catalog.c            |    1 +
 src/backend/catalog/heap.c               |  105 +-
 src/backend/catalog/index.c              |  134 +-
 src/backend/catalog/namespace.c          |    7 +
 src/backend/catalog/storage.c            |   52 +-
 src/backend/catalog/storage_gtt.c        | 1509 ++++++++++++++++++++++
 src/backend/catalog/system_views.sql     |   85 ++
 src/backend/commands/analyze.c           |   78 +-
 src/backend/commands/cluster.c           |   19 +
 src/backend/commands/copy.c              |    2 +-
 src/backend/commands/copyfrom.c          |    4 +
 src/backend/commands/indexcmds.c         |   64 +-
 src/backend/commands/lockcmds.c          |   24 +-
 src/backend/commands/sequence.c          |   97 +-
 src/backend/commands/tablecmds.c         |  205 ++-
 src/backend/commands/vacuum.c            |  107 +-
 src/backend/commands/view.c              |    6 +
 src/backend/executor/execMain.c          |    4 +
 src/backend/executor/execPartition.c     |    1 +
 src/backend/executor/nodeModifyTable.c   |    4 +
 src/backend/optimizer/path/allpaths.c    |    4 +-
 src/backend/optimizer/plan/planner.c     |    2 +-
 src/backend/optimizer/util/plancat.c     |    9 +
 src/backend/parser/analyze.c             |    5 +
 src/backend/parser/gram.y                |   20 +-
 src/backend/parser/parse_relation.c      |   51 +
 src/backend/parser/parse_utilcmd.c       |    9 +
 src/backend/postmaster/autovacuum.c      |   10 +-
 src/backend/storage/buffer/bufmgr.c      |   11 +
 src/backend/storage/ipc/ipci.c           |    4 +
 src/backend/storage/ipc/procarray.c      |   80 ++
 src/backend/storage/lmgr/lwlock.c        |    4 +-
 src/backend/storage/lmgr/proc.c          |    2 +
 src/backend/utils/adt/dbsize.c           |    7 +
 src/backend/utils/adt/selfuncs.c         |   99 +-
 src/backend/utils/cache/lsyscache.c      |   14 +
 src/backend/utils/cache/relcache.c       |  115 +-
 src/backend/utils/misc/guc.c             |   32 +
 src/bin/pg_dump/pg_dump.c                |   49 +-
 src/bin/pg_upgrade/check.c               |    4 +-
 src/bin/pg_upgrade/info.c                |   21 +-
 src/bin/pg_upgrade/pg_upgrade.c          |   12 +-
 src/bin/pg_upgrade/pg_upgrade.h          |    2 +-
 src/bin/psql/describe.c                  |    3 +-
 src/bin/psql/tab-complete.c              |    7 +
 src/include/catalog/heap.h               |    2 +-
 src/include/catalog/index.h              |    1 +
 src/include/catalog/pg_class.h           |    1 +
 src/include/catalog/pg_proc.dat          |   34 +
 src/include/catalog/storage.h            |    2 +-
 src/include/catalog/storage_gtt.h        |   45 +
 src/include/commands/sequence.h          |    1 +
 src/include/commands/tablecmds.h         |    2 +-
 src/include/parser/parse_relation.h      |    3 +
 src/include/storage/bufpage.h            |    2 +
 src/include/storage/lwlock.h             |    1 +
 src/include/storage/proc.h               |    2 +
 src/include/storage/procarray.h          |    2 +
 src/include/utils/guc.h                  |    4 +
 src/include/utils/rel.h                  |   47 +-
 69 files changed, 3138 insertions(+), 206 deletions(-)
 create mode 100644 src/backend/catalog/storage_gtt.c
 create mode 100644 src/include/catalog/storage_gtt.h

diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 774a70f63df..692455ead0a 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -18,6 +18,7 @@
 #include "access/toast_internals.h"
 #include "access/visibilitymap.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "funcapi.h"
 #include "miscadmin.h"
 #include "storage/bufmgr.h"
@@ -226,6 +227,8 @@ verify_heapam(PG_FUNCTION_ARGS)
 	BlockNumber last_block;
 	BlockNumber nblocks;
 	const char *skip;
+	TransactionId	relfrozenxid = InvalidTransactionId;
+	MultiXactId		relminmxid = InvalidMultiXactId;
 
 	/* Check to see if caller supports us returning a tuplestore */
 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -342,6 +345,13 @@ verify_heapam(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(ctx.rel) &&
+		!gtt_storage_attached(RelationGetRelid(ctx.rel)))
+	{
+		relation_close(ctx.rel, AccessShareLock);
+		PG_RETURN_NULL();
+	}
+
 	/* Early exit if the relation is empty */
 	nblocks = RelationGetNumberOfBlocks(ctx.rel);
 	if (!nblocks)
@@ -409,9 +419,25 @@ verify_heapam(PG_FUNCTION_ARGS)
 
 	update_cached_xid_range(&ctx);
 	update_cached_mxid_range(&ctx);
-	ctx.relfrozenxid = ctx.rel->rd_rel->relfrozenxid;
+
+	if (RELATION_IS_GLOBAL_TEMP(ctx.rel))
+	{
+		get_gtt_relstats(RelationGetRelid(ctx.rel),
+						NULL,
+						NULL,
+						NULL,
+						&relfrozenxid,
+						&relminmxid);
+	}
+	else
+	{
+		relfrozenxid = ctx.rel->rd_rel->relfrozenxid;
+		relminmxid = ctx.rel->rd_rel->relminmxid;
+	}
+
+	ctx.relfrozenxid = relfrozenxid;
 	ctx.relfrozenfxid = FullTransactionIdFromXidAndCtx(ctx.relfrozenxid, &ctx);
-	ctx.relminmxid = ctx.rel->rd_rel->relminmxid;
+	ctx.relminmxid = relminmxid;
 
 	if (TransactionIdIsNormal(ctx.relfrozenxid))
 		ctx.oldest_xid = ctx.relfrozenxid;
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index b5602f53233..21b2d2a9527 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -159,6 +159,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * In order to avoid consistency problems, the global temporary table
+	 * uses ShareUpdateExclusiveLock.
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temporary table on commit options",
+			RELOPT_KIND_HEAP,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},
 	/* list terminator */
 	{{NULL}}
 };
@@ -1834,6 +1847,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 43ba03b6eb9..49f1052fdb1 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1023,7 +1023,7 @@ gistproperty(Oid index_oid, int attno,
 XLogRecPtr
 gistGetFakeLSN(Relation rel)
 {
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	if (RELATION_IS_TEMP(rel))
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index eb3810494f2..cbd22909582 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -151,7 +151,7 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	if (!RELATION_IS_TEMP(index))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index ec234a5e595..eb9f7f0eb48 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -52,6 +52,7 @@
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/atomics.h"
@@ -5844,6 +5845,19 @@ heap_abort_speculative(Relation relation, ItemPointer tid)
 	BlockNumber block;
 	Buffer		buffer;
 	TransactionId prune_xid;
+	TransactionId relfrozenxid = InvalidTransactionId;
+
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		get_gtt_relstats(RelationGetRelid(relation),
+						NULL,
+						NULL,
+						NULL,
+						&relfrozenxid,
+						NULL);
+	}
+	else
+		relfrozenxid = relation->rd_rel->relfrozenxid;
 
 	Assert(ItemPointerIsValid(tid));
 
@@ -5896,8 +5910,8 @@ heap_abort_speculative(Relation relation, ItemPointer tid)
 	 * TransactionXmin, so there's no race here).
 	 */
 	Assert(TransactionIdIsValid(TransactionXmin));
-	if (TransactionIdPrecedes(TransactionXmin, relation->rd_rel->relfrozenxid))
-		prune_xid = relation->rd_rel->relfrozenxid;
+	if (TransactionIdPrecedes(TransactionXmin, relfrozenxid))
+		prune_xid = relfrozenxid;
 	else
 		prune_xid = TransactionXmin;
 	PageSetPrunable(page, prune_xid);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 9befe012a9e..26fce0c4b83 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -593,7 +593,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
@@ -645,7 +645,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(RelationGetSmgr(rel), dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 558cc88a08a..0076666971e 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -63,6 +63,7 @@
 #include "access/xlog.h"
 #include "catalog/index.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -507,6 +508,25 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 	TransactionId OldestXmin;
 	TransactionId FreezeLimit;
 	MultiXactId MultiXactCutoff;
+	TransactionId	relfrozenxid = InvalidTransactionId;
+	MultiXactId		relminmxid = InvalidMultiXactId;
+	double			reltuples = 0;
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		get_gtt_relstats(RelationGetRelid(rel),
+						NULL,
+						&reltuples,
+						NULL,
+						&relfrozenxid,
+						&relminmxid);
+	}
+	else
+	{
+		relfrozenxid = rel->rd_rel->relfrozenxid;
+		relminmxid = rel->rd_rel->relminmxid;
+		reltuples = rel->rd_rel->reltuples;
+	}
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
@@ -542,9 +562,9 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 	 * table's minimum MultiXactId is older than or equal to the requested
 	 * mxid full-table scan limit; or if DISABLE_PAGE_SKIPPING was specified.
 	 */
-	aggressive = TransactionIdPrecedesOrEquals(rel->rd_rel->relfrozenxid,
+	aggressive = TransactionIdPrecedesOrEquals(relfrozenxid,
 											   xidFullScanLimit);
-	aggressive |= MultiXactIdPrecedesOrEquals(rel->rd_rel->relminmxid,
+	aggressive |= MultiXactIdPrecedesOrEquals(relminmxid,
 											  mxactFullScanLimit);
 	if (params->options & VACOPT_DISABLE_PAGE_SKIPPING)
 		aggressive = true;
@@ -591,9 +611,9 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 	}
 
 	vacrel->bstrategy = bstrategy;
-	vacrel->relfrozenxid = rel->rd_rel->relfrozenxid;
-	vacrel->relminmxid = rel->rd_rel->relminmxid;
-	vacrel->old_live_tuples = rel->rd_rel->reltuples;
+	vacrel->relfrozenxid = relfrozenxid;
+	vacrel->relminmxid = relminmxid;
+	vacrel->old_live_tuples = reltuples;
 
 	/* Set cutoffs for entire VACUUM */
 	vacrel->OldestXmin = OldestXmin;
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 5bc7c3616a9..0b261f723d7 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -677,6 +678,14 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		/*
+		 * If a global temporary table storage file is not initialized in the
+		 * current backend, its index does not have a root page, just returns 0.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 4e6efda97f3..ae12fd91a1b 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -44,6 +44,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index aa7d4d5456b..595cb03eb4a 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -504,6 +504,7 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 81cc39fb70e..0bf05f37a80 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -62,6 +62,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,
@@ -366,13 +368,26 @@ heap_create(const char *relname,
 			break;
 	}
 
+	/* For global temporary table, even if the storage is not initialized,
+	 * the relfilenode needs to be generated and put into the catalog.
+	 */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		create_storage = false;
+		if (OidIsValid(relfilenode))
+			elog(ERROR, "global temporary table can not reuse an existing relfilenode");
+
+		relfilenode = relid;
+	}
 	/*
 	 * Decide whether to create storage. If caller passed a valid relfilenode,
 	 * storage is already created, so don't do it here.  Also don't create it
 	 * for relkinds without physical storage.
 	 */
-	if (!RELKIND_HAS_STORAGE(relkind) || OidIsValid(relfilenode))
+	else if (!RELKIND_HAS_STORAGE(relkind) || OidIsValid(relfilenode))
+	{
 		create_storage = false;
+	}
 	else
 	{
 		create_storage = true;
@@ -427,7 +442,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:
@@ -445,7 +460,8 @@ heap_create(const char *relname,
 	 * protected by the existence of a physical file; but for relations with
 	 * no files, add a pg_shdepend entry to account for that.
 	 */
-	if (!create_storage && reltablespace != InvalidOid)
+	if (!create_storage && reltablespace != InvalidOid &&
+		relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
 		recordDependencyOnTablespace(RelationRelationId, relid,
 									 reltablespace);
 
@@ -1001,6 +1017,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -1039,8 +1056,21 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/*
+	 * The transaction information of the global temporary table is stored
+	 * in the local hash table, not in catalog.
+	 */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1413,6 +1443,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1498,8 +1529,9 @@ heap_create_with_catalog(const char *relname,
 	/*
 	 * If there's a special on-commit action, remember it
 	 */
-	if (oncommit != ONCOMMIT_NOOP)
-		register_on_commit_action(relid, oncommit);
+	if (oncommit != ONCOMMIT_NOOP &&
+		relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+		register_on_commit_action(relid, oncommit, false);
 
 	/*
 	 * ok, the relation has been cataloged, so close our relations and return
@@ -1996,6 +2028,19 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/*
+	 * Only when other sessions are not using this Global temporary table,
+	 * is it allowed to DROP it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot drop global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3280,7 +3325,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3292,7 +3337,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3328,7 +3373,7 @@ RelationTruncateIndexes(Relation heapRelation)
  * ON COMMIT truncation of temporary tables, where it doesn't matter.
  */
 void
-heap_truncate(List *relids)
+heap_truncate(List *relids, bool is_global_temp)
 {
 	List	   *relations = NIL;
 	ListCell   *cell;
@@ -3338,8 +3383,23 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode;
+
+		/*
+		 * Truncate global temporary table only clears local data,
+		 * so only low-level locks need to be held.
+		 */
+		if (is_global_temp)
+		{
+			if (!gtt_storage_attached(rid))
+				continue;
 
-		rel = table_open(rid, AccessExclusiveLock);
+			lockmode = RowExclusiveLock;
+		}
+		else
+			lockmode = AccessExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3372,6 +3432,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3380,23 +3441,39 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	/*
+	 * Truncate GTT only clears local data, so only low-level locks
+	 * need to be held.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		lockmode = AccessShareLock;
+	else
+		lockmode = AccessExclusiveLock;
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	/*
+	 * After the data is cleaned up on the GTT, the transaction information
+	 * for the data(stored in local hash table) is also need reset.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(RelationGetRelid(rel), 0, 0, 0, RecentXmin, GetOldestMultiXactId());
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index c255806e38c..7a52322ede4 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -54,6 +54,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"
@@ -734,6 +735,25 @@ index_create(Relation heapRelation,
 	TransactionId relfrozenxid;
 	MultiXactId relminmxid;
 
+	/* For global temporary table only */
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temporary table with concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot reindex global temporary tables concurrently")));
+
+		/*
+		 * For the case that some backend is applied relcache message to create
+		 * an index on a global temporary table, if this table in the current
+		 * backend are not initialized, the creation of index storage on the
+		 * table are also skipped.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/* constraint flags can only be set when a constraint is requested */
 	Assert((constr_flags == 0) ||
 		   ((flags & INDEX_CREATE_ADD_CONSTRAINT) != 0));
@@ -2117,7 +2137,7 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 * lock (see comments in RemoveRelations), and a non-concurrent DROP is
 	 * more efficient.
 	 */
-	Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+	Assert(!RelpersistenceTsTemp(get_rel_persistence(indexId)) ||
 		   (!concurrent && !concurrent_lock_mode));
 
 	/*
@@ -2149,6 +2169,21 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/*
+	 * Allow to drop index on global temporary table when only current
+	 * backend use it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation) &&
+		is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+	{
+		ereport(ERROR,
+			(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+			 errmsg("cannot drop index %s on global temporary table %s",
+					RelationGetRelationName(userIndexRelation), RelationGetRelationName(userHeapRelation)),
+					errdetail("Because the index is created on the global temporary table and other backend attached it."),
+					errhint("Please try detach all sessions using this temporary table and try again.")));
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2757,6 +2792,7 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = RELATION_IS_GLOBAL_TEMP(rel);
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2851,20 +2887,37 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
-		{
-			rd_rel->relpages = (int32) relpages;
-			dirty = true;
-		}
-		if (rd_rel->reltuples != (float4) reltuples)
+		/* For global temporary table */
+		if (is_gtt)
 		{
-			rd_rel->reltuples = (float4) reltuples;
-			dirty = true;
+			/* Update GTT'statistics into local relcache */
+			rel->rd_rel->relpages = (int32) relpages;
+			rel->rd_rel->reltuples = (float4) reltuples;
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+
+			/* Update GTT'statistics into local hashtable */
+			up_gtt_relstats(RelationGetRelid(rel), relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+		else
 		{
-			rd_rel->relallvisible = (int32) relallvisible;
-			dirty = true;
+			if (rd_rel->relpages != (int32) relpages)
+			{
+				rd_rel->relpages = (int32) relpages;
+				dirty = true;
+			}
+
+			if (rd_rel->reltuples != (float4) reltuples)
+			{
+				rd_rel->reltuples = (float4) reltuples;
+				dirty = true;
+			}
+
+			if (rd_rel->relallvisible != (int32) relallvisible)
+			{
+				rd_rel->relallvisible = (int32) relallvisible;
+				dirty = true;
+			}
 		}
 	}
 
@@ -2977,6 +3030,26 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, progress_index, progress_vals);
 	}
 
+	/* For build index on global temporary table */
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		/*
+		 * If the storage for the index in this session is not initialized,
+		 * it needs to be created.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			/* Before create init storage, fix the local Relcache first */
+			force_enable_gtt_index(indexRelation);
+
+			Assert(gtt_storage_attached(RelationGetRelid(heapRelation)));
+
+			/* Init storage for index */
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3518,6 +3591,8 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	PGRUsage	ru0;
 	bool		progress = ((params->options & REINDEXOPT_REPORT_PROGRESS) != 0);
 	bool		set_tablespace = false;
+	LOCKMODE	lockmode_on_heap = ShareLock;
+	LOCKMODE	lockmode_on_index = AccessExclusiveLock;
 
 	pg_rusage_init(&ru0);
 
@@ -3531,10 +3606,29 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	if (!OidIsValid(heapId))
 		return;
 
+	/*
+	 * For reindex on global temporary table, If the storage for the index
+	 * in current backend is not initialized, nothing is done.
+	 */
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		if (!gtt_storage_attached(indexId))
+		{
+			/* Suppress use of the target index while rebuilding it */
+			SetReindexProcessing(heapId, indexId);
+			/* Re-allow use of target index */
+			ResetReindexProcessing();
+			return;
+		}
+
+		lockmode_on_heap = AccessShareLock;
+		lockmode_on_index = AccessShareLock;
+	}
+
 	if ((params->options & REINDEXOPT_MISSING_OK) != 0)
-		heapRelation = try_table_open(heapId, ShareLock);
+		heapRelation = try_table_open(heapId, lockmode_on_heap);
 	else
-		heapRelation = table_open(heapId, ShareLock);
+		heapRelation = table_open(heapId, lockmode_on_heap);
 
 	/* if relation is gone, leave */
 	if (!heapRelation)
@@ -3560,7 +3654,7 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	 * Open the target index relation and get an exclusive lock on it, to
 	 * ensure that no one else is touching this particular index.
 	 */
-	iRel = index_open(indexId, AccessExclusiveLock);
+	iRel = index_open(indexId, lockmode_on_index);
 
 	if (progress)
 		pgstat_progress_update_param(PROGRESS_CREATEIDX_ACCESS_METHOD_OID,
@@ -3811,6 +3905,12 @@ reindex_relation(Oid relid, int flags, ReindexParams *params)
 	bool		result;
 	ListCell   *indexId;
 	int			i;
+	LOCKMODE	lockmode;
+
+	if (flags & REINDEX_REL_PROCESS_GLOBAL_TEMP)
+		lockmode = AccessShareLock;
+	else
+		lockmode = ShareLock;
 
 	/*
 	 * Open and lock the relation.  ShareLock is sufficient since we only need
@@ -3818,9 +3918,9 @@ reindex_relation(Oid relid, int flags, ReindexParams *params)
 	 * should match ReindexTable().
 	 */
 	if ((params->options & REINDEXOPT_MISSING_OK) != 0)
-		rel = try_table_open(relid, ShareLock);
+		rel = try_table_open(relid, lockmode);
 	else
-		rel = table_open(relid, ShareLock);
+		rel = table_open(relid, lockmode);
 
 	/* if relation is gone, leave */
 	if (!rel)
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 4de8400fd0f..fe3fcc712cb 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -656,6 +656,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/* Do not allow create global temporary table in temporary schemas */
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp table in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index c5ad28d71fe..707068a6fd8 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,6 +27,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -61,6 +62,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			temprelOid;			/* InvalidOid if not a global temporary rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -115,7 +117,7 @@ AddPendingSync(const RelFileNode *rnode)
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -126,7 +128,12 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/*
+		 * Global temporary table and local temporary table use same
+		 * design on storage module.
+		 */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -154,6 +161,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
@@ -165,6 +173,21 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		AddPendingSync(&rnode);
 	}
 
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		Assert(rel && RELATION_IS_GLOBAL_TEMP(rel));
+
+		/*
+		 * Remember the reloid of global temporary table, which is used for
+		 * transaction commit or rollback.
+		 * see smgrDoPendingDeletes.
+		 */
+		pending->temprelOid = RelationGetRelid(rel);
+
+		/* Remember global temporary table storage info to localhash */
+		remember_gtt_storage_info(rnode, rel);
+	}
+
 	return srel;
 }
 
@@ -201,11 +224,20 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/*
+	 * Remember the reloid of global temporary table, which is used for
+	 * transaction commit or rollback.
+	 * see smgrDoPendingDeletes.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->temprelOid = RelationGetRelid(rel);
+
 	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
@@ -618,6 +650,7 @@ smgrDoPendingDeletes(bool isCommit)
 	int			nrels = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -647,14 +680,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->temprelOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -664,12 +701,21 @@ smgrDoPendingDeletes(bool isCommit)
 
 	if (nrels > 0)
 	{
+		int	i;
+
 		smgrdounlinkall(srels, nrels, false);
 
-		for (int i = 0; i < nrels; i++)
+		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* Delete global temporary table info in localhash */
+			if (gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 00000000000..3eb8a5468cc
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1509 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  The body implementation of Global Temparary table.
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *	  See src/backend/catalog/GTT_README for Global temparary table's
+ *	  requirements and design.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_info_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+
+int		vacuum_gtt_defer_check_age = 0;
+
+/*
+ * The Global temporary table's shared hash table data structure
+ */
+typedef struct gtt_ctl_data
+{
+	LWLock		lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+/* record this global temporary table in which backends are being used */
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+/*
+ * The Global temporary table's local hash table data structure
+ */
+/* Record the storage information and statistical information of the global temporary table */
+typedef struct
+{
+	Oid			relfilenode;
+	Oid			spcnode;
+
+	/* pg_class relstat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+
+	/* pg_statistic column stat */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_free_statistics(gtt_relfilenode *rnode);
+static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok);
+static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok);
+static Bitmapset *copy_active_gtt_bitmap(Oid relid);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+/*
+ * Calculate shared hash table entry size for GTT.
+ */
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	/* hash entry header size */
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	/*
+	 * hash entry data size
+	 * this is a bitmap in shared memory, each backend have a bit.
+	 */
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+/*
+ * Calculate shared hash table max size for GTT.
+ */
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	/* shared hash header size */
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	/* hash entry size */
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	/* max size */
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+/*
+ * Initialization shared hash table for GTT.
+ */
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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();
+	}
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(gtt_fnode);
+	info.entrysize = action_gtt_shared_hash_entry_size();
+	active_gtt_shared_hash =
+		ShmemInitHash("active gtt shared hash",
+						gtt_shared_ctl->max_entry,
+						gtt_shared_ctl->max_entry,
+						&info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+/*
+ * Record GTT relid to shared hash table, which means that current backend is using this GTT.
+ */
+static void
+gtt_storage_checkin(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			found;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *)&(fnode), 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_global_temporary_table.")));
+	}
+
+	if (!found)
+	{
+		int			wordnum;
+
+		/* init bitmap */
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	/* record itself in bitmap */
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+/*
+ * Remove the GTT relid record from the shared hash table which means that current backend is
+ * not use this GTT.
+ */
+static void
+gtt_storage_checkout(Oid relid, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		if (isCommit)
+			elog(WARNING, "relid %u not exist in gtt shared hash when drop local storage", relid);
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+	/* remove itself from bitmap */
+	bms_del_member(entry->map, MyBackendId);
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return;
+}
+
+/*
+ * Gets usage information for a GTT from shared hash table.
+ * The information is in the form of bitmap.
+ * Quickly copy the entire bitmap from shared memory and return it.
+ * that to avoid holding locks for a long time.
+ */
+static Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	Bitmapset		*map_copy = NULL;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry)
+	{
+		Assert(entry->map);
+		/* copy the entire bitmap */
+		if (!bms_is_empty(entry->map))
+			map_copy = bms_copy(entry->map);
+	}
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return map_copy;
+}
+
+/*
+ * Check if there are other backends using this GTT besides the current backend.
+ */
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			in_use = false;
+	int			num_use = 0;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		return false;
+	}
+
+	Assert(entry->map);
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+	/* how many backend are using this GTT */
+	num_use = bms_num_members(entry->map);
+	if (num_use == 0)
+		in_use = false;
+	else if (num_use == 1)
+	{
+		/* check if this is itself */
+		if(bms_is_member(MyBackendId, entry->map))
+			in_use = false;
+		else
+			in_use = true;
+	}
+	else
+		in_use = true;
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return in_use;
+}
+
+/*
+ * Record GTT information to local hash.
+ * They include GTT storage info, transaction info and statistical info.
+ */
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+	gtt_local_hash_entry		*entry;
+	MemoryContext			oldcontext;
+	gtt_relfilenode			*new_node = NULL;
+	Oid				relid = RelationGetRelid(rel);
+	int				natts = 0;
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	/* First time through: initialize the hash table */
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		HASHCTL		ctl;
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_info_context =
+			AllocSetContextCreate(CacheMemoryContext,
+								"gtt info context",
+								ALLOCSET_DEFAULT_SIZES);
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		ctl.hcxt = gtt_info_context;
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+	}
+
+	Assert(CacheMemoryContext);
+	Assert(gtt_info_context);
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	entry = gtt_search_by_relid(relid, true);
+	if (!entry)
+	{
+		bool		found = false;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash,
+						(void *) &relid, HASH_ENTER, &found);
+
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			/* record the on commit clause */
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS, true);
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			gtt_storage_checkin(relid);
+		}
+	}
+
+	/* record storage info relstat columnstats and transaction info to relfilenode list */
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	new_node->natts = 0;
+	new_node->attnum = NULL;
+	new_node->att_stat_tups = NULL;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* init column stats structure */
+	natts = RelationGetNumberOfAttributes(rel);
+	new_node->attnum = palloc0(sizeof(int) * natts);
+	new_node->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+	new_node->natts = natts;
+
+	/* only heap rel or toast rel have transaction info */
+	if (entry->relkind == RELKIND_RELATION ||
+		entry->relkind == RELKIND_TOASTVALUE)
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+
+	/* Registration callbacks are used to trigger cleanup during process exit */
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+/*
+ * Remove GTT information from local hash when transaction commit/rollback.
+ */
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	gtt_relfilenode		*d_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+		return;
+	}
+
+	d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+		else
+		{
+			/* rollback transaction */
+			if (entry->relfilenode_list == NIL)
+			{
+				if (entry->relkind == RELKIND_RELATION ||
+					entry->relkind == RELKIND_SEQUENCE)
+					gtt_storage_checkout(relid, isCommit);
+
+				hash_search(gtt_storage_local_hash,
+						(void *) &(relid), HASH_REMOVE, NULL);
+			}
+
+			return;
+		}
+	}
+
+	/* Clean up transaction info from Local order list and MyProc */
+	if (entry->relkind == RELKIND_RELATION ||
+		entry->relkind == RELKIND_TOASTVALUE)
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+
+		/* this is valid relfrozenxid */
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	/* delete relfilenode from rel entry */
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	gtt_free_statistics(d_rnode);
+
+	if (entry->relfilenode_list == NIL)
+	{
+		/* tell shared hash that current backend will no longer use this GTT */
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(relid, isCommit);
+
+		hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_REMOVE, NULL);
+	}
+
+	return;
+}
+
+/*
+ * Check if current backend is using this GTT.
+ */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool			found = false;
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (!OidIsValid(relid))
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry)
+		found = true;
+
+	return found;
+}
+
+/*
+ * When backend exit, bulk cleaning all GTT storage and local buffer of this backend.
+ */
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS			status;
+	gtt_local_hash_entry	*entry;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	/* Need to ensure we have a usable transaction. */
+	AbortOutOfAnyTransaction();
+
+	/* Search all relfilenode for GTT in current backend */
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel[1];
+			RelFileNode		rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel[0] = smgropen(rnode, MyBackendId);
+			smgrdounlinkall(srel, 1, false);
+			smgrclose(srel[0]);
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(entry->relid, false);
+
+		hash_search(gtt_storage_local_hash, (void *) &(entry->relid), HASH_REMOVE, NULL);
+	}
+
+	/* set to global area */
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;
+
+	return;
+}
+
+/*
+ * Update GTT relstats(relpage/reltuple/relallvisible)
+ * to local hash.
+ */
+void
+up_gtt_relstats(Oid relid,
+					BlockNumber num_pages,
+					double num_tuples,
+					BlockNumber num_all_visible_pages,
+					TransactionId relfrozenxid,
+					TransactionId relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (!OidIsValid(relid))
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (num_pages > 0 &&
+		gtt_rnode->relpages != (int32)num_pages)
+		gtt_rnode->relpages = (int32)num_pages;
+
+	if (num_tuples > 0 &&
+		gtt_rnode->reltuples != (float4)num_tuples)
+		gtt_rnode->reltuples = (float4)num_tuples;
+
+	/* only heap contain transaction information and relallvisible */
+	if (entry->relkind == RELKIND_RELATION ||
+		entry->relkind == RELKIND_TOASTVALUE)
+	{
+		if (num_all_visible_pages > 0 &&
+			gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+		{
+			gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+		}
+
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNextTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			/* set to local order list */
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			/* set to global area */
+			set_gtt_session_relfrozenxid();
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search GTT relstats(relpage/reltuple/relallvisible)
+ * from local has.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update GTT info(definition is same as pg_statistic)
+ * to local hash.
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+	MemoryContext		oldcontext;
+	bool		found = false;
+	int			i = 0;
+
+	/* not support whole row or system column */
+	if (attnum <= 0)
+		return;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return;
+
+	Assert(entry->relid == reloid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	/* switch context to gtt_info_context for store tuple at heap_form_tuple */
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == 0)
+		{
+			Assert(gtt_rnode->att_stat_tups[i] == NULL);
+			gtt_rnode->attnum[i] = attnum;
+			gtt_rnode->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+			found = true;
+			break;
+		}
+		else if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			heap_freetuple(gtt_rnode->att_stat_tups[i]);
+			gtt_rnode->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+			found = true;
+			break;
+		}
+	}
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!found)
+		elog(WARNING, "analyze can not update relid %u column %d statistics after add or drop column, try truncate table first", reloid, attnum);
+
+	return;
+}
+
+/*
+ * Search GTT statistic info(definition is same as pg_statistic)
+ * from local hash.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	int			i = 0;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	/* not support whole row or system column */
+	if (attnum <= 0)
+		return NULL;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return NULL;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return NULL;
+
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			return gtt_rnode->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Insert a RelfrozenXID into the list and keep the list in order.
+ */
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int		i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Remove a RelfrozenXID from order list gtt_session_relfrozenxid_list.
+ */
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+/*
+ * Update of backend Level oldest relfrozenxid to MyProc.
+ * This makes each backend's oldest RelFrozenxID globally visible.
+ */
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list != NIL)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	if (MyProc->backend_gtt_frozenxid != gtt_frozenxid)
+		MyProc->backend_gtt_frozenxid = gtt_frozenxid;
+}
+
+/*
+ * Get GTT column level data statistics.
+ */
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate		*tupstore;
+	HeapTuple		tuple;
+	Relation		rel = NULL;
+	Oid			reloid = PG_GETARG_OID(0);
+	int			attnum = PG_GETARG_INT32(1);
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	if (!RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	/* get data from local hash */
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum		values[Natts_pg_statistic];
+		bool		isnull[Natts_pg_statistic];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, AccessShareLock);
+	relation_close(pg_tatistic, AccessShareLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get GTT table level data statistics.
+ */
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate	*tupstore;
+	TupleDesc	tupdesc;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid			reloid = PG_GETARG_OID(0);
+	Oid			relnode = 0;
+	BlockNumber		relpages = 0;
+	BlockNumber		relallvisible = 0;
+	uint32			relfrozenxid = 0;
+	uint32			relminmxid = 0;
+	double			reltuples = 0;
+	Relation		rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	if (!RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[6];
+		bool	isnull[6];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get a list of backend pids that are currently using this GTT.
+ */
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	PGPROC			*proc = NULL;
+	Bitmapset		*map = NULL;
+	Tuplestorestate		*tupstore;
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	HeapTuple		tuple;
+	Oid			reloid = PG_GETARG_OID(0);
+	Relation		rel = NULL;
+	int				backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	if (!RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	/* get data from share hash */
+	map = copy_active_gtt_bitmap(reloid);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			/* backendid map to process pid */
+			proc = BackendIdGetProc(backendid);
+			if (proc && proc->pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+				pid_t	pid = proc->pid;
+
+				memset(isnull, 0, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, AccessShareLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get backend level oldest relfrozenxid of each backend using GTT in current database.
+ */
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate		*tupstore;
+	int			*pids = NULL;
+	uint32			*xids = NULL;
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	HeapTuple		tuple;
+	int			num_xid = MaxBackends + 1;
+	int			i = 0;
+	int			j = 0;
+	uint32			oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	if (pids == NULL || xids == NULL)
+		elog(ERROR, "out of memory");
+
+	/* Get backend level oldest relfrozenxid in all backend that in MyDatabaseId use GTT */
+	oldest = list_all_backend_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		/* save oldest relfrozenxid */
+		pids[i] = 0;
+		xids[i] = oldest;
+		i++;
+
+		/* save relfrozenxid for each session */
+		for (j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, 0, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+/*
+ * In order to build the GTT index, force enable GTT'index.
+ */
+void
+force_enable_gtt_index(Relation index)
+{
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(RelationGetRelid(index)));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+/*
+ * Fix the local state of the GTT's index.
+ */
+void
+gtt_fix_index_backend_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid heapOid = index->rd_index->indrelid;
+
+	/* Must be GTT */
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	/*
+	 * If this GTT is not initialized in the current backend,
+	 * its index status is temporarily set to invalid(local relcache).
+	 */
+	if (gtt_storage_attached(heapOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+/*
+ * During the SQL initialization of the executor (InitPlan)
+ * Initialize storage of GTT GTT'indexes and build empty index.
+ */
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+	Relation	relation = resultRelInfo->ri_RelationDesc;
+	int		i;
+	Oid		toastrelid;
+
+	if (!(operation == CMD_UTILITY || operation == CMD_INSERT))
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	/* Each GTT is initialized once in each backend */
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	/* init heap storage */
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	/* rebuild all local index for global temp table */
+	for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+	{
+		Relation	index = resultRelInfo->ri_IndexRelationDescs[i];
+		IndexInfo	*info = resultRelInfo->ri_IndexRelationInfo[i];
+
+		index_build(relation, index, info, true, false);
+
+		/* after build index, index re-enabled */
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+		Assert(info->ii_ReadyForInserts);
+	}
+
+	/* rebuild index for global temp toast table */
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+
+		/* init index storage */
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid			indexId = lfirst_oid(indlist);
+			Relation	currentIndex;
+			IndexInfo	*indexInfo;
+
+			currentIndex = index_open(indexId, RowExclusiveLock);
+			/* build empty index */
+			indexInfo = BuildDummyIndexInfo(currentIndex);
+			index_build(toastrel, currentIndex, indexInfo, true, false);
+			Assert(currentIndex->rd_index->indisvalid);
+			Assert(currentIndex->rd_index->indislive);
+			Assert(currentIndex->rd_index->indisready);
+			index_close(currentIndex, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+/*
+ * Release the data structure memory used to store GTT storage info.
+ */
+static void
+gtt_free_statistics(gtt_relfilenode *rnode)
+{
+	int i;
+
+	Assert(rnode);
+
+	for (i = 0; i < rnode->natts; i++)
+	{
+		if (rnode->att_stat_tups[i])
+		{
+			heap_freetuple(rnode->att_stat_tups[i]);
+			rnode->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (rnode->attnum)
+		pfree(rnode->attnum);
+
+	if (rnode->att_stat_tups)
+		pfree(rnode->att_stat_tups);
+
+	pfree(rnode);
+
+	return;
+}
+
+/*
+ * Get the current relfilenode of this GTT.
+ */
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
+/*
+ * Get a relfilenode used by this GTT during the transaction life cycle.
+ */
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok)
+{
+	gtt_relfilenode		*rnode = NULL;
+	ListCell		*lc;
+
+	Assert(entry);
+
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == relfilenode)
+		{
+			rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (!missing_ok && rnode == NULL)
+		elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+	return rnode;
+}
+
+/*
+ * Get one GTT info from local hash.
+ */
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry == NULL && !missing_ok)
+		elog(ERROR, "relid %u not found in local hash", relid);
+
+	return entry;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index eb560955cda..abfd0f6e03f 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 4928702aec0..fc6e64a79b6 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -104,7 +105,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -185,6 +186,17 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -601,14 +613,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]));
 		}
 
 		/*
@@ -1622,7 +1635,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;
@@ -1724,31 +1737,48 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/*
+		 * For global temporary table,
+		 * Update column statistic to localhash, not catalog.
+		 */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 9d22f648a84..a44eefa1f6b 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/toasting.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
@@ -390,6 +391,22 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+	{
+		if (gtt_storage_attached(RelationGetRelid(OldHeap)))
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("not support cluster global temporary table yet")));
+
+		relation_close(OldHeap, AccessExclusiveLock);
+		pgstat_progress_end_command();
+		return;
+	}
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
@@ -585,6 +602,8 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
 	TransactionId frozenXid;
 	MultiXactId cutoffMulti;
 
+	Assert(!RELATION_IS_GLOBAL_TEMP(OldHeap));
+
 	/* Mark the correct index as clustered */
 	if (OidIsValid(indexOid))
 		mark_index_clustered(OldHeap, indexOid, true);
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 53f48531419..c03191cce94 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -289,7 +289,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP_ON_CURRENT_SESSION(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, whereClause,
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index f366a818a14..56269b6e38f 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -30,6 +30,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/copyfrom_internal.h"
 #include "commands/progress.h"
@@ -659,6 +660,9 @@ CopyFrom(CopyFromState cstate)
 
 	ExecOpenIndices(resultRelInfo, false);
 
+	/* Check and init global temporary table storage in current backend */
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	/*
 	 * Set up a ModifyTableState so we can let FDW(s) init themselves for
 	 * foreign-table result relation(s).
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index c14ca27c5ed..d65ae895356 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -111,6 +111,7 @@ struct ReindexIndexCallbackState
 {
 	ReindexParams params;		/* options from statement */
 	Oid			locked_table_oid;	/* tracks previously locked table */
+	LOCKMODE	lockmode;
 };
 
 /*
@@ -570,7 +571,7 @@ DefineIndex(Oid relationId,
 	 * is more efficient.  Do this before any use of the concurrent option is
 	 * done.
 	 */
-	if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+	if (stmt->concurrent && !RelpersistenceTsTemp(get_rel_persistence(relationId)))
 		concurrent = true;
 	else
 		concurrent = false;
@@ -2581,9 +2582,9 @@ ReindexIndex(RangeVar *indexRelation, ReindexParams *params, bool isTopLevel)
 	 */
 	state.params = *params;
 	state.locked_table_oid = InvalidOid;
+	state.lockmode = AccessShareLock;
 	indOid = RangeVarGetRelidExtended(indexRelation,
-									  (params->options & REINDEXOPT_CONCURRENTLY) != 0 ?
-									  ShareUpdateExclusiveLock : AccessExclusiveLock,
+									  AccessShareLock,
 									  0,
 									  RangeVarCallbackForReindexIndex,
 									  &state);
@@ -2594,11 +2595,25 @@ ReindexIndex(RangeVar *indexRelation, ReindexParams *params, bool isTopLevel)
 	 */
 	persistence = get_rel_persistence(indOid);
 	relkind = get_rel_relkind(indOid);
+	if (persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		LOCKMODE	lockmode;
+
+		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0)
+			lockmode = ShareUpdateExclusiveLock;
+		else
+			lockmode = AccessExclusiveLock;
+
+		/* lock heap first */
+		Assert(OidIsValid(state.locked_table_oid));
+		LockRelationOid(state.locked_table_oid, lockmode);
+		LockRelationOid(indOid, lockmode);
+	}
 
 	if (relkind == RELKIND_PARTITIONED_INDEX)
 		ReindexPartitions(indOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 persistence != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(persistence))
 		ReindexRelationConcurrently(indOid, params);
 	else
 	{
@@ -2620,15 +2635,7 @@ RangeVarCallbackForReindexIndex(const RangeVar *relation,
 {
 	char		relkind;
 	struct ReindexIndexCallbackState *state = arg;
-	LOCKMODE	table_lockmode;
-
-	/*
-	 * Lock level here should match table lock in reindex_index() for
-	 * non-concurrent case and table locks used by index_concurrently_*() for
-	 * concurrent case.
-	 */
-	table_lockmode = (state->params.options & REINDEXOPT_CONCURRENTLY) != 0 ?
-		ShareUpdateExclusiveLock : ShareLock;
+	LOCKMODE	table_lockmode = state->lockmode;
 
 	/*
 	 * If we previously locked some other index's heap, and the name we're
@@ -2689,6 +2696,8 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 {
 	Oid			heapOid;
 	bool		result;
+	char		relpersistence;
+	int 		reindex_flags = 0;
 
 	/*
 	 * The lock level used here should match reindex_relation().
@@ -2699,15 +2708,27 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 	 * locks on our temporary table.
 	 */
 	heapOid = RangeVarGetRelidExtended(relation,
-									   (params->options & REINDEXOPT_CONCURRENTLY) != 0 ?
-									   ShareUpdateExclusiveLock : ShareLock,
+									   AccessShareLock,
 									   0,
 									   RangeVarCallbackOwnsTable, NULL);
 
+	relpersistence = get_rel_persistence(heapOid);
+	if (relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		LOCKMODE	lockmode;
+
+		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0)
+			lockmode = ShareUpdateExclusiveLock;
+		else
+			lockmode = ShareLock;
+
+		LockRelationOid(heapOid, lockmode);
+	}
+
 	if (get_rel_relkind(heapOid) == RELKIND_PARTITIONED_TABLE)
 		ReindexPartitions(heapOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(relpersistence))
 	{
 		result = ReindexRelationConcurrently(heapOid, params);
 
@@ -2721,9 +2742,14 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 		ReindexParams newparams = *params;
 
 		newparams.options |= REINDEXOPT_REPORT_PROGRESS;
+
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			reindex_flags |= REINDEX_REL_PROCESS_GLOBAL_TEMP;
+
+		reindex_flags |= REINDEX_REL_PROCESS_TOAST;
+		reindex_flags |= REINDEX_REL_CHECK_CONSTRAINTS;
 		result = reindex_relation(heapOid,
-								  REINDEX_REL_PROCESS_TOAST |
-								  REINDEX_REL_CHECK_CONSTRAINTS,
+								  reindex_flags,
 								  &newparams);
 		if (!result)
 			ereport(NOTICE,
@@ -3122,7 +3148,7 @@ ReindexMultipleInternal(List *relids, ReindexParams *params)
 			   relkind != RELKIND_PARTITIONED_TABLE);
 
 		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			relpersistence != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(relpersistence))
 		{
 			ReindexParams newparams = *params;
 
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 62465bacd81..ef37f79ba68 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -51,12 +51,32 @@ LockTableCommand(LockStmt *lockstmt)
 		RangeVar   *rv = (RangeVar *) lfirst(p);
 		bool		recurse = rv->inh;
 		Oid			reloid;
+		LOCKMODE	lockmode = lockstmt->mode;
+		char		relpersistence;
 
-		reloid = RangeVarGetRelidExtended(rv, lockstmt->mode,
-										  lockstmt->nowait ? RVR_NOWAIT : 0,
+		reloid = RangeVarGetRelidExtended(rv, NoLock, 0,
 										  RangeVarCallbackForLockTable,
 										  (void *) &lockstmt->mode);
 
+		relpersistence = get_rel_persistence(reloid);
+		if (relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			if (!lockstmt->nowait)
+				LockRelationOid(reloid, lockmode);
+			else if (!ConditionalLockRelationOid(reloid, lockmode))
+			{
+				/* try to throw error by name; relation could be deleted... */
+				char	   *relname = get_rel_name(reloid);
+
+				if (!relname)
+					return;		/* child concurrently dropped, just skip it */
+				ereport(ERROR,
+						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						 errmsg("could not obtain lock on relation \"%s\"",
+								relname)));
+			}
+		}
+
 		if (get_rel_relkind(reloid) == RELKIND_VIEW)
 			LockViewRecurse(reloid, lockstmt->mode, lockstmt->nowait, NIL);
 		else if (recurse)
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 72bfdc07a49..e5257f610f6 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -220,9 +223,12 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	rel = table_open(seqoid, AccessExclusiveLock);
 	tupDesc = RelationGetDescr(rel);
 
-	/* now initialize the sequence's data */
-	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	if (!RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		/* now initialize the sequence's data */
+		tuple = heap_form_tuple(tupDesc, value, null);
+		fill_seq_with_data(rel, tuple);
+	}
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -275,8 +281,6 @@ ResetSequence(Oid seq_relid)
 	Buffer		buf;
 	HeapTupleData seqdatatuple;
 	HeapTuple	tuple;
-	HeapTuple	pgstuple;
-	Form_pg_sequence pgsform;
 	int64		startv;
 
 	/*
@@ -287,12 +291,7 @@ ResetSequence(Oid seq_relid)
 	init_sequence(seq_relid, &elm, &seq_rel);
 	(void) read_seq_tuple(seq_rel, &buf, &seqdatatuple);
 
-	pgstuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seq_relid));
-	if (!HeapTupleIsValid(pgstuple))
-		elog(ERROR, "cache lookup failed for sequence %u", seq_relid);
-	pgsform = (Form_pg_sequence) GETSTRUCT(pgstuple);
-	startv = pgsform->seqstart;
-	ReleaseSysCache(pgstuple);
+	startv = get_seqence_start_value(seq_relid);
 
 	/*
 	 * Copy the existing sequence tuple.
@@ -451,6 +450,15 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 
 	init_sequence(relid, &elm, &seqrel);
 
+	if (RELATION_IS_GLOBAL_TEMP(seqrel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(seqrel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary sequence %s when other backend attached it.",
+						RelationGetRelationName(seqrel))));
+	}
+
 	rel = table_open(SequenceRelationId, RowExclusiveLock);
 	seqtuple = SearchSysCacheCopy1(SEQRELID,
 								   ObjectIdGetDatum(relid));
@@ -611,7 +619,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +944,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1153,6 +1161,14 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	/* Initializes the storage for sequence which the global temporary table belongs. */
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1927,3 +1943,58 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+/*
+ * Get the startValue of the sequence from syscache.
+ */
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+/*
+ * Initialize sequence which global temporary table belongs.
+ */
+void
+gtt_init_seq(Relation rel)
+{
+	Datum		value[SEQ_COL_LASTCOL];
+	bool		null[SEQ_COL_LASTCOL];
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL - 1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+	null[SEQ_COL_LASTVAL - 1] = false;
+
+	value[SEQ_COL_LOG - 1] = Int64GetDatum((int64)0);
+	null[SEQ_COL_LOG - 1] = false;
+
+	value[SEQ_COL_CALLED - 1] = BoolGetDatum(false);
+	null[SEQ_COL_CALLED - 1] = false;
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 857cc5ce6e2..ef14597b900 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -118,6 +119,7 @@ typedef struct OnCommitItem
 	 */
 	SubTransactionId creating_subid;
 	SubTransactionId deleting_subid;
+	bool			 is_global_temp;
 } OnCommitItem;
 
 static List *on_commits = NIL;
@@ -602,7 +604,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
 static char GetAttributeCompression(Oid atttypid, char *compression);
-
+static OnCommitAction gtt_oncommit_option(List *options);
 
 /* ----------------------------------------------------------------
  *		DefineRelation
@@ -647,6 +649,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -658,7 +661,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * Check consistency of arguments
 	 */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !RelpersistenceTsTemp(stmt->relation->relpersistence))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -688,7 +691,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	if (RelpersistenceTsTemp(stmt->relation->relpersistence)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -789,6 +792,50 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* For global temporary table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		if (!(relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE))
+			elog(ERROR, "Only support global temporary regular table.");
+
+		/* Check parent table */
+		if (inheritOids)
+			elog(ERROR, "Not support global temporary partition table or inherit table.");
+
+		/* Check oncommit clause and save to reloptions */
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temporary table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1415,7 +1462,7 @@ RemoveRelations(DropStmt *drop)
 		 * relation persistence cannot be known without its OID.
 		 */
 		if (drop->concurrent &&
-			get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(get_rel_persistence(relOid)))
 		{
 			Assert(list_length(drop->objects) == 1 &&
 				   drop->removeType == OBJECT_INDEX);
@@ -1624,9 +1671,9 @@ ExecuteTruncate(TruncateStmt *stmt)
 		Relation	rel;
 		bool		recurse = rv->inh;
 		Oid			myrelid;
-		LOCKMODE	lockmode = AccessExclusiveLock;
+		LOCKMODE	lockmode;
 
-		myrelid = RangeVarGetRelidExtended(rv, lockmode,
+		myrelid = RangeVarGetRelidExtended(rv, AccessShareLock,
 										   0, RangeVarCallbackForTruncate,
 										   NULL);
 
@@ -1634,9 +1681,21 @@ ExecuteTruncate(TruncateStmt *stmt)
 		if (list_member_oid(relids, myrelid))
 			continue;
 
-		/* open the relation, we already hold a lock on it */
+		/* open the relation, we need hold a low-level lock first */
 		rel = table_open(myrelid, NoLock);
 
+		/*
+		 * Truncate global temp table only cleans up the data in current backend,
+		 * only low-level locks are required.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel))
+			lockmode = AccessShareLock;
+		else
+		{
+			lockmode = AccessExclusiveLock;
+			LockRelationOid(myrelid, lockmode);
+		}
+
 		/*
 		 * RangeVarGetRelidExtended() has done most checks with its callback,
 		 * but other checks with the now-opened Relation remain.
@@ -1886,6 +1945,7 @@ ExecuteTruncateGuts(List *explicit_rels,
 	foreach(cell, rels)
 	{
 		Relation	rel = (Relation) lfirst(cell);
+		LOCKMODE	lockmode;
 
 		/* Skip partitioned tables as there is nothing to do */
 		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
@@ -1936,6 +1996,19 @@ ExecuteTruncateGuts(List *explicit_rels,
 			continue;
 		}
 
+		/*
+		 * Skip the global temporary table that is not initialized for storage
+		 * in current backend.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel))
+		{
+			lockmode = AccessShareLock;
+			if (!gtt_storage_attached(RelationGetRelid(rel)))
+				continue;
+		}
+		else
+			lockmode = AccessExclusiveLock;
+
 		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
@@ -1954,6 +2027,7 @@ ExecuteTruncateGuts(List *explicit_rels,
 			Oid			heap_relid;
 			Oid			toast_relid;
 			ReindexParams reindex_params = {0};
+			int			reindex_flags = 0;
 
 			/*
 			 * This effectively deletes all rows in the table, and may be done
@@ -1981,17 +2055,21 @@ ExecuteTruncateGuts(List *explicit_rels,
 			if (OidIsValid(toast_relid))
 			{
 				Relation	toastrel = relation_open(toast_relid,
-													 AccessExclusiveLock);
+													 lockmode);
 
 				RelationSetNewRelfilenode(toastrel,
 										  toastrel->rd_rel->relpersistence);
 				table_close(toastrel, NoLock);
 			}
 
+			reindex_flags = REINDEX_REL_PROCESS_TOAST;
+			if (RELATION_IS_GLOBAL_TEMP(rel))
+				reindex_flags |= REINDEX_REL_PROCESS_GLOBAL_TEMP;
+
 			/*
 			 * Reconstruct the indexes to match, and we're done.
 			 */
-			reindex_relation(heap_relid, REINDEX_REL_PROCESS_TOAST,
+			reindex_relation(heap_relid, reindex_flags,
 							 &reindex_params);
 		}
 
@@ -4032,6 +4110,16 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temporary table only current backend use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -5363,6 +5451,24 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 
 			rel = table_open(tab->relid, NoLock);
 			find_composite_type_dependencies(rel->rd_rel->reltype, rel, NULL);
+
+			if (RELATION_IS_GLOBAL_TEMP(rel) && tab->rewrite > 0)
+			{
+				if (tab->chgPersistence)
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot change global temporary table persistence setting")));
+
+				if(gtt_storage_attached(tab->relid))
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("Only support alter global temporary table in an empty context."),
+						 errhint("Please create a new connection and execute ALTER TABLE on the new connection.")));
+
+				/* There is no need to override the whole temp table */
+				tab->rewrite = 0;
+			}
+
 			table_close(rel, NoLock);
 		}
 
@@ -5414,6 +5520,8 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot rewrite temporary tables of other sessions")));
 
+			Assert(!RELATION_IS_GLOBAL_TEMP(OldHeap));
+
 			/*
 			 * Select destination tablespace (same as original unless user
 			 * requested a change)
@@ -9045,6 +9153,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -13082,7 +13196,9 @@ TryReuseIndex(Oid oldId, IndexStmt *stmt)
 		Relation	irel = index_open(oldId, NoLock);
 
 		/* If it's a partitioned index, there is no storage to share. */
-		if (irel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
+		/* multiple global temp table are not allow use same relfilenode */
+		if (irel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX &&
+			!RELATION_IS_GLOBAL_TEMP(irel))
 		{
 			stmt->oldNode = irel->rd_node.relNode;
 			stmt->oldCreateSubid = irel->rd_createSubid;
@@ -13744,6 +13860,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -13943,6 +14062,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temporary table");
+
 	/* Check first if relation can be moved to new tablespace */
 	if (!CheckRelationTableSpaceMove(rel, newTableSpace))
 	{
@@ -14246,7 +14368,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(RelationGetSmgr(rel), dstrel, MAIN_FORKNUM,
@@ -15844,6 +15966,7 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
 	switch (rel->rd_rel->relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -16285,7 +16408,7 @@ AlterSeqNamespaces(Relation classRel, Relation rel,
  * Register a newly-created relation's ON COMMIT action.
  */
 void
-register_on_commit_action(Oid relid, OnCommitAction action)
+register_on_commit_action(Oid relid, OnCommitAction action, bool is_gloal_temp)
 {
 	OnCommitItem *oc;
 	MemoryContext oldcxt;
@@ -16304,6 +16427,7 @@ register_on_commit_action(Oid relid, OnCommitAction action)
 	oc->oncommit = action;
 	oc->creating_subid = GetCurrentSubTransactionId();
 	oc->deleting_subid = InvalidSubTransactionId;
+	oc->is_global_temp = is_gloal_temp;
 
 	/*
 	 * We use lcons() here so that ON COMMIT actions are processed in reverse
@@ -16349,6 +16473,7 @@ PreCommit_on_commit_actions(void)
 	ListCell   *l;
 	List	   *oids_to_truncate = NIL;
 	List	   *oids_to_drop = NIL;
+	List	   *oids_to_truncate_gtt = NIL;
 
 	foreach(l, on_commits)
 	{
@@ -16372,7 +16497,12 @@ PreCommit_on_commit_actions(void)
 				 * tables, as they must still be empty.
 				 */
 				if ((MyXactFlags & XACT_FLAGS_ACCESSEDTEMPNAMESPACE))
-					oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid);
+				{
+					if (oc->is_global_temp)
+						oids_to_truncate_gtt = lappend_oid(oids_to_truncate_gtt, oc->relid);
+					else
+						oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid);
+				}
 				break;
 			case ONCOMMIT_DROP:
 				oids_to_drop = lappend_oid(oids_to_drop, oc->relid);
@@ -16389,7 +16519,10 @@ PreCommit_on_commit_actions(void)
 	 * exists at truncation time.
 	 */
 	if (oids_to_truncate != NIL)
-		heap_truncate(oids_to_truncate);
+		heap_truncate(oids_to_truncate, false);
+
+	if (oids_to_truncate_gtt != NIL)
+		heap_truncate(oids_to_truncate_gtt, true);
 
 	if (oids_to_drop != NIL)
 	{
@@ -17388,6 +17521,13 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot attach temporary relation of another session as partition")));
 
+	/* If the parent is permanent, so must be all of its partitions. */
+	if (attachrel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach a global temporary relation as partition of permanent relation \"%s\"",
+						RelationGetRelationName(rel))));
+
 	/* Check if there are any columns in attachrel that aren't in the parent */
 	tupleDesc = RelationGetDescr(attachrel);
 	natts = tupleDesc->natts;
@@ -18858,3 +18998,40 @@ GetAttributeCompression(Oid atttypid, char *compression)
 
 	return cmethod;
 }
+
+/*
+ * Parse the on commit clause for the temporary table
+ */
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			bool	res = false;
+			char	*sval = defGetString(def);
+
+			/* It has to be a Boolean value */
+			if (!parse_bool(sval, &res))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
+
+			if (res)
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 5c4bc15b441..3a861c47946 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"
@@ -1315,6 +1316,27 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = RELATION_IS_GLOBAL_TEMP(relation);
+
+	 /* For global temporary table */
+	if (is_gtt)
+	{
+		/* Store relation statistics and transaction information to the localhash */
+		up_gtt_relstats(RelationGetRelid(relation),
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+
+		/* Update relation statistics to local relcache */
+		relation->rd_rel->relpages = (int32) num_pages;
+		relation->rd_rel->reltuples = (float4) num_tuples;
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+		if (TransactionIdIsNormal(frozenxid))
+			relation->rd_rel->relfrozenxid = frozenxid;
+
+		if (MultiXactIdIsValid(minmulti))
+			relation->rd_rel->relminmxid = minmulti;
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1328,17 +1350,23 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (!is_gtt &&
+		pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (!is_gtt &&
+		pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (!is_gtt &&
+		pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1351,7 +1379,15 @@ vac_update_relstats(Relation relation,
 		/*
 		 * If we didn't find any indexes, reset relhasindex.
 		 */
-		if (pgcform->relhasindex && !hasindex)
+		if (is_gtt &&
+			RelationGetIndexList(relation) != NIL)
+		{
+			/*
+			 * Global temporary tables may contain indexes that are not valid locally.
+			 * The catalog should not be updated based on local invalid index.
+			 */
+		}
+		else if (pgcform->relhasindex && !hasindex)
 		{
 			pgcform->relhasindex = false;
 			dirty = true;
@@ -1383,7 +1419,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNextTransactionId(),
@@ -1394,7 +1431,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1502,6 +1540,13 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/*
+		 * The relfrozenxid for a global temporary talble is stored in localhash,
+		 * not pg_class, See list_all_session_gtt_frozenxids()
+		 */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1559,6 +1604,43 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/* If enable global temporary table */
+	if (max_active_gtt > 0)
+	{
+		TransactionId	safe_age;
+		/*  */
+		TransactionId	oldest_gtt_frozenxid =
+			list_all_backend_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid))
+		{
+			safe_age = oldest_gtt_frozenxid + vacuum_gtt_defer_check_age;
+			if (safe_age < FirstNormalTransactionId)
+				safe_age += FirstNormalTransactionId;
+
+			/*
+			 * We tolerate that the minimum age of gtt is less than
+			 * the minimum age of conventional tables, otherwise it will
+			 * throw warning message.
+			 */
+			if (TransactionIdIsNormal(safe_age) &&
+				TransactionIdPrecedes(safe_age, newFrozenXid))
+			{
+				ereport(WARNING,
+					(errmsg("global temp table oldest relfrozenxid %u is the oldest in the entire db",
+							oldest_gtt_frozenxid),
+					 errdetail("The oldest relfrozenxid in pg_class is %u", newFrozenXid),
+					 errhint("If they differ greatly, please consider cleaning up the data in global temp table.")));
+			}
+
+			/*
+			 * We need to ensure that the clog required by gtt is not cleand.
+			 */
+			if (TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+				newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1910,6 +1992,19 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	/*
+	 * Skip those global temporary table that are not initialized in
+	 * current backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel) &&
+		!gtt_storage_attached(RelationGetRelid(rel)))
+	{
+		relation_close(rel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 4df05a0b33d..4c181e2e14e 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -527,6 +527,12 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	/* Global temporary table are not sensible. */
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b3ce4bae530..611e3f18a70 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -784,6 +784,10 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		/* This is one kind of temp table */
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 5c723bc54e1..191e0f6fd21 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d328856ae5b..4e2bbb224cc 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -38,6 +38,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -633,6 +634,9 @@ ExecInsert(ModifyTableState *mtstate,
 		resultRelInfo->ri_IndexRelationDescs == NULL)
 		ExecOpenIndices(resultRelInfo, onconflict != ONCONFLICT_NONE);
 
+	/* Init storage for global temporary table in current backend */
+	init_gtt_storage(CMD_INSERT, resultRelInfo);
+
 	/*
 	 * BEFORE ROW INSERT Triggers.
 	 *
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 296dd75c1b6..d971aea2546 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -48,7 +48,7 @@
 #include "partitioning/partprune.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/lsyscache.h"
-
+#include "utils/rel.h"
 
 /* results of subquery_is_pushdown_safe */
 typedef struct pushdown_safety_info
@@ -619,7 +619,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			if (RelpersistenceTsTemp(get_rel_persistence(rte->relid)))
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index bd01ec0526f..ff4e81ca2cf 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6071,7 +6071,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
-	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+	if (RELATION_IS_TEMP(heap) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index c5194fdbbf2..38d7c658541 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -30,6 +30,7 @@
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temporary table in current backend */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 146ee8dd1ea..2d4e9393f00 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2907,6 +2907,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 d0eb80e69cb..8dab345aad9 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3417,17 +3417,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; }
@@ -11755,19 +11749,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 c5c3f26ecf1..2a2b2789077 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -82,6 +82,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);
 
 
 /*
@@ -3665,3 +3666,53 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * Like function isQueryUsingTempRelation_walker
+ * return true if any relation underlying
+ * the query is a global temporary table.
+ */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* Check if the query uses global temporary table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 313d7b6ff02..38e0e162e82 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -447,6 +447,13 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	/*
+	 * If a sequence is bound to a global temporary table, then the sequence
+	 * must been "global temporary"
+	 */
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
@@ -3326,6 +3333,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		cxt.isforeign = false;
 	}
 	cxt.relation = stmt->relation;
+	/* Sets the table persistence to the context */
+	cxt.relation->relpersistence = RelationGetRelPersistence(rel);
 	cxt.rel = rel;
 	cxt.inhRelations = NIL;
 	cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 96332320a73..75ed4d0ae9e 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2115,6 +2115,14 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/*
+			 * Aotuvacuum cannot vacuum the private data stored in each backend
+			 * that belongs to global temporary table, so skip them.
+			 */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2181,7 +2189,7 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		if (RelpersistenceTsTemp(classForm->relpersistence))
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 08ebabfe96a..c346c59c7f4 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlogutils.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -2934,6 +2935,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * Returns 0 if this global temporary table is not initialized in current
+	 * backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 9fa3e0631e6..cc3eb928bc6 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -23,6 +23,7 @@
 #include "access/syncscan.h"
 #include "access/twophase.h"
 #include "commands/async.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
@@ -143,6 +144,7 @@ CalculateShmemSize(int *num_semaphores)
 	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
@@ -246,6 +248,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* For global temporary table shared hashtable */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 892f0f67998..bdc6b478ca8 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -65,6 +65,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -5148,3 +5149,82 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temporary table.
+ */
+int
+list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct		*arrayP = NULL;
+	TransactionId		result = InvalidTransactionId;
+	int			index = 0;
+	int			i = 0;
+	uint8		flags = 0;
+
+	if (n)
+		*n = 0;
+
+	/* return 0 if feature is disabled */
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+	}
+
+	/* Disable in standby node */
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	arrayP = procArray;
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		uint8           statusFlags = ProcGlobal->statusFlags[index];
+
+		if (statusFlags & flags)
+			continue;
+
+		/* Fetch all backend that is belonging to MyDatabaseId */
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->backend_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->backend_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->backend_gtt_frozenxid, result))
+				result = proc->backend_gtt_frozenxid;
+
+			/* save backend pid and backend level oldest relfrozenxid */
+			if (pids)
+				pids[i] = proc->pid;
+
+			if (xids)
+				xids[i] = proc->backend_gtt_frozenxid;
+
+			i++;
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (n)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 862097352bb..4edd3b31f7a 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -176,7 +176,9 @@ static const char *const BuiltinTrancheNames[] = {
 	/* LWTRANCHE_PARALLEL_APPEND: */
 	"ParallelAppend",
 	/* LWTRANCHE_PER_XACT_PREDICATE_LIST: */
-	"PerXactPredicateList"
+	"PerXactPredicateList",
+	/* LWTRANCHE_GTT_CTL */
+	"GlobalTempTableControl"
 };
 
 StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index b7d9da0aa9f..8051f2053f9 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -393,6 +393,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;	/* init backend level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
@@ -578,6 +579,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;	/* init backend level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index d5a7fb13f3c..8225cf6219f 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -982,6 +982,13 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/*
+			 * For global temporary table ,each backend has its own storage,
+			 * also only sees its own storage. Use Backendid to identify them.
+			 */
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 10895fb2876..66255eb7604 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -108,6 +108,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -5115,12 +5116,26 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								/* For global temporary table, get statistic data from localhash */
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -5368,15 +5383,28 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6820,6 +6848,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;
@@ -6837,6 +6866,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6848,6 +6885,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;
@@ -6863,6 +6902,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7781,6 +7828,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))
@@ -7793,6 +7842,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7805,6 +7863,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.
@@ -7824,6 +7884,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 4ebaa552a27..78c33d2ac87 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -3113,6 +3114,19 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		/* For global temporary table, get statistic data from localhash */
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 9fa9e671a11..65b40e58943 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -65,6 +65,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1152,6 +1153,36 @@ retry:
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+				TransactionId	relfrozenxid = InvalidTransactionId;
+				MultiXactId 	relminmxid = InvalidMultiXactId;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+
+				/* For global temporary table, get relstat data from localhash */
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								&relfrozenxid,
+								&relminmxid);
+
+				/* And put them to local relcache */
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+				if (TransactionIdIsNormal(relfrozenxid))
+					relation->rd_rel->relfrozenxid = relfrozenxid;
+
+				if (MultiXactIdIsValid(relminmxid))
+					relation->rd_rel->relminmxid = relminmxid;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1209,6 +1240,8 @@ retry:
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			/* The state of the global temporary table's index may need to be set */
+			gtt_fix_index_backend_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -1351,7 +1384,22 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+			/*
+			 * For global temporary table, get the latest relfilenode
+			 * from localhash and put it in relcache.
+			 */
+			if (OidIsValid(newrelnode) &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2302,6 +2350,9 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		/* The state of the global temporary table's index may need to be set */
+		gtt_fix_index_backend_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3582,6 +3633,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3691,28 +3746,39 @@ void
 RelationSetNewRelfilenode(Relation relation, char persistence)
 {
 	Oid			newrelfilenode;
-	Relation	pg_class;
-	HeapTuple	tuple;
+	Relation	pg_class = NULL;
+	HeapTuple	tuple = NULL;
 	Form_pg_class classform;
 	MultiXactId minmulti = InvalidMultiXactId;
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
 
+	/*
+	 * For global temporary table, storage information for the table is
+	 * maintained locally, not in catalog.
+	 */
+	bool		update_catalog = !RELATION_IS_GLOBAL_TEMP(relation);
+
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
 
-	/*
-	 * Get a writable copy of the pg_class tuple for the given relation.
-	 */
-	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+	memset(&classform, 0, sizeof(classform));
 
-	tuple = SearchSysCacheCopy1(RELOID,
-								ObjectIdGetDatum(RelationGetRelid(relation)));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "could not find tuple for relation %u",
-			 RelationGetRelid(relation));
-	classform = (Form_pg_class) GETSTRUCT(tuple);
+	if (update_catalog)
+	{
+		/*
+		 * Get a writable copy of the pg_class tuple for the given relation.
+		 */
+		pg_class = table_open(RelationRelationId, RowExclusiveLock);
+
+		tuple = SearchSysCacheCopy1(RELOID,
+									ObjectIdGetDatum(RelationGetRelid(relation)));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u",
+				 RelationGetRelid(relation));
+		classform = (Form_pg_class) GETSTRUCT(tuple);
+	}
 
 	/*
 	 * Schedule unlinking of the old storage at transaction commit.
@@ -3738,7 +3804,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;
@@ -3758,6 +3824,18 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 			break;
 	}
 
+	/* For global temporary table */
+	if (!update_catalog)
+	{
+		Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+		Assert(RELATION_IS_GLOBAL_TEMP(relation));
+		Assert(!RelationIsMapped(relation));
+
+		/* Make cache invalid and set new relnode to local cache. */
+		CacheInvalidateRelcache(relation);
+		relation->rd_node.relNode = relnode;
+	}
 	/*
 	 * If we're dealing with a mapped index, pg_class.relfilenode doesn't
 	 * change; instead we have to send the update to the relation mapper.
@@ -3767,7 +3845,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	 * possibly-inaccurate values of relpages etc, but those will be fixed up
 	 * later.
 	 */
-	if (RelationIsMapped(relation))
+	else if (RelationIsMapped(relation))
 	{
 		/* This case is only supported for indexes */
 		Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3813,9 +3891,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 		CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 	}
 
-	heap_freetuple(tuple);
+	if (update_catalog)
+	{
+		heap_freetuple(tuple);
 
-	table_close(pg_class, RowExclusiveLock);
+		table_close(pg_class, RowExclusiveLock);
+	}
 
 	/*
 	 * Make the pg_class row change or relation map change visible.  This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index e91d5a3cfda..b8ec6b3ca46 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -44,6 +44,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "commands/prepare.h"
 #include "commands/trigger.h"
@@ -153,6 +154,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temporary table feature.
+ * table schema are still saved in catalog.
+ *
+ * num > 0 means allows the database to manage multiple active tables at the same time.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2128,6 +2141,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 "
@@ -2698,6 +2720,16 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"vacuum_gtt_defer_check_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("The defer check age of GTT, used to check expired data after vacuum."),
+			NULL
+		},
+		&vacuum_gtt_defer_check_age,
+		10000, 0, 1000000,
+		NULL, NULL, NULL
+	},
+
 	/*
 	 * See also CheckRequiredParameterValues() if this parameter changes
 	 */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index b9635a95b6f..c08ef663aca 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2504,6 +2504,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
 		dopt->no_unlogged_table_data)
 		return;
 
+	/* Don't dump data in global temporary table/sequence */
+	if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		return;
+
 	/* Check that the data is not explicitly excluded */
 	if (simple_oid_list_member(&tabledata_exclude_oids,
 							   tbinfo->dobj.catId.oid))
@@ -15815,6 +15819,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
 		char	   *foreign = "";
+		char	   *table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15868,9 +15873,15 @@ dumpTableSchema(Archive *fout, const 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);
 
@@ -16234,6 +16245,15 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 			}
 		}
 
+		/*
+		 * Transaction information for the global temporary table is not stored
+		 * in the pg_class.
+		 */
+		if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			Assert(tbinfo->frozenxid == 0);
+			Assert(tbinfo->minmxid == 0);
+		}
 		/*
 		 * In binary_upgrade mode, arrange to restore the old relfrozenxid and
 		 * relminmxid of all vacuumable relations.  (While vacuum.c processes
@@ -16241,7 +16261,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		 * of other relations; so this "if" lacks RELKIND_TOASTVALUE, and the
 		 * child toast table is handled below.)
 		 */
-		if (dopt->binary_upgrade &&
+		else if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
 			 tbinfo->relkind == RELKIND_MATVIEW))
 		{
@@ -17244,6 +17264,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	PQExpBuffer query = createPQExpBuffer();
 	PQExpBuffer delqry = createPQExpBuffer();
 	char	   *qseqname;
+	bool	   global_temp_seq = false;
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
@@ -17253,9 +17274,12 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
-						  "FROM pg_catalog.pg_sequence "
-						  "WHERE seqrelid = '%u'::oid",
+						  "seqcache, seqcycle, "
+						  "c.relpersistence "
+						  "FROM pg_catalog.pg_sequence s, "
+						  "pg_catalog.pg_class c "
+						  "WHERE seqrelid = '%u'::oid "
+						  "and s.seqrelid = c.oid",
 						  tbinfo->dobj.catId.oid);
 	}
 	else if (fout->remoteVersion >= 80400)
@@ -17300,6 +17324,9 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
 
+	if (fout->remoteVersion >= 140000)
+		global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
 	if (strcmp(seqtype, "smallint") == 0)
@@ -17377,9 +17404,13 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	}
 	else
 	{
-		appendPQExpBuffer(query,
-						  "CREATE SEQUENCE %s\n",
-						  fmtQualifiedDumpable(tbinfo));
+		appendPQExpBuffer(query, "CREATE ");
+
+		if (global_temp_seq)
+			appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+		appendPQExpBuffer(query, "SEQUENCE %s\n",
+								  fmtQualifiedDumpable(tbinfo));
 
 		if (strcmp(seqtype, "bigint") != 0)
 			appendPQExpBuffer(query, "    AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index ad5f3919956..f3819860096 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -88,7 +88,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -178,7 +178,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 5d9a26cf822..2de11d5d707 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -447,8 +449,17 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
 			 "         ON c.relnamespace = n.oid "
 			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
+			 CppAsString2(RELKIND_MATVIEW) ") AND ");
+
+	if (skip_gtt)
+	{
+		/* exclude global temp tables */
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+			"    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND ");
+	}
+
 	/* exclude possible orphaned temp tables */
+	snprintf(query + strlen(query), sizeof(query) - strlen(query),
 			 "    ((n.nspname !~ '^pg_temp_' AND "
 			 "      n.nspname !~ '^pg_toast_temp_' AND "
 			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 3628bd74a7b..bbb9b5ea13d 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -407,7 +407,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -645,7 +645,10 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+			/* exclude global temp tables */
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -656,7 +659,10 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+		/* exclude global temp tables */
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index ca0795f68ff..018a2effd4b 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -389,7 +389,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 006661412ea..ffba81acb86 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -4089,7 +4089,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 4f724e4428b..ab7eb31cd42 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1059,6 +1059,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2603,6 +2605,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2830,6 +2835,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE", "SEQUENCE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 6ce480b49c1..dda3f3c5a60 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -85,7 +85,7 @@ extern Oid	heap_create_with_catalog(const char *relname,
 
 extern void heap_drop_with_catalog(Oid relid);
 
-extern void heap_truncate(List *relids);
+extern void heap_truncate(List *relids, bool is_global_temp);
 
 extern void heap_truncate_one_rel(Relation rel);
 
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 008f723e104..875b1003899 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -157,6 +157,7 @@ extern void reindex_index(Oid indexId, bool skip_constraint_checks,
 #define REINDEX_REL_CHECK_CONSTRAINTS		0x04
 #define REINDEX_REL_FORCE_INDEXES_UNLOGGED	0x08
 #define REINDEX_REL_FORCE_INDEXES_PERMANENT 0x10
+#define REINDEX_REL_PROCESS_GLOBAL_TEMP		0x20
 
 extern bool reindex_relation(Oid relid, int flags, ReindexParams *params);
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index fef9945ed8f..9176b7dcc07 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -172,6 +172,7 @@ DECLARE_INDEX(pg_class_tblspc_relfilenode_index, 3455, ClassTblspcRelfilenodeInd
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index d068d6532ec..fd5089388f2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5735,6 +5735,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '9874',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '9875',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '9876',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '9877',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 0ab32b44e91..92e9f8ba485 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,7 +22,7 @@
 /* GUC variables */
 extern int	wal_skip_threshold;
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationPreTruncate(Relation rel);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 00000000000..8a3d9558712
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,45 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern int		vacuum_gtt_defer_check_age;
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Oid relid,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void force_enable_gtt_index(Relation index);
+extern void gtt_fix_index_backend_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index 40544dd4c70..7b66d808fc5 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 336549cc5f0..3e8167134b7 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -86,7 +86,7 @@ extern void find_composite_type_dependencies(Oid typeOid,
 
 extern void check_of_type(HeapTuple typetuple);
 
-extern void register_on_commit_action(Oid relid, OnCommitAction action);
+extern void register_on_commit_action(Oid relid, OnCommitAction action, bool is_gloal_temp);
 extern void remove_on_commit_action(Oid relid);
 
 extern void PreCommit_on_commit_actions(void);
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 8336c2c5a29..bddcfe7256d 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index c86ccdaf608..6b395551c1d 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -399,6 +399,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index a8f052e4845..4b4ed1a13aa 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -189,6 +189,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_SHARED_TIDBITMAP,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_PER_XACT_PREDICATE_LIST,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index be67d8a8616..e2f8bb5162d 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -157,6 +157,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId backend_gtt_frozenxid;	/* backend level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index b01fa52139a..8efffa55ac5 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -94,4 +94,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index aa18d304ac0..524c9d7de3f 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -288,6 +288,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 b4faa1c1238..a74558a8383 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	SMgrRelation rd_smgr;		/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -326,6 +326,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -608,11 +609,13 @@ RelationGetSmgr(Relation rel)
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -620,6 +623,7 @@ RelationGetSmgr(Relation rel)
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -632,6 +636,30 @@ RelationGetSmgr(Relation rel)
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP_ON_CURRENT_SESSION
+ *		Test a rel is either local temp relation of this session
+ *		or global temp relation.
+ */
+#define RELATION_IS_TEMP_ON_CURRENT_SESSION(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is local temp relation or global temporary relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RelpersistenceTsTemp
+ *		Test a relpersistence is local temp relation or global temporary relation.
+ */
+#define RelpersistenceTsTemp(relpersistence) \
+	(relpersistence == RELPERSISTENCE_TEMP || \
+	 relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -677,6 +705,19 @@ RelationGetSmgr(Relation rel)
 	 (relation)->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&	\
 	 !IsCatalogRelation(relation))
 
+/* For global temporary table */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/* Get on commit clause value only for global temporary table */
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+/* Get relpersistence for relation */
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
-- 
2.30.1 (Apple Git-130)

0004-gtt-v61-regress.patchapplication/octet-stream; name=0004-gtt-v61-regress.patchDownload
diff --git a/src/test/isolation/expected/gtt-sequence.out b/src/test/isolation/expected/gtt-sequence.out
new file mode 100644
index 00000000000..31db2ebd423
--- /dev/null
+++ b/src/test/isolation/expected/gtt-sequence.out
@@ -0,0 +1,48 @@
+unused step name: s1_seq_restart
+Parsed test spec with 2 sessions
+
+starting permutation: s1_seq_next s2_seq_next s1_seq_next
+step s1_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      1
+(1 row)
+
+step s2_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      1
+(1 row)
+
+step s1_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      2
+(1 row)
+
+
+starting permutation: s1_select s2_select s1_insert s2_insert s1_select s2_select
+step s1_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+(0 rows)
+
+step s2_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+(0 rows)
+
+step s1_insert: insert into gtt_with_seq values(1);
+step s2_insert: insert into gtt_with_seq values(10);
+step s1_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+ 1| 3
+(1 row)
+
+step s2_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+10| 1
+(1 row)
+
diff --git a/src/test/isolation/expected/gtt-table.out b/src/test/isolation/expected/gtt-table.out
new file mode 100644
index 00000000000..5825773aa12
--- /dev/null
+++ b/src/test/isolation/expected/gtt-table.out
@@ -0,0 +1,675 @@
+Parsed test spec with 3 sessions
+
+starting permutation: s1_update_d
+step s1_update_d: update gtt_on_commit_delete_row set b = 'update'
+
+starting permutation: s1_select_d s1_insert_d s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_begin s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+1|test1
+(1 row)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_begin s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+1|test1
+(1 row)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_select_d s1_truncate_d s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_begin s1_insert_d s1_select_d s1_truncate_d s1_select_d s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_begin s1_insert_d s1_select_d s1_truncate_d s1_select_d s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_insert_d s1_select_d s1_begin s1_insert_d s1_select_d s1_save_1 s1_truncate_d s1_insert_d s1_select_d s1_save_2 s1_truncate_d s1_insert_d s1_select_d s1_save_3 s1_rollback_to_save_2 s1_select_d s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_save_1: SAVEPOINT save1
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_save_2: SAVEPOINT save2
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+4|test1
+(1 row)
+
+step s1_save_3: SAVEPOINT save3
+step s1_rollback_to_save_2: rollback to savepoint save2
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+5|test1
+(2 rows)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_insert_d s1_select_d s1_begin s1_insert_d s1_select_d s1_save_1 s1_truncate_d s1_insert_d s1_select_d s1_save_2 s1_truncate_d s1_insert_d s1_select_d s1_save_3 s1_rollback_to_save_2 s1_select_d s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_save_1: SAVEPOINT save1
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_save_2: SAVEPOINT save2
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+4|test1
+(1 row)
+
+step s1_save_3: SAVEPOINT save3
+step s1_rollback_to_save_2: rollback to savepoint save2
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+5|test1
+(2 rows)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_update_p
+step s2_update_p: update gtt_on_commit_preserve_row set b = 'update'
+
+starting permutation: s2_select_p s2_insert_p s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_begin s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_begin s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_select_p s2_insert_p s2_select_p s2_truncate_p s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_select_p s2_insert_p s2_begin s2_insert_p s2_select_p s2_truncate_p s2_select_p s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_insert_p s2_begin s2_insert_p s2_select_p s2_truncate_p s2_select_p s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_insert_p s2_select_p s2_begin s2_insert_p s2_select_p s2_save_1 s2_truncate_p s2_insert_p s2_select_p s2_save_2 s2_truncate_p s2_insert_p s2_select_p s2_save_3 s2_rollback_to_save_2 s2_select_p s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_save_1: SAVEPOINT save1
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_save_2: SAVEPOINT save2
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+4|test10
+(1 row)
+
+step s2_save_3: SAVEPOINT save3
+step s2_rollback_to_save_2: rollback to savepoint save2
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+
+starting permutation: s2_insert_p s2_select_p s2_begin s2_insert_p s2_select_p s2_save_1 s2_truncate_p s2_insert_p s2_select_p s2_save_2 s2_truncate_p s2_insert_p s2_select_p s2_save_3 s2_rollback_to_save_2 s2_select_p s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_save_1: SAVEPOINT save1
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_save_2: SAVEPOINT save2
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+4|test10
+(1 row)
+
+step s2_save_3: SAVEPOINT save3
+step s2_rollback_to_save_2: rollback to savepoint save2
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_insert_p s2_insert_p s1_select_p s2_select_p
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_truncate_p s2_truncate_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_begin s1_insert_d s2_insert_d s1_truncate_d s2_insert_d s1_commit
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s2_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s2_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_commit: commit
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_reindex_p s2_reindex_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_reindex_p: reindex table gtt_on_commit_preserve_row
+step s2_reindex_p: reindex table gtt_on_commit_preserve_row
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_reindex_i_p s2_reindex_i_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_reindex_i_p: reindex index gtt_on_commit_preserve_row_pkey
+step s2_reindex_i_p: reindex index gtt_on_commit_preserve_row_pkey
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_insert_c s3_create_c s3_insert_c s1_insert_c s1_analyze_c s2_analyze_c s3_analyze_c s1_select_c s2_select_c s3_select_c
+step s2_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s3_create_c: create unique index idx_temp_table_a on gtt_test_createindex(a)
+step s3_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s1_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s1_analyze_c: analyze gtt_test_createindex
+step s2_analyze_c: analyze gtt_test_createindex
+step s3_analyze_c: analyze gtt_test_createindex
+step s1_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                                               
+---------------------------------------------------------
+Index Scan using idx_temp_table_a on gtt_test_createindex
+  Index Cond: (a = 1)                                    
+(2 rows)
+
+step s2_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                      
+--------------------------------
+Seq Scan on gtt_test_createindex
+  Filter: (a = 1)               
+(2 rows)
+
+step s3_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                                               
+---------------------------------------------------------
+Index Scan using idx_temp_table_a on gtt_test_createindex
+  Index Cond: (a = 1)                                    
+(2 rows)
+
+
+starting permutation: s1_begin s2_begin s1_lock_p s2_lock_p s1_truncate_p s2_truncate_p s1_insert_p s2_insert_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_lock_p: LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE
+step s2_lock_p: LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE
+step s1_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index f4c01006fc1..746a17f824c 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -96,3 +96,5 @@ test: plpgsql-toast
 test: truncate-conflict
 test: serializable-parallel
 test: serializable-parallel-2
+test: gtt-sequence
+test: gtt-table
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 88594a3cb5d..ec643aadb5f 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -80,9 +80,30 @@ disconnect_atexit(void)
 {
 	int			i;
 
-	for (i = 0; i < nconns; i++)
+	for (i = 1; i < nconns; i++)
 		if (conns[i].conn)
 			PQfinish(conns[i].conn);
+
+	if (parseresult.destroy)
+	{
+		PGresult   *res;
+
+		res = PQexec(conns[0].conn, parseresult.destroy);
+		if (PQresultStatus(res) == PGRES_TUPLES_OK)
+		{
+			printResultSet(res);
+		}
+		else if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		{
+			fprintf(stderr, "destroy failed: %s",
+					PQerrorMessage(conns[0].conn));
+			/* don't exit on teardown failure */
+		}
+		PQclear(res);
+	}
+
+	if (conns[0].conn)
+		PQfinish(conns[0].conn);
 }
 
 int
@@ -214,6 +235,24 @@ main(int argc, char **argv)
 	PQclear(res);
 	termPQExpBuffer(&wait_query);
 
+	if (parseresult.initialize)
+	{
+		PGresult   *res;
+
+		res = PQexec(conns[0].conn, parseresult.initialize);
+		if (PQresultStatus(res) == PGRES_TUPLES_OK)
+		{
+			printResultSet(res);
+		}
+		else if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		{
+			fprintf(stderr, "initialize failed: %s",
+					PQerrorMessage(conns[0].conn));
+			/* don't exit on teardown failure */
+		}
+		PQclear(res);
+	}
+
 	/*
 	 * Run the permutations specified in the spec, or all if none were
 	 * explicitly specified.
diff --git a/src/test/isolation/isolationtester.h b/src/test/isolation/isolationtester.h
index 5f300219c20..b5a29893da9 100644
--- a/src/test/isolation/isolationtester.h
+++ b/src/test/isolation/isolationtester.h
@@ -81,6 +81,8 @@ typedef struct
 	int			nsessions;
 	Permutation **permutations;
 	int			npermutations;
+	char	   *initialize;
+	char	   *destroy;
 } TestSpec;
 
 extern TestSpec parseresult;
diff --git a/src/test/isolation/specparse.y b/src/test/isolation/specparse.y
index c25aa1a73fa..2784f758ed9 100644
--- a/src/test/isolation/specparse.y
+++ b/src/test/isolation/specparse.y
@@ -39,7 +39,7 @@ TestSpec		parseresult;			/* result of parsing is left here */
 }
 
 %type <ptr_list> setup_list
-%type <str>  opt_setup opt_teardown
+%type <str>  opt_setup opt_teardown opt_initialize opt_destroy
 %type <str> setup
 %type <ptr_list> step_list session_list permutation_list opt_permutation_list
 %type <ptr_list> permutation_step_list blocker_list
@@ -51,23 +51,27 @@ TestSpec		parseresult;			/* result of parsing is left here */
 
 %token <str> sqlblock identifier
 %token <integer> INTEGER
-%token NOTICES PERMUTATION SESSION SETUP STEP TEARDOWN TEST
+%token NOTICES PERMUTATION SESSION SETUP STEP TEARDOWN TEST INITIALIZE DESTROY
 
 %%
 
 TestSpec:
+			opt_initialize
 			setup_list
 			opt_teardown
+			opt_destroy
 			session_list
 			opt_permutation_list
 			{
-				parseresult.setupsqls = (char **) $1.elements;
-				parseresult.nsetupsqls = $1.nelements;
-				parseresult.teardownsql = $2;
-				parseresult.sessions = (Session **) $3.elements;
-				parseresult.nsessions = $3.nelements;
-				parseresult.permutations = (Permutation **) $4.elements;
-				parseresult.npermutations = $4.nelements;
+				parseresult.setupsqls = (char **) $2.elements;
+				parseresult.nsetupsqls = $2.nelements;
+				parseresult.teardownsql = $3;
+				parseresult.sessions = (Session **) $5.elements;
+				parseresult.nsessions = $5.nelements;
+				parseresult.permutations = (Permutation **) $6.elements;
+				parseresult.npermutations = $6.nelements;
+				parseresult.initialize = $1;
+				parseresult.destroy = $4;
 			}
 		;
 
@@ -100,6 +104,16 @@ opt_teardown:
 			| TEARDOWN sqlblock	{ $$ = $2; }
 		;
 
+opt_initialize:
+			/* EMPTY */			{ $$ = NULL; }
+			| INITIALIZE sqlblock	{ $$ = $2; }
+		;
+
+opt_destroy:
+			/* EMPTY */			{ $$ = NULL; }
+			| DESTROY sqlblock	{ $$ = $2; }
+		;
+
 session_list:
 			session_list session
 			{
diff --git a/src/test/isolation/specs/gtt-sequence.spec b/src/test/isolation/specs/gtt-sequence.spec
new file mode 100644
index 00000000000..88eece45e29
--- /dev/null
+++ b/src/test/isolation/specs/gtt-sequence.spec
@@ -0,0 +1,39 @@
+# Tests for global temporary relations
+
+initialize
+{
+  CREATE GLOBAL TEMPORARY TABLE if not exists gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+}
+
+destroy
+{
+  /* wait other backend exit */
+  select pg_sleep(1);
+
+  DROP TABLE gtt_with_seq;
+}
+
+# Session 1
+session "s1"
+step "s1_seq_next" { select nextval('gtt_with_seq_c2_seq'); }
+step "s1_seq_restart" { alter sequence gtt_with_seq_c2_seq RESTART; }
+step "s1_insert" { insert into gtt_with_seq values(1); }
+step "s1_select" { select * from gtt_with_seq order by c1,c2; }
+teardown
+{
+  TRUNCATE gtt_with_seq;
+}
+
+# Session 2
+session "s2"
+step "s2_seq_next" { select nextval('gtt_with_seq_c2_seq'); }
+step "s2_insert" { insert into gtt_with_seq values(10); }
+step "s2_select" { select * from gtt_with_seq order by c1,c2; }
+teardown
+{
+  TRUNCATE gtt_with_seq RESTART IDENTITY;
+}
+
+permutation "s1_seq_next" "s2_seq_next" "s1_seq_next"
+permutation "s1_select" "s2_select" "s1_insert" "s2_insert" "s1_select" "s2_select"
+
diff --git a/src/test/isolation/specs/gtt-table.spec b/src/test/isolation/specs/gtt-table.spec
new file mode 100644
index 00000000000..e0396b21ef0
--- /dev/null
+++ b/src/test/isolation/specs/gtt-table.spec
@@ -0,0 +1,135 @@
+# Tests for global temporary relations
+
+initialize
+{
+  create global temp table gtt_on_commit_delete_row(a bigserial primary key, b text) on commit delete rows;
+  create global temp table gtt_on_commit_preserve_row(a bigserial primary key, b text) on commit preserve rows;
+  create global temp table gtt_test_createindex(a int, b char(1000)) on commit preserve rows;
+}
+
+destroy
+{
+  /* wait other backend exit */
+  select pg_sleep(1);
+
+  DROP TABLE gtt_on_commit_delete_row;
+  DROP TABLE gtt_on_commit_preserve_row;
+  DROP TABLE gtt_test_createindex;
+}
+
+# Session 1
+session "s1"
+step "s1_begin" {begin}
+step "s1_commit" {commit}
+step "s1_rollback" {rollback}
+step "s1_insert_d" {insert into gtt_on_commit_delete_row (b) values('test1')}
+step "s1_select_d" {select a,b from gtt_on_commit_delete_row order by a,b}
+step "s1_insert_p" {insert into gtt_on_commit_preserve_row (b) values('test20')}
+step "s1_select_p" {select a,b from gtt_on_commit_preserve_row order by a,b}
+step "s1_truncate_d" {truncate gtt_on_commit_delete_row}
+step "s1_truncate_p" {truncate gtt_on_commit_preserve_row}
+step "s1_lock_p" {LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE}
+step "s1_update_d" {update gtt_on_commit_delete_row set b = 'update'}
+step "s1_save_1" {SAVEPOINT save1}
+step "s1_save_2" {SAVEPOINT save2}
+step "s1_save_3" {SAVEPOINT save3}
+step "s1_rollback_to_save_2" {rollback to savepoint save2}
+step "s1_reindex_p" {reindex table gtt_on_commit_preserve_row}
+step "s1_reindex_i_p" {reindex index gtt_on_commit_preserve_row_pkey}
+step "s1_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s1_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s1_analyze_c" {analyze gtt_test_createindex}
+teardown
+{
+  TRUNCATE gtt_on_commit_delete_row RESTART IDENTITY;
+  TRUNCATE gtt_on_commit_preserve_row RESTART IDENTITY;
+}
+
+# Session 2
+session "s2"
+step "s2_begin" {begin}
+step "s2_commit" {commit}
+step "s2_rollback" {rollback}
+step "s2_insert_p" {insert into gtt_on_commit_preserve_row (b) values('test10')}
+step "s2_select_p" {select a,b from gtt_on_commit_preserve_row order by a,b}
+step "s2_insert_d" {insert into gtt_on_commit_delete_row (b) values('test1')}
+step "s2_truncate_p" {truncate gtt_on_commit_preserve_row}
+step "s2_update_p" {update gtt_on_commit_preserve_row set b = 'update'}
+step "s2_lock_p" {LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE}
+step "s2_save_1" {SAVEPOINT save1}
+step "s2_save_2" {SAVEPOINT save2}
+step "s2_save_3" {SAVEPOINT save3}
+step "s2_rollback_to_save_2" {rollback to savepoint save2}
+step "s2_reindex_p" {reindex table gtt_on_commit_preserve_row}
+step "s2_reindex_i_p" {reindex index gtt_on_commit_preserve_row_pkey}
+step "s2_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s2_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s2_analyze_c" {analyze gtt_test_createindex}
+teardown
+{
+  TRUNCATE gtt_on_commit_delete_row RESTART IDENTITY;
+  TRUNCATE gtt_on_commit_preserve_row RESTART IDENTITY;
+}
+
+session "s3"
+step "s3_create_c" {create unique index idx_temp_table_a on gtt_test_createindex(a)}
+step "s3_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s3_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s3_analyze_c" {analyze gtt_test_createindex}
+
+
+#
+# test on commit delete temp table
+#
+
+# test update empty temp table
+permutation "s1_update_d"
+# test insert into temp table
+permutation "s1_select_d" "s1_insert_d" "s1_select_d"
+# test temp table in transaction(commit)
+permutation "s1_select_d" "s1_begin"    "s1_insert_d" "s1_select_d"   "s1_commit"   "s1_select_d" 
+# test temp table in transaction(rollback)
+permutation "s1_select_d" "s1_begin"    "s1_insert_d" "s1_select_d"   "s1_rollback" "s1_select_d" 
+# test truncate
+permutation "s1_select_d" "s1_insert_d" "s1_select_d" "s1_truncate_d" "s1_select_d"
+# test truncate in transaction block
+permutation "s1_select_d" "s1_insert_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_truncate_d" "s1_select_d"   "s1_insert_d" "s1_select_d" "s1_commit"   "s1_select_d" 
+permutation "s1_select_d" "s1_insert_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_truncate_d" "s1_select_d"   "s1_insert_d" "s1_select_d" "s1_rollback" "s1_select_d"
+# test temp table with subtransaction or savepoint
+permutation "s1_insert_d" "s1_select_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_save_1"     "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_2"   "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_3" "s1_rollback_to_save_2" "s1_select_d" "s1_insert_d" "s1_select_d" "s1_commit" "s1_select_d"
+permutation "s1_insert_d" "s1_select_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_save_1"     "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_2"   "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_3" "s1_rollback_to_save_2" "s1_select_d" "s1_insert_d" "s1_select_d" "s1_rollback" "s1_select_d"
+
+#
+# test on commit preserve table
+#
+
+# same as test on commit delete temp table
+permutation "s2_update_p"
+permutation "s2_select_p" "s2_insert_p" "s2_select_p"
+permutation "s2_select_p" "s2_begin"    "s2_insert_p" "s2_select_p"   "s2_commit"   "s2_select_p"
+permutation "s2_select_p" "s2_begin"    "s2_insert_p" "s2_select_p"   "s2_rollback" "s2_select_p"
+permutation "s2_select_p" "s2_insert_p" "s2_select_p" "s2_truncate_p" "s2_select_p"
+permutation "s2_select_p" "s2_insert_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_truncate_p" "s2_select_p"   "s2_insert_p" "s2_select_p" "s2_commit"   "s2_select_p" 
+permutation "s2_select_p" "s2_insert_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_truncate_p" "s2_select_p"   "s2_insert_p" "s2_select_p" "s2_rollback" "s2_select_p" 
+permutation "s2_insert_p" "s2_select_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_save_1"     "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_2"   "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_3" "s2_rollback_to_save_2" "s2_select_p" "s2_insert_p" "s2_select_p" "s2_commit" "s2_select_p"
+permutation "s2_insert_p" "s2_select_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_save_1"     "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_2"   "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_3" "s2_rollback_to_save_2" "s2_select_p" "s2_insert_p" "s2_select_p" "s2_rollback" "s2_select_p"
+
+#
+# test concurrent operation on temp table
+#
+
+#  test concurrent read
+permutation "s1_insert_p" "s2_insert_p" "s1_select_p" "s2_select_p" 
+#  test concurrent truncate
+permutation "s1_begin" "s2_begin"    "s1_insert_p" "s2_insert_p"   "s1_truncate_p" "s2_truncate_p"  "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+permutation "s1_begin" "s1_insert_d" "s2_insert_d" "s1_truncate_d" "s2_insert_d"   "s1_commit" 
+#  test concurrent reindex table
+permutation "s1_begin" "s2_begin" "s1_insert_p" "s2_insert_p" "s1_reindex_p"   "s2_reindex_p"   "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+#  test concurrent reindex index
+permutation "s1_begin" "s2_begin" "s1_insert_p" "s2_insert_p" "s1_reindex_i_p" "s2_reindex_i_p" "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+
+# test create index
+permutation "s2_insert_c" "s3_create_c" "s3_insert_c" "s1_insert_c" "s1_analyze_c" "s2_analyze_c" "s3_analyze_c" "s1_select_c" "s2_select_c" "s3_select_c"
+
+# test lock gtt
+permutation "s1_begin" "s2_begin" "s1_lock_p" "s2_lock_p" "s1_truncate_p" "s2_truncate_p" "s1_insert_p" "s2_insert_p" "s1_commit" "s2_commit" "s1_select_p" "s2_select_p"
diff --git a/src/test/isolation/specscanner.l b/src/test/isolation/specscanner.l
index d9fa6a5b54a..697db975479 100644
--- a/src/test/isolation/specscanner.l
+++ b/src/test/isolation/specscanner.l
@@ -67,6 +67,8 @@ session			{ return SESSION; }
 setup			{ return SETUP; }
 step			{ return STEP; }
 teardown		{ return TEARDOWN; }
+initialize		 { return INITIALIZE; }
+destroy			 { return DESTROY; }
 
  /* Whitespace and comments */
 [\n]			{ yyline++; }
diff --git a/src/test/regress/expected/global_temporary_table.out b/src/test/regress/expected/global_temporary_table.out
new file mode 100644
index 00000000000..ec696c61ce9
--- /dev/null
+++ b/src/test/regress/expected/global_temporary_table.out
@@ -0,0 +1,556 @@
+--
+-- GLobal emparary table test case 
+--
+CREATE SCHEMA IF NOT EXISTS global_temporary_table;
+set search_path=global_temporary_table,sys;
+--
+--  test create global temp table basic syntax
+--
+create global temp table gtt_on_commit_default(a int primary key, b text);
+create global temp table gtt_on_commit_delete(a int primary key, b text) on commit delete rows;
+create global temp table gtt_on_commit_delete2(n int) with (on_commit_delete_rows='true');
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit PRESERVE rows;
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt_test_createindex(c0 tsvector,c1 varchar(100), c2 int);
+create global temp table gtt_test_alter(b text) with(on_commit_delete_rows=true);
+--
+-- test DML on global temp table
+--
+-- update empty temp table
+update gtt_on_commit_delete set b ='test';
+begin;
+insert into gtt_on_commit_delete values (1);
+update gtt_on_commit_delete set b ='test';
+-- should 1 row
+select * from gtt_on_commit_delete;
+ a |  b   
+---+------
+ 1 | test
+(1 row)
+
+commit;
+-- data delete after transaction commit
+-- should 0 row
+select * from gtt_on_commit_delete;
+ a | b 
+---+---
+(0 rows)
+
+-- update empty temp table
+update gtt_on_commit_preserve set b ='test';
+insert into gtt_on_commit_preserve values (2);
+begin;
+insert into gtt_on_commit_preserve values (3);
+update gtt_on_commit_preserve set b ='test';
+-- should 2 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 2 | test
+ 3 | test
+(2 rows)
+
+delete from gtt_on_commit_preserve where a=2;
+commit;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 3 | test
+(1 row)
+
+begin;
+insert into gtt_on_commit_preserve values (4);
+-- temp table support truncate;
+truncate gtt_on_commit_preserve;
+select * from gtt_on_commit_preserve order by a;
+ a | b 
+---+---
+(0 rows)
+
+rollback;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 3 | test
+(1 row)
+
+--
+-- test unsupported global temp partition table
+--
+-- should fail
+CREATE global temp TABLE global_temp_partition_01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+on commit delete rows;
+ERROR:  Only support global temporary regular table.
+CREATE TABLE regular_partition_01_2019 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+);
+CREATE TABLE regular_partition01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time);
+-- should fail
+CREATE global temp TABLE temp_partition01_2018
+PARTITION OF regular_partition01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+ERROR:  Not support global temporary partition table or inherit table.
+CREATE global temp TABLE global_temp_partition_01_2021 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+)on commit delete rows;
+-- should fail
+ALTER TABLE regular_partition01 ATTACH PARTITION global_temp_partition_01_2021 FOR VALUES FROM ('2021-01-01 00:00:00') TO ('2022-01-01 00:00:00');
+ERROR:  cannot attach a global temporary relation as partition of permanent relation "regular_partition01"
+--
+-- test unsupported inherit table
+--
+create table 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 inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent);
+ERROR:  Not support global temporary partition table or inherit table.
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent_global_temp) on commit delete rows;
+ERROR:  Not support global temporary partition table or inherit table.
+--
+-- test DDL on global temp table
+--
+create index idx_gtt_test_alter_b on gtt_test_alter (b);
+insert into gtt_test_alter values('test');
+alter table gtt_test_alter alter b type varchar;
+create index gtt_idx_1 on gtt_test_createindex using gin (c0);
+create index gtt_idx_2 on gtt_test_createindex using gist (c0);
+create index gtt_idx_3 on gtt_test_createindex using hash (c2);
+alter table gtt_test_rename rename to gtt_test_new;
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+create index CONCURRENTLY idx_b on gtt_on_commit_default (b);
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+insert into gtt_on_commit_default values(1,'test');
+create global temp table gtt_test_alter1 (a int primary key,b text);
+alter table gtt_test_alter1 alter a type varchar;
+-- should fail
+alter table gtt_on_commit_default alter a type varchar;
+ERROR:  Only support alter global temporary table in an empty context.
+HINT:  Please create a new connection and execute ALTER TABLE on the new connection.
+-- should fail
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+ERROR:  not support cluster global temporary table yet
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- should fail
+alter table gtt_on_commit_default SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temporary table
+-- should fail
+alter table gtt_on_commit_default set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table
+-- should fail
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- should fail
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- should fail
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit drop;
+ERROR:  global temporary table not support on commit drop clause
+-- should fail
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  could not create global temporary table with on commit and with clause at same time
+-- should fail
+CREATE MATERIALIZED VIEW mv_gtt_on_commit_default as select * from gtt_on_commit_default;
+ERROR:  materialized views must not use global temporary tables or views
+--
+-- test foreign key dependencies for global temp table
+--
+CREATE global temp TABLE temp_products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+CREATE global temp TABLE temp_orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+)on commit delete rows;
+-- should fail
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+CREATE global temp TABLE temp_orders_2 (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products(product_no),
+    quantity integer
+);
+--should fail
+insert into temp_orders values(1,1,1);
+ERROR:  insert or update on table "temp_orders" violates foreign key constraint "temp_orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "temp_products".
+insert into temp_products values(1,'test',1.0);
+begin;
+insert into temp_orders values(1,1,1);
+commit;
+-- should 1 row
+select count(*) from temp_products;
+ count 
+-------
+     1
+(1 row)
+
+-- should 0 row
+select count(*) from temp_orders;
+ count 
+-------
+     0
+(1 row)
+
+--
+-- test sequence on global temp table
+--
+create global temp table global_temp_with_serial (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+-- support insert data to temp table in read only transaction
+insert into global_temp_with_serial (b) values(1);
+select * from global_temp_with_serial;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_delete(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_preserve(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table temp_table_with_sequence_oncommit_delete add c2 int default nextval('seq_1');
+alter table temp_table_with_sequence_oncommit_preserve add c2 int default nextval('seq_1');
+begin;
+insert into temp_table_with_sequence_oncommit_delete (c1)values(1);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(2);
+insert into temp_table_with_sequence_oncommit_delete (c1)values(3);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(4);
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  3 |  3
+(2 rows)
+
+commit;
+-- should 0 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_preserve order by c1;
+ c1 | c2 
+----+----
+  2 |  2
+  4 |  4
+(2 rows)
+
+--
+-- test statistics on temp table
+--
+create global temp table temp_table_test_statistics(a int);
+insert into temp_table_test_statistics values(generate_series(1,100000));
+create index idx_test_1 on temp_table_test_statistics (a);
+create index idx_test_2 on temp_table_test_statistics((a*10));
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+                   QUERY PLAN                   
+------------------------------------------------
+ Bitmap Heap Scan on temp_table_test_statistics
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_test_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+                   QUERY PLAN                   
+------------------------------------------------
+ Bitmap Heap Scan on temp_table_test_statistics
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_test_2
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+-- test statistic for whole row
+explain (costs off) select count(*) from temp_table_test_statistics group by temp_table_test_statistics;
+                  QUERY PLAN                  
+----------------------------------------------
+ HashAggregate
+   Group Key: temp_table_test_statistics.*
+   ->  Seq Scan on temp_table_test_statistics
+(3 rows)
+
+-- test statistic for system column
+explain (costs off) select count(*) from temp_table_test_statistics group by tableoid;
+                  QUERY PLAN                  
+----------------------------------------------
+ HashAggregate
+   Group Key: tableoid
+   ->  Seq Scan on temp_table_test_statistics
+(3 rows)
+
+analyze temp_table_test_statistics;
+-- indexscan by idx_test_1
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Index Only Scan using idx_test_1 on temp_table_test_statistics
+   Index Cond: (a = 200000)
+(2 rows)
+
+-- indexscan by idx_test_2
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+                        QUERY PLAN                         
+-----------------------------------------------------------
+ Index Scan using idx_test_2 on temp_table_test_statistics
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+explain (costs off) select count(*) from temp_table_test_statistics group by temp_table_test_statistics;
+                  QUERY PLAN                  
+----------------------------------------------
+ HashAggregate
+   Group Key: temp_table_test_statistics.*
+   ->  Seq Scan on temp_table_test_statistics
+(3 rows)
+
+explain (costs off) select count(*) from temp_table_test_statistics group by tableoid;
+                  QUERY PLAN                  
+----------------------------------------------
+ HashAggregate
+   Group Key: tableoid
+   ->  Seq Scan on temp_table_test_statistics
+(3 rows)
+
+--
+-- test temp table with toast table
+--
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |                0 |        483328 |           98304 |                 581632
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            32768 |                  |         32768 |               0 |                  32768
+(3 rows)
+
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |             8192 |        499712 |          106496 |                 606208
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            40960 |                  |         40960 |               0 |                  40960
+(3 rows)
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,10),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',10);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |             8192 |                0 |         16384 |           32768 |                  49152
+ idx_gtt_t_kenyon_1 |            16384 |                  |         16384 |               0 |                  16384
+ idx_gtt_t_kenyon_2 |            16384 |                  |         16384 |               0 |                  16384
+(3 rows)
+
+rollback;
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |             8192 |        499712 |          106496 |                 606208
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            40960 |                  |         40960 |               0 |                  40960
+(3 rows)
+
+-- test analyze/vacuum on global temp table
+ANALYZE gtt_t_kenyon;
+VACUUM gtt_t_kenyon;
+--
+-- test global temp table system view
+--
+create global temp table temp_table_test_systemview(a int primary key, b text) on commit PRESERVE rows;
+-- should empty, storage not initialized
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+ tablename 
+-----------
+(0 rows)
+
+-- should empty, storage not initialized
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into temp_table_test_systemview values(generate_series(1,10000),'test');
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+         tablename          
+----------------------------
+ temp_table_test_systemview
+(1 row)
+
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+       schemaname       |            tablename            | relpages | reltuples | relallvisible 
+------------------------+---------------------------------+----------+-----------+---------------
+ global_temporary_table | temp_table_test_systemview      |        0 |         0 |             0
+ global_temporary_table | temp_table_test_systemview_pkey |        1 |         0 |             0
+(2 rows)
+
+-- should empty
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+analyze temp_table_test_systemview;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+       schemaname       |            tablename            | relpages | reltuples | relallvisible 
+------------------------+---------------------------------+----------+-----------+---------------
+ global_temporary_table | temp_table_test_systemview      |       55 |     10000 |             0
+ global_temporary_table | temp_table_test_systemview_pkey |       30 |     10000 |             0
+(2 rows)
+
+-- get data after analyze;
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+       schemaname       |         tablename          | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------------------+----------------------------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ global_temporary_table | temp_table_test_systemview | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ global_temporary_table | temp_table_test_systemview | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+-- get all object info in current schema
+select relname ,relkind, relpersistence, reloptions from pg_class c, pg_namespace n where c.relnamespace = n.oid and n.nspname = 'global_temporary_table' order by relkind,relpersistence,relname;
+                     relname                     | relkind | relpersistence |          reloptions           
+-------------------------------------------------+---------+----------------+-------------------------------
+ global_temp_partition_01_2021_id_seq            | S       | g              | {on_commit_delete_rows=false}
+ global_temp_with_serial_a_seq                   | S       | g              | {on_commit_delete_rows=false}
+ regular_partition01_id_seq                      | S       | p              | 
+ regular_partition_01_2019_id_seq                | S       | p              | 
+ seq_1                                           | S       | p              | 
+ gtt_idx_1                                       | i       | g              | 
+ gtt_idx_2                                       | i       | g              | 
+ gtt_idx_3                                       | i       | g              | 
+ gtt_on_commit_default_pkey                      | i       | g              | 
+ gtt_on_commit_delete_pkey                       | i       | g              | 
+ gtt_on_commit_preserve_pkey                     | i       | g              | 
+ gtt_test_alter1_pkey                            | i       | g              | 
+ gtt_test_rename_pkey                            | i       | g              | 
+ idx_b                                           | i       | g              | 
+ idx_gtt_t_kenyon_1                              | i       | g              | 
+ idx_gtt_t_kenyon_2                              | i       | g              | 
+ idx_gtt_test_alter_b                            | i       | g              | 
+ idx_test_1                                      | i       | g              | 
+ idx_test_2                                      | i       | g              | 
+ products_pkey                                   | i       | g              | 
+ temp_orders_2_pkey                              | i       | g              | 
+ temp_orders_pkey                                | i       | g              | 
+ temp_products_pkey                              | i       | g              | 
+ temp_table_test_systemview_pkey                 | i       | g              | 
+ temp_table_with_sequence_oncommit_delete_pkey   | i       | g              | 
+ temp_table_with_sequence_oncommit_preserve_pkey | i       | g              | 
+ regular_partition01                             | p       | p              | 
+ global_temp_partition_01_2021                   | r       | g              | {on_commit_delete_rows=true}
+ global_temp_with_serial                         | r       | g              | {on_commit_delete_rows=false}
+ gtt_on_commit_default                           | r       | g              | {on_commit_delete_rows=false}
+ gtt_on_commit_delete                            | r       | g              | {on_commit_delete_rows=true}
+ gtt_on_commit_delete2                           | r       | g              | {on_commit_delete_rows=true}
+ gtt_on_commit_preserve                          | r       | g              | {on_commit_delete_rows=false}
+ gtt_t_kenyon                                    | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_alter                                  | r       | g              | {on_commit_delete_rows=true}
+ gtt_test_alter1                                 | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_createindex                            | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_new                                    | r       | g              | {on_commit_delete_rows=false}
+ inherits_parent_global_temp                     | r       | g              | {on_commit_delete_rows=true}
+ products                                        | r       | g              | {on_commit_delete_rows=false}
+ temp_orders                                     | r       | g              | {on_commit_delete_rows=true}
+ temp_orders_2                                   | r       | g              | {on_commit_delete_rows=false}
+ temp_products                                   | r       | g              | {on_commit_delete_rows=false}
+ temp_table_test_statistics                      | r       | g              | {on_commit_delete_rows=false}
+ temp_table_test_systemview                      | r       | g              | {on_commit_delete_rows=false}
+ temp_table_with_sequence_oncommit_delete        | r       | g              | {on_commit_delete_rows=true}
+ temp_table_with_sequence_oncommit_preserve      | r       | g              | {on_commit_delete_rows=false}
+ foo                                             | r       | p              | 
+ inherits_parent                                 | r       | p              | 
+ regular_partition_01_2019                       | r       | p              | 
+(50 rows)
+
+reset search_path;
+drop schema global_temporary_table cascade;
+NOTICE:  drop cascades to 25 other objects
+DETAIL:  drop cascades to table global_temporary_table.gtt_on_commit_default
+drop cascades to table global_temporary_table.gtt_on_commit_delete
+drop cascades to table global_temporary_table.gtt_on_commit_delete2
+drop cascades to table global_temporary_table.gtt_on_commit_preserve
+drop cascades to table global_temporary_table.gtt_test_new
+drop cascades to table global_temporary_table.gtt_test_createindex
+drop cascades to table global_temporary_table.gtt_test_alter
+drop cascades to table global_temporary_table.regular_partition_01_2019
+drop cascades to table global_temporary_table.regular_partition01
+drop cascades to table global_temporary_table.global_temp_partition_01_2021
+drop cascades to table global_temporary_table.inherits_parent
+drop cascades to table global_temporary_table.inherits_parent_global_temp
+drop cascades to table global_temporary_table.gtt_test_alter1
+drop cascades to table global_temporary_table.foo
+drop cascades to table global_temporary_table.temp_products
+drop cascades to table global_temporary_table.products
+drop cascades to table global_temporary_table.temp_orders
+drop cascades to table global_temporary_table.temp_orders_2
+drop cascades to table global_temporary_table.global_temp_with_serial
+drop cascades to sequence global_temporary_table.seq_1
+drop cascades to table global_temporary_table.temp_table_with_sequence_oncommit_delete
+drop cascades to table global_temporary_table.temp_table_with_sequence_oncommit_preserve
+drop cascades to table global_temporary_table.temp_table_test_statistics
+drop cascades to table global_temporary_table.gtt_t_kenyon
+drop cascades to table global_temporary_table.temp_table_test_systemview
+-- should empty
+select * from pg_list_gtt_relfrozenxids();
+ pid | relfrozenxid 
+-----+--------------
+(0 rows)
+
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2fa00a3c29a..e0001bc3448 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1359,6 +1359,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 7be89178f0f..db8095d30bf 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -130,3 +130,6 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: global_temporary_table
diff --git a/src/test/regress/sql/global_temporary_table.sql b/src/test/regress/sql/global_temporary_table.sql
new file mode 100644
index 00000000000..1c45de23294
--- /dev/null
+++ b/src/test/regress/sql/global_temporary_table.sql
@@ -0,0 +1,299 @@
+--
+-- GLobal emparary table test case 
+--
+
+CREATE SCHEMA IF NOT EXISTS global_temporary_table;
+set search_path=global_temporary_table,sys;
+
+--
+--  test create global temp table basic syntax
+--
+create global temp table gtt_on_commit_default(a int primary key, b text);
+create global temp table gtt_on_commit_delete(a int primary key, b text) on commit delete rows;
+create global temp table gtt_on_commit_delete2(n int) with (on_commit_delete_rows='true');
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit PRESERVE rows;
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt_test_createindex(c0 tsvector,c1 varchar(100), c2 int);
+create global temp table gtt_test_alter(b text) with(on_commit_delete_rows=true);
+--
+-- test DML on global temp table
+--
+
+-- update empty temp table
+update gtt_on_commit_delete set b ='test';
+begin;
+insert into gtt_on_commit_delete values (1);
+update gtt_on_commit_delete set b ='test';
+-- should 1 row
+select * from gtt_on_commit_delete;
+commit;
+-- data delete after transaction commit
+-- should 0 row
+select * from gtt_on_commit_delete;
+
+-- update empty temp table
+update gtt_on_commit_preserve set b ='test';
+insert into gtt_on_commit_preserve values (2);
+begin;
+insert into gtt_on_commit_preserve values (3);
+update gtt_on_commit_preserve set b ='test';
+-- should 2 row
+select * from gtt_on_commit_preserve order by a;
+delete from gtt_on_commit_preserve where a=2;
+commit;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+
+begin;
+insert into gtt_on_commit_preserve values (4);
+-- temp table support truncate;
+truncate gtt_on_commit_preserve;
+select * from gtt_on_commit_preserve order by a;
+rollback;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+
+--
+-- test unsupported global temp partition table
+--
+
+-- should fail
+CREATE global temp TABLE global_temp_partition_01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+on commit delete rows;
+
+CREATE TABLE regular_partition_01_2019 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+);
+
+CREATE TABLE regular_partition01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time);
+
+-- should fail
+CREATE global temp TABLE temp_partition01_2018
+PARTITION OF regular_partition01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE global_temp_partition_01_2021 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+)on commit delete rows;
+
+-- should fail
+ALTER TABLE regular_partition01 ATTACH PARTITION global_temp_partition_01_2021 FOR VALUES FROM ('2021-01-01 00:00:00') TO ('2022-01-01 00:00:00');
+
+--
+-- test unsupported inherit table
+--
+create table 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 inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent);
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent_global_temp) on commit delete rows;
+
+--
+-- test DDL on global temp table
+--
+create index idx_gtt_test_alter_b on gtt_test_alter (b);
+insert into gtt_test_alter values('test');
+alter table gtt_test_alter alter b type varchar;
+create index gtt_idx_1 on gtt_test_createindex using gin (c0);
+create index gtt_idx_2 on gtt_test_createindex using gist (c0);
+create index gtt_idx_3 on gtt_test_createindex using hash (c2);
+alter table gtt_test_rename rename to gtt_test_new;
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+create index CONCURRENTLY idx_b on gtt_on_commit_default (b);
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+insert into gtt_on_commit_default values(1,'test');
+create global temp table gtt_test_alter1 (a int primary key,b text);
+alter table gtt_test_alter1 alter a type varchar;
+-- should fail
+alter table gtt_on_commit_default alter a type varchar;
+-- should fail
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) on commit delete rows;
+-- should fail
+alter table gtt_on_commit_default SET TABLESPACE pg_default;
+-- should fail
+alter table gtt_on_commit_default set ( on_commit_delete_rows='true');
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) with(on_commit_delete_rows=true);
+-- should fail
+create or replace global temp view gtt_v as select 5;
+create table foo();
+-- should fail
+alter table foo set (on_commit_delete_rows='true');
+-- should fail
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit drop;
+-- should fail
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+-- should fail
+CREATE MATERIALIZED VIEW mv_gtt_on_commit_default as select * from gtt_on_commit_default;
+
+--
+-- test foreign key dependencies for global temp table
+--
+CREATE global temp TABLE temp_products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+CREATE global temp TABLE temp_orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+)on commit delete rows;
+
+-- should fail
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+);
+
+CREATE global temp TABLE temp_orders_2 (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products(product_no),
+    quantity integer
+);
+
+--should fail
+insert into temp_orders values(1,1,1);
+
+insert into temp_products values(1,'test',1.0);
+begin;
+insert into temp_orders values(1,1,1);
+commit;
+-- should 1 row
+select count(*) from temp_products;
+-- should 0 row
+select count(*) from temp_orders;
+
+--
+-- test sequence on global temp table
+--
+create global temp table global_temp_with_serial (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+-- support insert data to temp table in read only transaction
+insert into global_temp_with_serial (b) values(1);
+select * from global_temp_with_serial;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_delete(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_preserve(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table temp_table_with_sequence_oncommit_delete add c2 int default nextval('seq_1');
+alter table temp_table_with_sequence_oncommit_preserve add c2 int default nextval('seq_1');
+begin;
+insert into temp_table_with_sequence_oncommit_delete (c1)values(1);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(2);
+insert into temp_table_with_sequence_oncommit_delete (c1)values(3);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(4);
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+commit;
+-- should 0 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_preserve order by c1;
+
+--
+-- test statistics on temp table
+--
+create global temp table temp_table_test_statistics(a int);
+insert into temp_table_test_statistics values(generate_series(1,100000));
+create index idx_test_1 on temp_table_test_statistics (a);
+create index idx_test_2 on temp_table_test_statistics((a*10));
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+-- test statistic for whole row
+explain (costs off) select count(*) from temp_table_test_statistics group by temp_table_test_statistics;
+-- test statistic for system column
+explain (costs off) select count(*) from temp_table_test_statistics group by tableoid;
+analyze temp_table_test_statistics;
+-- indexscan by idx_test_1
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+-- indexscan by idx_test_2
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+explain (costs off) select count(*) from temp_table_test_statistics group by temp_table_test_statistics;
+explain (costs off) select count(*) from temp_table_test_statistics group by tableoid;
+
+--
+-- test temp table with toast table
+--
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,10),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',10);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+rollback;
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+
+-- test analyze/vacuum on global temp table
+ANALYZE gtt_t_kenyon;
+VACUUM gtt_t_kenyon;
+
+--
+-- test global temp table system view
+--
+create global temp table temp_table_test_systemview(a int primary key, b text) on commit PRESERVE rows;
+-- should empty, storage not initialized
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+-- should empty, storage not initialized
+select count(*) from pg_list_gtt_relfrozenxids();
+insert into temp_table_test_systemview values(generate_series(1,10000),'test');
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+select count(*) from pg_list_gtt_relfrozenxids();
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+-- should empty
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+analyze temp_table_test_systemview;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+-- get data after analyze;
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+
+-- get all object info in current schema
+select relname ,relkind, relpersistence, reloptions from pg_class c, pg_namespace n where c.relnamespace = n.oid and n.nspname = 'global_temporary_table' order by relkind,relpersistence,relname;
+
+reset search_path;
+drop schema global_temporary_table cascade;
+-- should empty
+select * from pg_list_gtt_relfrozenxids();
+
-- 
2.30.1 (Apple Git-130)

#342Andrew Bille
andrewbille@gmail.com
In reply to: wenjing (#341)
Re: [Proposal] Global temporary tables

Thanks for the patches. The feature has become much more stable.
However, there is another simple case that generates an error:
Master with v61 patches

CREATE GLOBAL TEMPORARY TABLE t AS SELECT 1 AS a;
ERROR: could not open file "base/13560/t3_16384": No such file or directory
Andrew

On Thu, Nov 11, 2021 at 3:15 PM wenjing <wjzeng2012@gmail.com> wrote:

Show quoted text

Fixed a bug in function pg_gtt_attached_pid.
Looking forward to your reply.

Wenjing

#343wenjing
wjzeng2012@gmail.com
In reply to: Andrew Bille (#342)
4 attachment(s)
Re: [Proposal] Global temporary tables

Andrew Bille <andrewbille@gmail.com> 于2021年11月15日周一 下午6:34写道:

Thanks for the patches. The feature has become much more stable.
However, there is another simple case that generates an error:
Master with v61 patches

CREATE GLOBAL TEMPORARY TABLE t AS SELECT 1 AS a;
ERROR: could not open file "base/13560/t3_16384": No such file or
directory

Thank you for pointing out that this part is not reasonable enough.
This issue has been fixed in v62.
Looking forward to your reply.

Wenjing

Show quoted text

Andrew

On Thu, Nov 11, 2021 at 3:15 PM wenjing <wjzeng2012@gmail.com> wrote:

Fixed a bug in function pg_gtt_attached_pid.
Looking forward to your reply.

Wenjing

Attachments:

0001-gtt-v62-reademe.patchapplication/octet-stream; name=0001-gtt-v62-reademe.patchDownload
diff --git a/README.gtt.txt b/README.gtt.txt
new file mode 100644
index 00000000000..d181df9acd7
--- /dev/null
+++ b/README.gtt.txt
@@ -0,0 +1,172 @@
+Global Temporary Table(GTT)
+=========================================
+
+Feature description
+-----------------------------------------
+
+Previously, temporary tables are defined once and automatically
+exist (starting with empty contents) in every session before using them.
+
+The temporary table implementation in PostgreSQL, known as Local temp tables(LTT),
+did not fully comply with the SQL standard. This version added the support of
+Global Temporary Table .
+
+The metadata of Global Temporary Table is persistent and shared among sessions.
+The data stored in the Global temporary table is independent of sessions. This
+means, when a session creates a Global Temporary Table and writes some data.
+Other sessions cannot see those data, but they have an empty Global Temporary
+Table with same schema.
+
+Like local temporary table, Global Temporary Table supports ON COMMIT PRESERVE ROWS
+or ON COMMIT DELETE ROWS clause, so that data in the temporary table can be
+cleaned up or preserved automatically when a session exits or a transaction COMMITs.
+
+Unlike Local Temporary Table, Global Temporary Table does not support
+ON COMMIT DROP clauses.
+
+In following paragraphs, we use GTT for Global Temporary Table and LTT for
+local temporary table.
+
+Main design ideas
+-----------------------------------------
+In general, GTT and LTT use the same storage and buffer design and
+implementation. The storage files for both types of temporary tables are named
+as t_backendid_relfilenode, and the local buffer is used to cache the data.
+
+The schema of GTTs is shared among sessions while their data are not. We build
+a new mechanisms to manage those non-shared data and their statistics.
+Here is the summary of changes:
+
+1) CATALOG
+GTTs store session-specific data. The storage information of GTTs'data, their
+transaction information, and their statistics are not stored in the catalog.
+
+2) STORAGE INFO & STATISTICS INFO & TRANSACTION INFO
+In order to maintain durability and availability of GTTs'session-specific data,
+their storage information, statistics, and transaction information is managed
+in a local hash table tt_storage_local_hash.
+
+3) DDL
+Currently, GTT supports almost all table'DDL except CLUSTER/VACUUM FULL.
+Part of the DDL behavior is limited by shared definitions and multiple copies of
+local data, and we added some structures to handle this.
+
+A shared hash table active_gtt_shared_hash is added to track the state of the
+GTT in a different session. This information is recorded in the hash table
+during the DDL execution of the GTT.
+
+The data stored in a GTT can only be modified or accessed by owning session.
+The statements that only modify data in a GTT do not need a high level of
+table locking. The operations making those changes include truncate GTT,
+reindex GTT, and lock GTT.
+
+4) MVCC commit log(clog) cleanup
+Each GTT in a session has its own piece of data, and they have their own
+transaction information. We set up data structures to track and maintain
+this information. The cleaning of CLOGs also needs to consider the transaction
+information of GTT.
+
+Detailed design
+-----------------------------------------
+
+1. CATALOG
+1.1 relpersistence
+define RELPERSISTENCEGLOBALTEMP 'g'
+Mark Global Temporary Table in pg_class relpersistence to 'g'. The relpersistence
+of indexes created on the GTT, sequences on GTT and toast tables on GTT are
+also set to 'g'
+
+1.2 on commit clause
+LTT's status associated with on commit DELETE ROWS and on commit PRESERVE ROWS
+is not stored in catalog. Instead, GTTs need a bool value on_commit_delete_rows
+in reloptions which is shared among sessions.
+
+1.3 gram.y
+GTT is already supported in syntax tree. We remove the warning message
+"GLOBAL is deprecated in temporary table creation" and mark
+relpersistence = RELPERSISTENCEGLOBALTEMP.
+
+2. STORAGE INFO & STATISTICS INFO & TRANSACTION INFO
+2.1. gtt_storage_local_hash
+Each backend creates a local hashtable gtt_storage_local_hash to track a GTT's
+storage file information, statistics, and transaction information.
+
+2.2 GTT storage file info track
+1) When one session inserts data into a GTT for the first time, record the
+storage info to gtt_storage_local_hash.
+2) Use beforeshmemexit to ensure that all files of session GTT are deleted when
+the session exits.
+
+2.3 statistics info
+1) relpages reltuples relallvisible relfilenode
+2) The statistics of each column from pg_statistic
+All the above information is stored in gtt_storage_local_hash.
+When doing vacuum or analyze, GTT's statistic is updated, which is used by
+the SQL planner.
+The statistics summarizes only data in the current session.
+
+2.3 transaction info track
+frozenxid minmulti from pg_class is stored to gtt_storage_local_hash.
+
+3 DDL
+3.1. active_gtt_shared_hash
+This is the hash table created in shared memory to trace the GTT files initialized
+in each session. Each hash entry contains a bitmap that records the backendid of
+the initialized GTT file. With this hash table, we know which backend/session
+is using this GTT. Such information is used during GTT's DDL operations.
+
+3.2 DROP GTT
+One GTT is allowed to be deleted when there is only one session using the table
+and the session is the current session. After holding the lock on GTT,
+active_gtt_shared_hash is checked to ensure that.
+
+3.3 ALTER GTT/DROP INDEX ON GTT
+Same as drop GTT.
+
+3.4 CREATE INDEX ON GTT
+1) create index on GTT statements build index based on local data in a session.
+2) After the index is created, record the index metadata to the catalog.
+3) Other sessions can enable or disable the local GTT index.
+
+3.5 TRUNCATE/REINDEX GTT
+The SQL truncate/reindex command open the GTT using AccessShareLock lock,
+not AccessExclusiveLock, because this command only cleans up local data and
+local buffers in current session. This allows these operations to be executed
+concurrently between sessions, unlike normal tables.
+
+3.6 LOCK GTT
+A lock GTT statement does not hold any relation lock.
+
+3.7 CLUSTER GTT/VACUUM FULL GTT
+The current version does not support.
+
+4 MVCC commit log(clog) cleanup
+
+The GTT storage file contains transaction information. Queries for GTT data rely
+on transaction information such as clog. The transaction information required by
+each session may be completely different. We need to ensure that the transaction
+information of the GTT data is not cleaned up during its lifetime and that
+transaction resources are recycled at the instance level.
+
+4.1 The session level GTT oldest frozenxid
+1) To manage all GTT transaction information, add session level oldest frozenxid
+in each session. When one GTT is created or removed, record the session level
+oldest frozenxid and store it in MyProc.
+2) When vacuum advances the database's frozenxid, session level oldest frozenxid
+should be considered. This is acquired by searching all of MyProc. This way,
+we can avoid the clog required by GTTs to be cleaned.
+
+4.2 vacuum GTT
+Users can perform vacuum over a GTT to clean up local data in the GTT.
+
+4.3 autovacuum GTT
+Autovacuum skips all GTTs, because the data in GTTs is only visible in current session.
+
+5 OTHERS
+5.1 Parallel query
+Planner does not produce parallel query plans for SQL related to GTT. Because
+GTT private data cannot be accessed across processes.
+
+5.2 WAL and Logical replication
+Like LTT, the DML on GTT does not record WAL and is not parsed or replay by
+the logical replication.
\ No newline at end of file
-- 
2.30.1 (Apple Git-130)

0004-gtt-v62-regress.patchapplication/octet-stream; name=0004-gtt-v62-regress.patchDownload
diff --git a/src/test/isolation/expected/gtt-sequence.out b/src/test/isolation/expected/gtt-sequence.out
new file mode 100644
index 00000000000..31db2ebd423
--- /dev/null
+++ b/src/test/isolation/expected/gtt-sequence.out
@@ -0,0 +1,48 @@
+unused step name: s1_seq_restart
+Parsed test spec with 2 sessions
+
+starting permutation: s1_seq_next s2_seq_next s1_seq_next
+step s1_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      1
+(1 row)
+
+step s2_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      1
+(1 row)
+
+step s1_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      2
+(1 row)
+
+
+starting permutation: s1_select s2_select s1_insert s2_insert s1_select s2_select
+step s1_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+(0 rows)
+
+step s2_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+(0 rows)
+
+step s1_insert: insert into gtt_with_seq values(1);
+step s2_insert: insert into gtt_with_seq values(10);
+step s1_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+ 1| 3
+(1 row)
+
+step s2_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+10| 1
+(1 row)
+
diff --git a/src/test/isolation/expected/gtt-table.out b/src/test/isolation/expected/gtt-table.out
new file mode 100644
index 00000000000..5825773aa12
--- /dev/null
+++ b/src/test/isolation/expected/gtt-table.out
@@ -0,0 +1,675 @@
+Parsed test spec with 3 sessions
+
+starting permutation: s1_update_d
+step s1_update_d: update gtt_on_commit_delete_row set b = 'update'
+
+starting permutation: s1_select_d s1_insert_d s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_begin s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+1|test1
+(1 row)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_begin s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+1|test1
+(1 row)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_select_d s1_truncate_d s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_begin s1_insert_d s1_select_d s1_truncate_d s1_select_d s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_begin s1_insert_d s1_select_d s1_truncate_d s1_select_d s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_insert_d s1_select_d s1_begin s1_insert_d s1_select_d s1_save_1 s1_truncate_d s1_insert_d s1_select_d s1_save_2 s1_truncate_d s1_insert_d s1_select_d s1_save_3 s1_rollback_to_save_2 s1_select_d s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_save_1: SAVEPOINT save1
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_save_2: SAVEPOINT save2
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+4|test1
+(1 row)
+
+step s1_save_3: SAVEPOINT save3
+step s1_rollback_to_save_2: rollback to savepoint save2
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+5|test1
+(2 rows)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_insert_d s1_select_d s1_begin s1_insert_d s1_select_d s1_save_1 s1_truncate_d s1_insert_d s1_select_d s1_save_2 s1_truncate_d s1_insert_d s1_select_d s1_save_3 s1_rollback_to_save_2 s1_select_d s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_save_1: SAVEPOINT save1
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_save_2: SAVEPOINT save2
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+4|test1
+(1 row)
+
+step s1_save_3: SAVEPOINT save3
+step s1_rollback_to_save_2: rollback to savepoint save2
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+5|test1
+(2 rows)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_update_p
+step s2_update_p: update gtt_on_commit_preserve_row set b = 'update'
+
+starting permutation: s2_select_p s2_insert_p s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_begin s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_begin s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_select_p s2_insert_p s2_select_p s2_truncate_p s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_select_p s2_insert_p s2_begin s2_insert_p s2_select_p s2_truncate_p s2_select_p s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_insert_p s2_begin s2_insert_p s2_select_p s2_truncate_p s2_select_p s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_insert_p s2_select_p s2_begin s2_insert_p s2_select_p s2_save_1 s2_truncate_p s2_insert_p s2_select_p s2_save_2 s2_truncate_p s2_insert_p s2_select_p s2_save_3 s2_rollback_to_save_2 s2_select_p s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_save_1: SAVEPOINT save1
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_save_2: SAVEPOINT save2
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+4|test10
+(1 row)
+
+step s2_save_3: SAVEPOINT save3
+step s2_rollback_to_save_2: rollback to savepoint save2
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+
+starting permutation: s2_insert_p s2_select_p s2_begin s2_insert_p s2_select_p s2_save_1 s2_truncate_p s2_insert_p s2_select_p s2_save_2 s2_truncate_p s2_insert_p s2_select_p s2_save_3 s2_rollback_to_save_2 s2_select_p s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_save_1: SAVEPOINT save1
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_save_2: SAVEPOINT save2
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+4|test10
+(1 row)
+
+step s2_save_3: SAVEPOINT save3
+step s2_rollback_to_save_2: rollback to savepoint save2
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_insert_p s2_insert_p s1_select_p s2_select_p
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_truncate_p s2_truncate_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_begin s1_insert_d s2_insert_d s1_truncate_d s2_insert_d s1_commit
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s2_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s2_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_commit: commit
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_reindex_p s2_reindex_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_reindex_p: reindex table gtt_on_commit_preserve_row
+step s2_reindex_p: reindex table gtt_on_commit_preserve_row
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_reindex_i_p s2_reindex_i_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_reindex_i_p: reindex index gtt_on_commit_preserve_row_pkey
+step s2_reindex_i_p: reindex index gtt_on_commit_preserve_row_pkey
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_insert_c s3_create_c s3_insert_c s1_insert_c s1_analyze_c s2_analyze_c s3_analyze_c s1_select_c s2_select_c s3_select_c
+step s2_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s3_create_c: create unique index idx_temp_table_a on gtt_test_createindex(a)
+step s3_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s1_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s1_analyze_c: analyze gtt_test_createindex
+step s2_analyze_c: analyze gtt_test_createindex
+step s3_analyze_c: analyze gtt_test_createindex
+step s1_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                                               
+---------------------------------------------------------
+Index Scan using idx_temp_table_a on gtt_test_createindex
+  Index Cond: (a = 1)                                    
+(2 rows)
+
+step s2_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                      
+--------------------------------
+Seq Scan on gtt_test_createindex
+  Filter: (a = 1)               
+(2 rows)
+
+step s3_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                                               
+---------------------------------------------------------
+Index Scan using idx_temp_table_a on gtt_test_createindex
+  Index Cond: (a = 1)                                    
+(2 rows)
+
+
+starting permutation: s1_begin s2_begin s1_lock_p s2_lock_p s1_truncate_p s2_truncate_p s1_insert_p s2_insert_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_lock_p: LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE
+step s2_lock_p: LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE
+step s1_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index f4c01006fc1..746a17f824c 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -96,3 +96,5 @@ test: plpgsql-toast
 test: truncate-conflict
 test: serializable-parallel
 test: serializable-parallel-2
+test: gtt-sequence
+test: gtt-table
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 88594a3cb5d..ec643aadb5f 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -80,9 +80,30 @@ disconnect_atexit(void)
 {
 	int			i;
 
-	for (i = 0; i < nconns; i++)
+	for (i = 1; i < nconns; i++)
 		if (conns[i].conn)
 			PQfinish(conns[i].conn);
+
+	if (parseresult.destroy)
+	{
+		PGresult   *res;
+
+		res = PQexec(conns[0].conn, parseresult.destroy);
+		if (PQresultStatus(res) == PGRES_TUPLES_OK)
+		{
+			printResultSet(res);
+		}
+		else if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		{
+			fprintf(stderr, "destroy failed: %s",
+					PQerrorMessage(conns[0].conn));
+			/* don't exit on teardown failure */
+		}
+		PQclear(res);
+	}
+
+	if (conns[0].conn)
+		PQfinish(conns[0].conn);
 }
 
 int
@@ -214,6 +235,24 @@ main(int argc, char **argv)
 	PQclear(res);
 	termPQExpBuffer(&wait_query);
 
+	if (parseresult.initialize)
+	{
+		PGresult   *res;
+
+		res = PQexec(conns[0].conn, parseresult.initialize);
+		if (PQresultStatus(res) == PGRES_TUPLES_OK)
+		{
+			printResultSet(res);
+		}
+		else if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		{
+			fprintf(stderr, "initialize failed: %s",
+					PQerrorMessage(conns[0].conn));
+			/* don't exit on teardown failure */
+		}
+		PQclear(res);
+	}
+
 	/*
 	 * Run the permutations specified in the spec, or all if none were
 	 * explicitly specified.
diff --git a/src/test/isolation/isolationtester.h b/src/test/isolation/isolationtester.h
index 5f300219c20..b5a29893da9 100644
--- a/src/test/isolation/isolationtester.h
+++ b/src/test/isolation/isolationtester.h
@@ -81,6 +81,8 @@ typedef struct
 	int			nsessions;
 	Permutation **permutations;
 	int			npermutations;
+	char	   *initialize;
+	char	   *destroy;
 } TestSpec;
 
 extern TestSpec parseresult;
diff --git a/src/test/isolation/specparse.y b/src/test/isolation/specparse.y
index c25aa1a73fa..2784f758ed9 100644
--- a/src/test/isolation/specparse.y
+++ b/src/test/isolation/specparse.y
@@ -39,7 +39,7 @@ TestSpec		parseresult;			/* result of parsing is left here */
 }
 
 %type <ptr_list> setup_list
-%type <str>  opt_setup opt_teardown
+%type <str>  opt_setup opt_teardown opt_initialize opt_destroy
 %type <str> setup
 %type <ptr_list> step_list session_list permutation_list opt_permutation_list
 %type <ptr_list> permutation_step_list blocker_list
@@ -51,23 +51,27 @@ TestSpec		parseresult;			/* result of parsing is left here */
 
 %token <str> sqlblock identifier
 %token <integer> INTEGER
-%token NOTICES PERMUTATION SESSION SETUP STEP TEARDOWN TEST
+%token NOTICES PERMUTATION SESSION SETUP STEP TEARDOWN TEST INITIALIZE DESTROY
 
 %%
 
 TestSpec:
+			opt_initialize
 			setup_list
 			opt_teardown
+			opt_destroy
 			session_list
 			opt_permutation_list
 			{
-				parseresult.setupsqls = (char **) $1.elements;
-				parseresult.nsetupsqls = $1.nelements;
-				parseresult.teardownsql = $2;
-				parseresult.sessions = (Session **) $3.elements;
-				parseresult.nsessions = $3.nelements;
-				parseresult.permutations = (Permutation **) $4.elements;
-				parseresult.npermutations = $4.nelements;
+				parseresult.setupsqls = (char **) $2.elements;
+				parseresult.nsetupsqls = $2.nelements;
+				parseresult.teardownsql = $3;
+				parseresult.sessions = (Session **) $5.elements;
+				parseresult.nsessions = $5.nelements;
+				parseresult.permutations = (Permutation **) $6.elements;
+				parseresult.npermutations = $6.nelements;
+				parseresult.initialize = $1;
+				parseresult.destroy = $4;
 			}
 		;
 
@@ -100,6 +104,16 @@ opt_teardown:
 			| TEARDOWN sqlblock	{ $$ = $2; }
 		;
 
+opt_initialize:
+			/* EMPTY */			{ $$ = NULL; }
+			| INITIALIZE sqlblock	{ $$ = $2; }
+		;
+
+opt_destroy:
+			/* EMPTY */			{ $$ = NULL; }
+			| DESTROY sqlblock	{ $$ = $2; }
+		;
+
 session_list:
 			session_list session
 			{
diff --git a/src/test/isolation/specs/gtt-sequence.spec b/src/test/isolation/specs/gtt-sequence.spec
new file mode 100644
index 00000000000..88eece45e29
--- /dev/null
+++ b/src/test/isolation/specs/gtt-sequence.spec
@@ -0,0 +1,39 @@
+# Tests for global temporary relations
+
+initialize
+{
+  CREATE GLOBAL TEMPORARY TABLE if not exists gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+}
+
+destroy
+{
+  /* wait other backend exit */
+  select pg_sleep(1);
+
+  DROP TABLE gtt_with_seq;
+}
+
+# Session 1
+session "s1"
+step "s1_seq_next" { select nextval('gtt_with_seq_c2_seq'); }
+step "s1_seq_restart" { alter sequence gtt_with_seq_c2_seq RESTART; }
+step "s1_insert" { insert into gtt_with_seq values(1); }
+step "s1_select" { select * from gtt_with_seq order by c1,c2; }
+teardown
+{
+  TRUNCATE gtt_with_seq;
+}
+
+# Session 2
+session "s2"
+step "s2_seq_next" { select nextval('gtt_with_seq_c2_seq'); }
+step "s2_insert" { insert into gtt_with_seq values(10); }
+step "s2_select" { select * from gtt_with_seq order by c1,c2; }
+teardown
+{
+  TRUNCATE gtt_with_seq RESTART IDENTITY;
+}
+
+permutation "s1_seq_next" "s2_seq_next" "s1_seq_next"
+permutation "s1_select" "s2_select" "s1_insert" "s2_insert" "s1_select" "s2_select"
+
diff --git a/src/test/isolation/specs/gtt-table.spec b/src/test/isolation/specs/gtt-table.spec
new file mode 100644
index 00000000000..e0396b21ef0
--- /dev/null
+++ b/src/test/isolation/specs/gtt-table.spec
@@ -0,0 +1,135 @@
+# Tests for global temporary relations
+
+initialize
+{
+  create global temp table gtt_on_commit_delete_row(a bigserial primary key, b text) on commit delete rows;
+  create global temp table gtt_on_commit_preserve_row(a bigserial primary key, b text) on commit preserve rows;
+  create global temp table gtt_test_createindex(a int, b char(1000)) on commit preserve rows;
+}
+
+destroy
+{
+  /* wait other backend exit */
+  select pg_sleep(1);
+
+  DROP TABLE gtt_on_commit_delete_row;
+  DROP TABLE gtt_on_commit_preserve_row;
+  DROP TABLE gtt_test_createindex;
+}
+
+# Session 1
+session "s1"
+step "s1_begin" {begin}
+step "s1_commit" {commit}
+step "s1_rollback" {rollback}
+step "s1_insert_d" {insert into gtt_on_commit_delete_row (b) values('test1')}
+step "s1_select_d" {select a,b from gtt_on_commit_delete_row order by a,b}
+step "s1_insert_p" {insert into gtt_on_commit_preserve_row (b) values('test20')}
+step "s1_select_p" {select a,b from gtt_on_commit_preserve_row order by a,b}
+step "s1_truncate_d" {truncate gtt_on_commit_delete_row}
+step "s1_truncate_p" {truncate gtt_on_commit_preserve_row}
+step "s1_lock_p" {LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE}
+step "s1_update_d" {update gtt_on_commit_delete_row set b = 'update'}
+step "s1_save_1" {SAVEPOINT save1}
+step "s1_save_2" {SAVEPOINT save2}
+step "s1_save_3" {SAVEPOINT save3}
+step "s1_rollback_to_save_2" {rollback to savepoint save2}
+step "s1_reindex_p" {reindex table gtt_on_commit_preserve_row}
+step "s1_reindex_i_p" {reindex index gtt_on_commit_preserve_row_pkey}
+step "s1_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s1_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s1_analyze_c" {analyze gtt_test_createindex}
+teardown
+{
+  TRUNCATE gtt_on_commit_delete_row RESTART IDENTITY;
+  TRUNCATE gtt_on_commit_preserve_row RESTART IDENTITY;
+}
+
+# Session 2
+session "s2"
+step "s2_begin" {begin}
+step "s2_commit" {commit}
+step "s2_rollback" {rollback}
+step "s2_insert_p" {insert into gtt_on_commit_preserve_row (b) values('test10')}
+step "s2_select_p" {select a,b from gtt_on_commit_preserve_row order by a,b}
+step "s2_insert_d" {insert into gtt_on_commit_delete_row (b) values('test1')}
+step "s2_truncate_p" {truncate gtt_on_commit_preserve_row}
+step "s2_update_p" {update gtt_on_commit_preserve_row set b = 'update'}
+step "s2_lock_p" {LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE}
+step "s2_save_1" {SAVEPOINT save1}
+step "s2_save_2" {SAVEPOINT save2}
+step "s2_save_3" {SAVEPOINT save3}
+step "s2_rollback_to_save_2" {rollback to savepoint save2}
+step "s2_reindex_p" {reindex table gtt_on_commit_preserve_row}
+step "s2_reindex_i_p" {reindex index gtt_on_commit_preserve_row_pkey}
+step "s2_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s2_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s2_analyze_c" {analyze gtt_test_createindex}
+teardown
+{
+  TRUNCATE gtt_on_commit_delete_row RESTART IDENTITY;
+  TRUNCATE gtt_on_commit_preserve_row RESTART IDENTITY;
+}
+
+session "s3"
+step "s3_create_c" {create unique index idx_temp_table_a on gtt_test_createindex(a)}
+step "s3_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s3_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s3_analyze_c" {analyze gtt_test_createindex}
+
+
+#
+# test on commit delete temp table
+#
+
+# test update empty temp table
+permutation "s1_update_d"
+# test insert into temp table
+permutation "s1_select_d" "s1_insert_d" "s1_select_d"
+# test temp table in transaction(commit)
+permutation "s1_select_d" "s1_begin"    "s1_insert_d" "s1_select_d"   "s1_commit"   "s1_select_d" 
+# test temp table in transaction(rollback)
+permutation "s1_select_d" "s1_begin"    "s1_insert_d" "s1_select_d"   "s1_rollback" "s1_select_d" 
+# test truncate
+permutation "s1_select_d" "s1_insert_d" "s1_select_d" "s1_truncate_d" "s1_select_d"
+# test truncate in transaction block
+permutation "s1_select_d" "s1_insert_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_truncate_d" "s1_select_d"   "s1_insert_d" "s1_select_d" "s1_commit"   "s1_select_d" 
+permutation "s1_select_d" "s1_insert_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_truncate_d" "s1_select_d"   "s1_insert_d" "s1_select_d" "s1_rollback" "s1_select_d"
+# test temp table with subtransaction or savepoint
+permutation "s1_insert_d" "s1_select_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_save_1"     "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_2"   "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_3" "s1_rollback_to_save_2" "s1_select_d" "s1_insert_d" "s1_select_d" "s1_commit" "s1_select_d"
+permutation "s1_insert_d" "s1_select_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_save_1"     "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_2"   "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_3" "s1_rollback_to_save_2" "s1_select_d" "s1_insert_d" "s1_select_d" "s1_rollback" "s1_select_d"
+
+#
+# test on commit preserve table
+#
+
+# same as test on commit delete temp table
+permutation "s2_update_p"
+permutation "s2_select_p" "s2_insert_p" "s2_select_p"
+permutation "s2_select_p" "s2_begin"    "s2_insert_p" "s2_select_p"   "s2_commit"   "s2_select_p"
+permutation "s2_select_p" "s2_begin"    "s2_insert_p" "s2_select_p"   "s2_rollback" "s2_select_p"
+permutation "s2_select_p" "s2_insert_p" "s2_select_p" "s2_truncate_p" "s2_select_p"
+permutation "s2_select_p" "s2_insert_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_truncate_p" "s2_select_p"   "s2_insert_p" "s2_select_p" "s2_commit"   "s2_select_p" 
+permutation "s2_select_p" "s2_insert_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_truncate_p" "s2_select_p"   "s2_insert_p" "s2_select_p" "s2_rollback" "s2_select_p" 
+permutation "s2_insert_p" "s2_select_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_save_1"     "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_2"   "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_3" "s2_rollback_to_save_2" "s2_select_p" "s2_insert_p" "s2_select_p" "s2_commit" "s2_select_p"
+permutation "s2_insert_p" "s2_select_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_save_1"     "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_2"   "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_3" "s2_rollback_to_save_2" "s2_select_p" "s2_insert_p" "s2_select_p" "s2_rollback" "s2_select_p"
+
+#
+# test concurrent operation on temp table
+#
+
+#  test concurrent read
+permutation "s1_insert_p" "s2_insert_p" "s1_select_p" "s2_select_p" 
+#  test concurrent truncate
+permutation "s1_begin" "s2_begin"    "s1_insert_p" "s2_insert_p"   "s1_truncate_p" "s2_truncate_p"  "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+permutation "s1_begin" "s1_insert_d" "s2_insert_d" "s1_truncate_d" "s2_insert_d"   "s1_commit" 
+#  test concurrent reindex table
+permutation "s1_begin" "s2_begin" "s1_insert_p" "s2_insert_p" "s1_reindex_p"   "s2_reindex_p"   "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+#  test concurrent reindex index
+permutation "s1_begin" "s2_begin" "s1_insert_p" "s2_insert_p" "s1_reindex_i_p" "s2_reindex_i_p" "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+
+# test create index
+permutation "s2_insert_c" "s3_create_c" "s3_insert_c" "s1_insert_c" "s1_analyze_c" "s2_analyze_c" "s3_analyze_c" "s1_select_c" "s2_select_c" "s3_select_c"
+
+# test lock gtt
+permutation "s1_begin" "s2_begin" "s1_lock_p" "s2_lock_p" "s1_truncate_p" "s2_truncate_p" "s1_insert_p" "s2_insert_p" "s1_commit" "s2_commit" "s1_select_p" "s2_select_p"
diff --git a/src/test/isolation/specscanner.l b/src/test/isolation/specscanner.l
index d9fa6a5b54a..697db975479 100644
--- a/src/test/isolation/specscanner.l
+++ b/src/test/isolation/specscanner.l
@@ -67,6 +67,8 @@ session			{ return SESSION; }
 setup			{ return SETUP; }
 step			{ return STEP; }
 teardown		{ return TEARDOWN; }
+initialize		 { return INITIALIZE; }
+destroy			 { return DESTROY; }
 
  /* Whitespace and comments */
 [\n]			{ yyline++; }
diff --git a/src/test/regress/expected/global_temporary_table.out b/src/test/regress/expected/global_temporary_table.out
new file mode 100644
index 00000000000..5adc58d77e1
--- /dev/null
+++ b/src/test/regress/expected/global_temporary_table.out
@@ -0,0 +1,574 @@
+--
+-- GLobal emparary table test case 
+--
+CREATE SCHEMA IF NOT EXISTS global_temporary_table;
+set search_path=global_temporary_table,sys;
+--
+--  test create global temp table basic syntax
+--
+create global temp table gtt_on_commit_default(a int primary key, b text);
+create global temp table gtt_on_commit_delete(a int primary key, b text) on commit delete rows;
+create global temp table gtt_on_commit_delete2(n int) with (on_commit_delete_rows='true');
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit PRESERVE rows;
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt_test_createindex(c0 tsvector,c1 varchar(100), c2 int);
+create global temp table gtt_test_alter(b text) with(on_commit_delete_rows=true);
+--
+-- test DML on global temp table
+--
+-- update empty temp table
+update gtt_on_commit_delete set b ='test';
+begin;
+insert into gtt_on_commit_delete values (1);
+update gtt_on_commit_delete set b ='test';
+-- should 1 row
+select * from gtt_on_commit_delete;
+ a |  b   
+---+------
+ 1 | test
+(1 row)
+
+commit;
+-- data delete after transaction commit
+-- should 0 row
+select * from gtt_on_commit_delete;
+ a | b 
+---+---
+(0 rows)
+
+-- update empty temp table
+update gtt_on_commit_preserve set b ='test';
+insert into gtt_on_commit_preserve values (2);
+begin;
+insert into gtt_on_commit_preserve values (3);
+update gtt_on_commit_preserve set b ='test';
+-- should 2 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 2 | test
+ 3 | test
+(2 rows)
+
+delete from gtt_on_commit_preserve where a=2;
+commit;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 3 | test
+(1 row)
+
+begin;
+insert into gtt_on_commit_preserve values (4);
+-- temp table support truncate;
+truncate gtt_on_commit_preserve;
+select * from gtt_on_commit_preserve order by a;
+ a | b 
+---+---
+(0 rows)
+
+rollback;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 3 | test
+(1 row)
+
+--
+-- test unsupported global temp partition table
+--
+-- should fail
+CREATE global temp TABLE global_temp_partition_01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+on commit delete rows;
+ERROR:  Only support global temporary regular table.
+CREATE TABLE regular_partition_01_2019 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+);
+CREATE TABLE regular_partition01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time);
+-- should fail
+CREATE global temp TABLE temp_partition01_2018
+PARTITION OF regular_partition01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+ERROR:  Not support global temporary partition table or inherit table.
+CREATE global temp TABLE global_temp_partition_01_2021 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+)on commit delete rows;
+-- should fail
+ALTER TABLE regular_partition01 ATTACH PARTITION global_temp_partition_01_2021 FOR VALUES FROM ('2021-01-01 00:00:00') TO ('2022-01-01 00:00:00');
+ERROR:  cannot attach a global temporary relation as partition of permanent relation "regular_partition01"
+--
+-- test unsupported inherit table
+--
+create table 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 inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent);
+ERROR:  Not support global temporary partition table or inherit table.
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent_global_temp) on commit delete rows;
+ERROR:  Not support global temporary partition table or inherit table.
+--
+-- test DDL on global temp table
+--
+create index idx_gtt_test_alter_b on gtt_test_alter (b);
+insert into gtt_test_alter values('test');
+alter table gtt_test_alter alter b type varchar;
+create index gtt_idx_1 on gtt_test_createindex using gin (c0);
+create index gtt_idx_2 on gtt_test_createindex using gist (c0);
+create index gtt_idx_3 on gtt_test_createindex using hash (c2);
+alter table gtt_test_rename rename to gtt_test_new;
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+create index CONCURRENTLY idx_b on gtt_on_commit_default (b);
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+insert into gtt_on_commit_default values(1,'test');
+create global temp table gtt_test_alter1 (a int primary key,b text);
+alter table gtt_test_alter1 alter a type varchar;
+-- should fail
+alter table gtt_on_commit_default alter a type varchar;
+ERROR:  Only support alter global temporary table in an empty context.
+HINT:  Please create a new connection and execute ALTER TABLE on the new connection.
+-- should fail
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+ERROR:  not support cluster global temporary table yet
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- should fail
+alter table gtt_on_commit_default SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temporary table
+-- should fail
+alter table gtt_on_commit_default set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table
+-- should fail
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- should fail
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- should fail
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit drop;
+ERROR:  global temporary table not support on commit drop clause
+-- should fail
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  could not create global temporary table with on commit and with clause at same time
+-- should fail
+CREATE MATERIALIZED VIEW mv_gtt_on_commit_default as select * from gtt_on_commit_default;
+ERROR:  materialized views must not use global temporary tables or views
+-- test create table as select
+CREATE GLOBAL TEMPORARY TABLE test_create_table_as AS SELECT 1 AS a;
+-- test copy stmt
+create global temp table gtt_copytest (
+        c1 int,
+        "col with , comma" text,
+        "col with "" quote"  int);
+copy gtt_copytest from stdin csv header;
+select count(*) from gtt_copytest;
+ count 
+-------
+     2
+(1 row)
+
+--
+-- test foreign key dependencies for global temp table
+--
+CREATE global temp TABLE temp_products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+CREATE global temp TABLE temp_orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+)on commit delete rows;
+-- should fail
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+CREATE global temp TABLE temp_orders_2 (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products(product_no),
+    quantity integer
+);
+--should fail
+insert into temp_orders values(1,1,1);
+ERROR:  insert or update on table "temp_orders" violates foreign key constraint "temp_orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "temp_products".
+insert into temp_products values(1,'test',1.0);
+begin;
+insert into temp_orders values(1,1,1);
+commit;
+-- should 1 row
+select count(*) from temp_products;
+ count 
+-------
+     1
+(1 row)
+
+-- should 0 row
+select count(*) from temp_orders;
+ count 
+-------
+     0
+(1 row)
+
+--
+-- test sequence on global temp table
+--
+create global temp table global_temp_with_serial (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+-- support insert data to temp table in read only transaction
+insert into global_temp_with_serial (b) values(1);
+select * from global_temp_with_serial;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_delete(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_preserve(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table temp_table_with_sequence_oncommit_delete add c2 int default nextval('seq_1');
+alter table temp_table_with_sequence_oncommit_preserve add c2 int default nextval('seq_1');
+begin;
+insert into temp_table_with_sequence_oncommit_delete (c1)values(1);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(2);
+insert into temp_table_with_sequence_oncommit_delete (c1)values(3);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(4);
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  3 |  3
+(2 rows)
+
+commit;
+-- should 0 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_preserve order by c1;
+ c1 | c2 
+----+----
+  2 |  2
+  4 |  4
+(2 rows)
+
+--
+-- test statistics on temp table
+--
+create global temp table temp_table_test_statistics(a int);
+insert into temp_table_test_statistics values(generate_series(1,100000));
+create index idx_test_1 on temp_table_test_statistics (a);
+create index idx_test_2 on temp_table_test_statistics((a*10));
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+                   QUERY PLAN                   
+------------------------------------------------
+ Bitmap Heap Scan on temp_table_test_statistics
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_test_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+                   QUERY PLAN                   
+------------------------------------------------
+ Bitmap Heap Scan on temp_table_test_statistics
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_test_2
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+-- test statistic for whole row
+explain (costs off) select count(*) from temp_table_test_statistics group by temp_table_test_statistics;
+                  QUERY PLAN                  
+----------------------------------------------
+ HashAggregate
+   Group Key: temp_table_test_statistics.*
+   ->  Seq Scan on temp_table_test_statistics
+(3 rows)
+
+-- test statistic for system column
+explain (costs off) select count(*) from temp_table_test_statistics group by tableoid;
+                  QUERY PLAN                  
+----------------------------------------------
+ HashAggregate
+   Group Key: tableoid
+   ->  Seq Scan on temp_table_test_statistics
+(3 rows)
+
+analyze temp_table_test_statistics;
+-- indexscan by idx_test_1
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Index Only Scan using idx_test_1 on temp_table_test_statistics
+   Index Cond: (a = 200000)
+(2 rows)
+
+-- indexscan by idx_test_2
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+                        QUERY PLAN                         
+-----------------------------------------------------------
+ Index Scan using idx_test_2 on temp_table_test_statistics
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+explain (costs off) select count(*) from temp_table_test_statistics group by temp_table_test_statistics;
+                  QUERY PLAN                  
+----------------------------------------------
+ HashAggregate
+   Group Key: temp_table_test_statistics.*
+   ->  Seq Scan on temp_table_test_statistics
+(3 rows)
+
+explain (costs off) select count(*) from temp_table_test_statistics group by tableoid;
+                  QUERY PLAN                  
+----------------------------------------------
+ HashAggregate
+   Group Key: tableoid
+   ->  Seq Scan on temp_table_test_statistics
+(3 rows)
+
+--
+-- test temp table with toast table
+--
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |                0 |        483328 |           98304 |                 581632
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            32768 |                  |         32768 |               0 |                  32768
+(3 rows)
+
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |             8192 |        499712 |          106496 |                 606208
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            40960 |                  |         40960 |               0 |                  40960
+(3 rows)
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,10),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',10);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |             8192 |                0 |         16384 |           32768 |                  49152
+ idx_gtt_t_kenyon_1 |            16384 |                  |         16384 |               0 |                  16384
+ idx_gtt_t_kenyon_2 |            16384 |                  |         16384 |               0 |                  16384
+(3 rows)
+
+rollback;
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |             8192 |        499712 |          106496 |                 606208
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            40960 |                  |         40960 |               0 |                  40960
+(3 rows)
+
+-- test analyze/vacuum on global temp table
+ANALYZE gtt_t_kenyon;
+VACUUM gtt_t_kenyon;
+--
+-- test global temp table system view
+--
+create global temp table temp_table_test_systemview(a int primary key, b text) on commit PRESERVE rows;
+-- should empty, storage not initialized
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+ tablename 
+-----------
+(0 rows)
+
+-- should empty, storage not initialized
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into temp_table_test_systemview values(generate_series(1,10000),'test');
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+         tablename          
+----------------------------
+ temp_table_test_systemview
+(1 row)
+
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+       schemaname       |            tablename            | relpages | reltuples | relallvisible 
+------------------------+---------------------------------+----------+-----------+---------------
+ global_temporary_table | temp_table_test_systemview      |        0 |         0 |             0
+ global_temporary_table | temp_table_test_systemview_pkey |        1 |         0 |             0
+(2 rows)
+
+-- should empty
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+analyze temp_table_test_systemview;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+       schemaname       |            tablename            | relpages | reltuples | relallvisible 
+------------------------+---------------------------------+----------+-----------+---------------
+ global_temporary_table | temp_table_test_systemview      |       55 |     10000 |             0
+ global_temporary_table | temp_table_test_systemview_pkey |       30 |     10000 |             0
+(2 rows)
+
+-- get data after analyze;
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+       schemaname       |         tablename          | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------------------+----------------------------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ global_temporary_table | temp_table_test_systemview | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ global_temporary_table | temp_table_test_systemview | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+-- get all object info in current schema
+select relname ,relkind, relpersistence, reloptions from pg_class c, pg_namespace n where c.relnamespace = n.oid and n.nspname = 'global_temporary_table' order by relkind,relpersistence,relname;
+                     relname                     | relkind | relpersistence |          reloptions           
+-------------------------------------------------+---------+----------------+-------------------------------
+ global_temp_partition_01_2021_id_seq            | S       | g              | {on_commit_delete_rows=false}
+ global_temp_with_serial_a_seq                   | S       | g              | {on_commit_delete_rows=false}
+ regular_partition01_id_seq                      | S       | p              | 
+ regular_partition_01_2019_id_seq                | S       | p              | 
+ seq_1                                           | S       | p              | 
+ gtt_idx_1                                       | i       | g              | 
+ gtt_idx_2                                       | i       | g              | 
+ gtt_idx_3                                       | i       | g              | 
+ gtt_on_commit_default_pkey                      | i       | g              | 
+ gtt_on_commit_delete_pkey                       | i       | g              | 
+ gtt_on_commit_preserve_pkey                     | i       | g              | 
+ gtt_test_alter1_pkey                            | i       | g              | 
+ gtt_test_rename_pkey                            | i       | g              | 
+ idx_b                                           | i       | g              | 
+ idx_gtt_t_kenyon_1                              | i       | g              | 
+ idx_gtt_t_kenyon_2                              | i       | g              | 
+ idx_gtt_test_alter_b                            | i       | g              | 
+ idx_test_1                                      | i       | g              | 
+ idx_test_2                                      | i       | g              | 
+ products_pkey                                   | i       | g              | 
+ temp_orders_2_pkey                              | i       | g              | 
+ temp_orders_pkey                                | i       | g              | 
+ temp_products_pkey                              | i       | g              | 
+ temp_table_test_systemview_pkey                 | i       | g              | 
+ temp_table_with_sequence_oncommit_delete_pkey   | i       | g              | 
+ temp_table_with_sequence_oncommit_preserve_pkey | i       | g              | 
+ regular_partition01                             | p       | p              | 
+ global_temp_partition_01_2021                   | r       | g              | {on_commit_delete_rows=true}
+ global_temp_with_serial                         | r       | g              | {on_commit_delete_rows=false}
+ gtt_copytest                                    | r       | g              | {on_commit_delete_rows=false}
+ gtt_on_commit_default                           | r       | g              | {on_commit_delete_rows=false}
+ gtt_on_commit_delete                            | r       | g              | {on_commit_delete_rows=true}
+ gtt_on_commit_delete2                           | r       | g              | {on_commit_delete_rows=true}
+ gtt_on_commit_preserve                          | r       | g              | {on_commit_delete_rows=false}
+ gtt_t_kenyon                                    | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_alter                                  | r       | g              | {on_commit_delete_rows=true}
+ gtt_test_alter1                                 | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_createindex                            | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_new                                    | r       | g              | {on_commit_delete_rows=false}
+ inherits_parent_global_temp                     | r       | g              | {on_commit_delete_rows=true}
+ products                                        | r       | g              | {on_commit_delete_rows=false}
+ temp_orders                                     | r       | g              | {on_commit_delete_rows=true}
+ temp_orders_2                                   | r       | g              | {on_commit_delete_rows=false}
+ temp_products                                   | r       | g              | {on_commit_delete_rows=false}
+ temp_table_test_statistics                      | r       | g              | {on_commit_delete_rows=false}
+ temp_table_test_systemview                      | r       | g              | {on_commit_delete_rows=false}
+ temp_table_with_sequence_oncommit_delete        | r       | g              | {on_commit_delete_rows=true}
+ temp_table_with_sequence_oncommit_preserve      | r       | g              | {on_commit_delete_rows=false}
+ test_create_table_as                            | r       | g              | {on_commit_delete_rows=false}
+ foo                                             | r       | p              | 
+ inherits_parent                                 | r       | p              | 
+ regular_partition_01_2019                       | r       | p              | 
+(52 rows)
+
+reset search_path;
+drop schema global_temporary_table cascade;
+NOTICE:  drop cascades to 27 other objects
+DETAIL:  drop cascades to table global_temporary_table.gtt_on_commit_default
+drop cascades to table global_temporary_table.gtt_on_commit_delete
+drop cascades to table global_temporary_table.gtt_on_commit_delete2
+drop cascades to table global_temporary_table.gtt_on_commit_preserve
+drop cascades to table global_temporary_table.gtt_test_new
+drop cascades to table global_temporary_table.gtt_test_createindex
+drop cascades to table global_temporary_table.gtt_test_alter
+drop cascades to table global_temporary_table.regular_partition_01_2019
+drop cascades to table global_temporary_table.regular_partition01
+drop cascades to table global_temporary_table.global_temp_partition_01_2021
+drop cascades to table global_temporary_table.inherits_parent
+drop cascades to table global_temporary_table.inherits_parent_global_temp
+drop cascades to table global_temporary_table.gtt_test_alter1
+drop cascades to table global_temporary_table.foo
+drop cascades to table global_temporary_table.test_create_table_as
+drop cascades to table global_temporary_table.gtt_copytest
+drop cascades to table global_temporary_table.temp_products
+drop cascades to table global_temporary_table.products
+drop cascades to table global_temporary_table.temp_orders
+drop cascades to table global_temporary_table.temp_orders_2
+drop cascades to table global_temporary_table.global_temp_with_serial
+drop cascades to sequence global_temporary_table.seq_1
+drop cascades to table global_temporary_table.temp_table_with_sequence_oncommit_delete
+drop cascades to table global_temporary_table.temp_table_with_sequence_oncommit_preserve
+drop cascades to table global_temporary_table.temp_table_test_statistics
+drop cascades to table global_temporary_table.gtt_t_kenyon
+drop cascades to table global_temporary_table.temp_table_test_systemview
+-- should empty
+select * from pg_list_gtt_relfrozenxids();
+ pid | relfrozenxid 
+-----+--------------
+(0 rows)
+
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2fa00a3c29a..e0001bc3448 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1359,6 +1359,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 017e962fed2..e633b16076e 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -131,3 +131,6 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: global_temporary_table
diff --git a/src/test/regress/sql/global_temporary_table.sql b/src/test/regress/sql/global_temporary_table.sql
new file mode 100644
index 00000000000..5185c3b15af
--- /dev/null
+++ b/src/test/regress/sql/global_temporary_table.sql
@@ -0,0 +1,313 @@
+--
+-- GLobal emparary table test case 
+--
+
+CREATE SCHEMA IF NOT EXISTS global_temporary_table;
+set search_path=global_temporary_table,sys;
+
+--
+--  test create global temp table basic syntax
+--
+create global temp table gtt_on_commit_default(a int primary key, b text);
+create global temp table gtt_on_commit_delete(a int primary key, b text) on commit delete rows;
+create global temp table gtt_on_commit_delete2(n int) with (on_commit_delete_rows='true');
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit PRESERVE rows;
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt_test_createindex(c0 tsvector,c1 varchar(100), c2 int);
+create global temp table gtt_test_alter(b text) with(on_commit_delete_rows=true);
+--
+-- test DML on global temp table
+--
+
+-- update empty temp table
+update gtt_on_commit_delete set b ='test';
+begin;
+insert into gtt_on_commit_delete values (1);
+update gtt_on_commit_delete set b ='test';
+-- should 1 row
+select * from gtt_on_commit_delete;
+commit;
+-- data delete after transaction commit
+-- should 0 row
+select * from gtt_on_commit_delete;
+
+-- update empty temp table
+update gtt_on_commit_preserve set b ='test';
+insert into gtt_on_commit_preserve values (2);
+begin;
+insert into gtt_on_commit_preserve values (3);
+update gtt_on_commit_preserve set b ='test';
+-- should 2 row
+select * from gtt_on_commit_preserve order by a;
+delete from gtt_on_commit_preserve where a=2;
+commit;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+
+begin;
+insert into gtt_on_commit_preserve values (4);
+-- temp table support truncate;
+truncate gtt_on_commit_preserve;
+select * from gtt_on_commit_preserve order by a;
+rollback;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+
+--
+-- test unsupported global temp partition table
+--
+
+-- should fail
+CREATE global temp TABLE global_temp_partition_01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+on commit delete rows;
+
+CREATE TABLE regular_partition_01_2019 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+);
+
+CREATE TABLE regular_partition01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time);
+
+-- should fail
+CREATE global temp TABLE temp_partition01_2018
+PARTITION OF regular_partition01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE global_temp_partition_01_2021 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+)on commit delete rows;
+
+-- should fail
+ALTER TABLE regular_partition01 ATTACH PARTITION global_temp_partition_01_2021 FOR VALUES FROM ('2021-01-01 00:00:00') TO ('2022-01-01 00:00:00');
+
+--
+-- test unsupported inherit table
+--
+create table 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 inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent);
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent_global_temp) on commit delete rows;
+
+--
+-- test DDL on global temp table
+--
+create index idx_gtt_test_alter_b on gtt_test_alter (b);
+insert into gtt_test_alter values('test');
+alter table gtt_test_alter alter b type varchar;
+create index gtt_idx_1 on gtt_test_createindex using gin (c0);
+create index gtt_idx_2 on gtt_test_createindex using gist (c0);
+create index gtt_idx_3 on gtt_test_createindex using hash (c2);
+alter table gtt_test_rename rename to gtt_test_new;
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+create index CONCURRENTLY idx_b on gtt_on_commit_default (b);
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+insert into gtt_on_commit_default values(1,'test');
+create global temp table gtt_test_alter1 (a int primary key,b text);
+alter table gtt_test_alter1 alter a type varchar;
+-- should fail
+alter table gtt_on_commit_default alter a type varchar;
+-- should fail
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) on commit delete rows;
+-- should fail
+alter table gtt_on_commit_default SET TABLESPACE pg_default;
+-- should fail
+alter table gtt_on_commit_default set ( on_commit_delete_rows='true');
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) with(on_commit_delete_rows=true);
+-- should fail
+create or replace global temp view gtt_v as select 5;
+create table foo();
+-- should fail
+alter table foo set (on_commit_delete_rows='true');
+-- should fail
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit drop;
+-- should fail
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+-- should fail
+CREATE MATERIALIZED VIEW mv_gtt_on_commit_default as select * from gtt_on_commit_default;
+-- test create table as select
+CREATE GLOBAL TEMPORARY TABLE test_create_table_as AS SELECT 1 AS a;
+-- test copy stmt
+create global temp table gtt_copytest (
+        c1 int,
+        "col with , comma" text,
+        "col with "" quote"  int);
+
+copy gtt_copytest from stdin csv header;
+this is just a line full of junk that would error out if parsed
+1,a,1
+2,b,2
+\.
+select count(*) from gtt_copytest;
+
+--
+-- test foreign key dependencies for global temp table
+--
+CREATE global temp TABLE temp_products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+CREATE global temp TABLE temp_orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+)on commit delete rows;
+
+-- should fail
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+);
+
+CREATE global temp TABLE temp_orders_2 (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products(product_no),
+    quantity integer
+);
+
+--should fail
+insert into temp_orders values(1,1,1);
+
+insert into temp_products values(1,'test',1.0);
+begin;
+insert into temp_orders values(1,1,1);
+commit;
+-- should 1 row
+select count(*) from temp_products;
+-- should 0 row
+select count(*) from temp_orders;
+
+--
+-- test sequence on global temp table
+--
+create global temp table global_temp_with_serial (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+-- support insert data to temp table in read only transaction
+insert into global_temp_with_serial (b) values(1);
+select * from global_temp_with_serial;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_delete(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_preserve(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table temp_table_with_sequence_oncommit_delete add c2 int default nextval('seq_1');
+alter table temp_table_with_sequence_oncommit_preserve add c2 int default nextval('seq_1');
+begin;
+insert into temp_table_with_sequence_oncommit_delete (c1)values(1);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(2);
+insert into temp_table_with_sequence_oncommit_delete (c1)values(3);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(4);
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+commit;
+-- should 0 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_preserve order by c1;
+
+--
+-- test statistics on temp table
+--
+create global temp table temp_table_test_statistics(a int);
+insert into temp_table_test_statistics values(generate_series(1,100000));
+create index idx_test_1 on temp_table_test_statistics (a);
+create index idx_test_2 on temp_table_test_statistics((a*10));
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+-- test statistic for whole row
+explain (costs off) select count(*) from temp_table_test_statistics group by temp_table_test_statistics;
+-- test statistic for system column
+explain (costs off) select count(*) from temp_table_test_statistics group by tableoid;
+analyze temp_table_test_statistics;
+-- indexscan by idx_test_1
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+-- indexscan by idx_test_2
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+explain (costs off) select count(*) from temp_table_test_statistics group by temp_table_test_statistics;
+explain (costs off) select count(*) from temp_table_test_statistics group by tableoid;
+
+--
+-- test temp table with toast table
+--
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,10),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',10);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+rollback;
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+
+-- test analyze/vacuum on global temp table
+ANALYZE gtt_t_kenyon;
+VACUUM gtt_t_kenyon;
+
+--
+-- test global temp table system view
+--
+create global temp table temp_table_test_systemview(a int primary key, b text) on commit PRESERVE rows;
+-- should empty, storage not initialized
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+-- should empty, storage not initialized
+select count(*) from pg_list_gtt_relfrozenxids();
+insert into temp_table_test_systemview values(generate_series(1,10000),'test');
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+select count(*) from pg_list_gtt_relfrozenxids();
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+-- should empty
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+analyze temp_table_test_systemview;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+-- get data after analyze;
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+
+-- get all object info in current schema
+select relname ,relkind, relpersistence, reloptions from pg_class c, pg_namespace n where c.relnamespace = n.oid and n.nspname = 'global_temporary_table' order by relkind,relpersistence,relname;
+
+reset search_path;
+drop schema global_temporary_table cascade;
+-- should empty
+select * from pg_list_gtt_relfrozenxids();
+
-- 
2.30.1 (Apple Git-130)

0002-gtt-v62-doc.patchapplication/octet-stream; name=0002-gtt-v62-doc.patchDownload
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 473a0a4aeb..e510bde8ac 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -169,32 +169,67 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     <listitem>
      <para>
       If specified, the table is created as a temporary table.
-      Temporary tables are automatically dropped at the end of a
-      session, or optionally at the end of the current transaction
-      (see <literal>ON COMMIT</literal> below).  The default
-      search_path includes the temporary schema first and so identically
-      named existing permanent tables are not chosen for new plans
+      Optionally, <literal>GLOBAL</literal> or <literal>LOCAL</literal>
+      can be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
+      They represent two types of temporary tables supported by <productname>PostgreSQL</productname>:
+      global temporary table and local temporary table. Without specified
+      GLOBAL or LOCAL, a local temporary table is created by default.
+     </para>
+
+    <para>
+     Both types of temporary tables’ data are truncated at the
+     end of a session or optionally at the end of the current transaction.
+     (see <literal>ON COMMIT</literal> below). For global temporary table,
+     its schema is reserved and reused by future sessions or transactions.
+     For local temporary table, both its data and its schema are dropped.
+    </para>
+
+    <variablelist>
+     <varlistentry>
+      <term><literal>Global Temporary Table</literal></term>
+      <listitem>
+       <para>
+        Global temporary table are defined just once and automatically exist
+        (starting with empty contents) in every session that needs them.
+        The schema definition of temporary tables is persistent and shared among sessions.
+        However, the data in temporary tables are kept private to sessions themselves,
+        even though they use same name and same schema.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>Local Temporary Table</literal></term>
+     <listitem>
+     <para>
+      Local temporary table are automatically dropped at the end of a
+      session (include schema and data). Future sessions need to create
+      their own temporary tables when they are used.
+     </para>
+     <para>
+      The default search_path includes the temporary schema first and so
+      identically named existing permanent tables are not chosen for new plans
       while the temporary table exists, unless they are referenced
       with schema-qualified names. Any indexes created on a temporary
       table are automatically temporary as well.
      </para>
+     </listitem>
+     </varlistentry>
+    </variablelist>
 
-     <para>
-      The <link linkend="autovacuum">autovacuum daemon</link> cannot
-      access and therefore cannot vacuum or analyze temporary tables.
-      For this reason, appropriate vacuum and analyze operations should be
-      performed via session SQL commands.  For example, if a temporary
-      table is going to be used in complex queries, it is wise to run
-      <command>ANALYZE</command> on the temporary table after it is populated.
-     </para>
+    <para>
+     The <link linkend="autovacuum">autovacuum daemon</link> cannot
+     access and therefore cannot vacuum or analyze temporary tables.
+     For this reason, appropriate vacuum and analyze operations should be
+     performed via session SQL commands.  For example, if a temporary
+     table is going to be used in complex queries, it is wise to run
+     <command>ANALYZE</command> on the temporary table after it is populated.
+    </para>
+    <para>
+     The Temporary table resembles the SQL standard, but has some differences.
+     see <xref linkend="sql-createtable-compatibility"/> below.
+    </para>
 
-     <para>
-      Optionally, <literal>GLOBAL</literal> or <literal>LOCAL</literal>
-      can be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
-      This presently makes no difference in <productname>PostgreSQL</productname>
-      and is deprecated; see
-      <xref linkend="sql-createtable-compatibility"/> below.
-     </para>
     </listitem>
    </varlistentry>
 
@@ -2133,13 +2168,17 @@ CREATE TABLE cities_partdef
    <title>Temporary Tables</title>
 
    <para>
-    Although the syntax of <literal>CREATE TEMPORARY TABLE</literal>
-    resembles that of the SQL standard, the effect is not the same.  In the
-    standard,
-    temporary tables are defined just once and automatically exist (starting
-    with empty contents) in every session that needs them.
-    <productname>PostgreSQL</productname> instead
-    requires each session to issue its own <literal>CREATE TEMPORARY
+    Although the syntax of <literal>CREATE GLOBAL/LOCAL TEMPORARY TABLE</literal>
+    resembles that of the SQL standard, the effect is not the same.
+    The global temporary table follows the SQL standards while local temporary
+    table does not.
+   </para>
+
+   <para>
+    First, in the standard, both global and local temporary tables are defined just
+    once and automatically exist (starting with empty contents) in every session
+    that needs them. For local temporary tables, <productname>PostgreSQL</productname>
+    instead requires each session to issue its own <literal>CREATE LOCAL TEMPORARY
     TABLE</literal> command for each temporary table to be used.  This allows
     different sessions to use the same temporary table name for different
     purposes, whereas the standard's approach constrains all instances of a
@@ -2147,29 +2186,14 @@ CREATE TABLE cities_partdef
    </para>
 
    <para>
-    The standard's definition of the behavior of temporary tables is
-    widely ignored.  <productname>PostgreSQL</productname>'s behavior
-    on this point is similar to that of several other SQL databases.
-   </para>
-
-   <para>
-    The SQL standard also distinguishes between global and local temporary
+    Second, the SQL standard distinguishes between global and local temporary
     tables, where a local temporary table has a separate set of contents for
     each SQL module within each session, though its definition is still shared
-    across sessions.  Since <productname>PostgreSQL</productname> does not
+    across sessions. Since <productname>PostgreSQL</productname> does not
     support SQL modules, this distinction is not relevant in
     <productname>PostgreSQL</productname>.
    </para>
 
-   <para>
-    For compatibility's sake, <productname>PostgreSQL</productname> will
-    accept the <literal>GLOBAL</literal> and <literal>LOCAL</literal> keywords
-    in a temporary table declaration, but they currently have no effect.
-    Use of these keywords is discouraged, since future versions of
-    <productname>PostgreSQL</productname> might adopt a more
-    standard-compliant interpretation of their meaning.
-   </para>
-
    <para>
     The <literal>ON COMMIT</literal> clause for temporary tables
     also resembles the SQL standard, but has some differences.
@@ -2177,7 +2201,8 @@ CREATE TABLE cities_partdef
     default behavior is <literal>ON COMMIT DELETE ROWS</literal>.  However, the
     default behavior in <productname>PostgreSQL</productname> is
     <literal>ON COMMIT PRESERVE ROWS</literal>.  The <literal>ON COMMIT
-    DROP</literal> option does not exist in SQL.
+    DROP</literal> option does not exist in SQL and is not supported by
+    global temporary table.
    </para>
   </refsect2>
 
-- 
2.30.1 (Apple Git-130)

0003-gtt-v62-implementation.patchapplication/octet-stream; name=0003-gtt-v62-implementation.patchDownload
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index bae5340111c..70739e9e92c 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -18,6 +18,7 @@
 #include "access/toast_internals.h"
 #include "access/visibilitymap.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "funcapi.h"
 #include "miscadmin.h"
 #include "storage/bufmgr.h"
@@ -226,6 +227,8 @@ verify_heapam(PG_FUNCTION_ARGS)
 	BlockNumber last_block;
 	BlockNumber nblocks;
 	const char *skip;
+	TransactionId	relfrozenxid = InvalidTransactionId;
+	MultiXactId		relminmxid = InvalidMultiXactId;
 
 	/* Check to see if caller supports us returning a tuplestore */
 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -342,6 +345,13 @@ verify_heapam(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(ctx.rel) &&
+		!gtt_storage_attached(RelationGetRelid(ctx.rel)))
+	{
+		relation_close(ctx.rel, AccessShareLock);
+		PG_RETURN_NULL();
+	}
+
 	/* Early exit if the relation is empty */
 	nblocks = RelationGetNumberOfBlocks(ctx.rel);
 	if (!nblocks)
@@ -409,9 +419,25 @@ verify_heapam(PG_FUNCTION_ARGS)
 
 	update_cached_xid_range(&ctx);
 	update_cached_mxid_range(&ctx);
-	ctx.relfrozenxid = ctx.rel->rd_rel->relfrozenxid;
+
+	if (RELATION_IS_GLOBAL_TEMP(ctx.rel))
+	{
+		get_gtt_relstats(RelationGetRelid(ctx.rel),
+						NULL,
+						NULL,
+						NULL,
+						&relfrozenxid,
+						&relminmxid);
+	}
+	else
+	{
+		relfrozenxid = ctx.rel->rd_rel->relfrozenxid;
+		relminmxid = ctx.rel->rd_rel->relminmxid;
+	}
+
+	ctx.relfrozenxid = relfrozenxid;
 	ctx.relfrozenfxid = FullTransactionIdFromXidAndCtx(ctx.relfrozenxid, &ctx);
-	ctx.relminmxid = ctx.rel->rd_rel->relminmxid;
+	ctx.relminmxid = relminmxid;
 
 	if (TransactionIdIsNormal(ctx.relfrozenxid))
 		ctx.oldest_xid = ctx.relfrozenxid;
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index b5602f53233..21b2d2a9527 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -159,6 +159,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * In order to avoid consistency problems, the global temporary table
+	 * uses ShareUpdateExclusiveLock.
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temporary table on commit options",
+			RELOPT_KIND_HEAP,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},
 	/* list terminator */
 	{{NULL}}
 };
@@ -1834,6 +1847,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 43ba03b6eb9..49f1052fdb1 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1023,7 +1023,7 @@ gistproperty(Oid index_oid, int attno,
 XLogRecPtr
 gistGetFakeLSN(Relation rel)
 {
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	if (RELATION_IS_TEMP(rel))
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index eb3810494f2..cbd22909582 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -151,7 +151,7 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	if (!RELATION_IS_TEMP(index))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index ec234a5e595..eb9f7f0eb48 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -52,6 +52,7 @@
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/atomics.h"
@@ -5844,6 +5845,19 @@ heap_abort_speculative(Relation relation, ItemPointer tid)
 	BlockNumber block;
 	Buffer		buffer;
 	TransactionId prune_xid;
+	TransactionId relfrozenxid = InvalidTransactionId;
+
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		get_gtt_relstats(RelationGetRelid(relation),
+						NULL,
+						NULL,
+						NULL,
+						&relfrozenxid,
+						NULL);
+	}
+	else
+		relfrozenxid = relation->rd_rel->relfrozenxid;
 
 	Assert(ItemPointerIsValid(tid));
 
@@ -5896,8 +5910,8 @@ heap_abort_speculative(Relation relation, ItemPointer tid)
 	 * TransactionXmin, so there's no race here).
 	 */
 	Assert(TransactionIdIsValid(TransactionXmin));
-	if (TransactionIdPrecedes(TransactionXmin, relation->rd_rel->relfrozenxid))
-		prune_xid = relation->rd_rel->relfrozenxid;
+	if (TransactionIdPrecedes(TransactionXmin, relfrozenxid))
+		prune_xid = relfrozenxid;
 	else
 		prune_xid = TransactionXmin;
 	PageSetPrunable(page, prune_xid);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 9befe012a9e..26fce0c4b83 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -593,7 +593,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
@@ -645,7 +645,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(RelationGetSmgr(rel), dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index a00947ea1c6..5a45d779e09 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -63,6 +63,7 @@
 #include "access/xlog.h"
 #include "catalog/index.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -507,6 +508,25 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 	TransactionId OldestXmin;
 	TransactionId FreezeLimit;
 	MultiXactId MultiXactCutoff;
+	TransactionId	relfrozenxid = InvalidTransactionId;
+	MultiXactId		relminmxid = InvalidMultiXactId;
+	double			reltuples = 0;
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		get_gtt_relstats(RelationGetRelid(rel),
+						NULL,
+						&reltuples,
+						NULL,
+						&relfrozenxid,
+						&relminmxid);
+	}
+	else
+	{
+		relfrozenxid = rel->rd_rel->relfrozenxid;
+		relminmxid = rel->rd_rel->relminmxid;
+		reltuples = rel->rd_rel->reltuples;
+	}
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
@@ -542,9 +562,9 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 	 * table's minimum MultiXactId is older than or equal to the requested
 	 * mxid full-table scan limit; or if DISABLE_PAGE_SKIPPING was specified.
 	 */
-	aggressive = TransactionIdPrecedesOrEquals(rel->rd_rel->relfrozenxid,
+	aggressive = TransactionIdPrecedesOrEquals(relfrozenxid,
 											   xidFullScanLimit);
-	aggressive |= MultiXactIdPrecedesOrEquals(rel->rd_rel->relminmxid,
+	aggressive |= MultiXactIdPrecedesOrEquals(relminmxid,
 											  mxactFullScanLimit);
 	if (params->options & VACOPT_DISABLE_PAGE_SKIPPING)
 		aggressive = true;
@@ -591,9 +611,9 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 	}
 
 	vacrel->bstrategy = bstrategy;
-	vacrel->relfrozenxid = rel->rd_rel->relfrozenxid;
-	vacrel->relminmxid = rel->rd_rel->relminmxid;
-	vacrel->old_live_tuples = rel->rd_rel->reltuples;
+	vacrel->relfrozenxid = relfrozenxid;
+	vacrel->relminmxid = relminmxid;
+	vacrel->old_live_tuples = reltuples;
 
 	/* Set cutoffs for entire VACUUM */
 	vacrel->OldestXmin = OldestXmin;
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 5bc7c3616a9..0b261f723d7 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -677,6 +678,14 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		/*
+		 * If a global temporary table storage file is not initialized in the
+		 * current backend, its index does not have a root page, just returns 0.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 4e6efda97f3..ae12fd91a1b 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -44,6 +44,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index aa7d4d5456b..595cb03eb4a 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -504,6 +504,7 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 81cc39fb70e..0bf05f37a80 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -62,6 +62,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,
@@ -366,13 +368,26 @@ heap_create(const char *relname,
 			break;
 	}
 
+	/* For global temporary table, even if the storage is not initialized,
+	 * the relfilenode needs to be generated and put into the catalog.
+	 */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		create_storage = false;
+		if (OidIsValid(relfilenode))
+			elog(ERROR, "global temporary table can not reuse an existing relfilenode");
+
+		relfilenode = relid;
+	}
 	/*
 	 * Decide whether to create storage. If caller passed a valid relfilenode,
 	 * storage is already created, so don't do it here.  Also don't create it
 	 * for relkinds without physical storage.
 	 */
-	if (!RELKIND_HAS_STORAGE(relkind) || OidIsValid(relfilenode))
+	else if (!RELKIND_HAS_STORAGE(relkind) || OidIsValid(relfilenode))
+	{
 		create_storage = false;
+	}
 	else
 	{
 		create_storage = true;
@@ -427,7 +442,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:
@@ -445,7 +460,8 @@ heap_create(const char *relname,
 	 * protected by the existence of a physical file; but for relations with
 	 * no files, add a pg_shdepend entry to account for that.
 	 */
-	if (!create_storage && reltablespace != InvalidOid)
+	if (!create_storage && reltablespace != InvalidOid &&
+		relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
 		recordDependencyOnTablespace(RelationRelationId, relid,
 									 reltablespace);
 
@@ -1001,6 +1017,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -1039,8 +1056,21 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/*
+	 * The transaction information of the global temporary table is stored
+	 * in the local hash table, not in catalog.
+	 */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1413,6 +1443,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1498,8 +1529,9 @@ heap_create_with_catalog(const char *relname,
 	/*
 	 * If there's a special on-commit action, remember it
 	 */
-	if (oncommit != ONCOMMIT_NOOP)
-		register_on_commit_action(relid, oncommit);
+	if (oncommit != ONCOMMIT_NOOP &&
+		relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+		register_on_commit_action(relid, oncommit, false);
 
 	/*
 	 * ok, the relation has been cataloged, so close our relations and return
@@ -1996,6 +2028,19 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/*
+	 * Only when other sessions are not using this Global temporary table,
+	 * is it allowed to DROP it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot drop global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3280,7 +3325,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3292,7 +3337,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3328,7 +3373,7 @@ RelationTruncateIndexes(Relation heapRelation)
  * ON COMMIT truncation of temporary tables, where it doesn't matter.
  */
 void
-heap_truncate(List *relids)
+heap_truncate(List *relids, bool is_global_temp)
 {
 	List	   *relations = NIL;
 	ListCell   *cell;
@@ -3338,8 +3383,23 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode;
+
+		/*
+		 * Truncate global temporary table only clears local data,
+		 * so only low-level locks need to be held.
+		 */
+		if (is_global_temp)
+		{
+			if (!gtt_storage_attached(rid))
+				continue;
 
-		rel = table_open(rid, AccessExclusiveLock);
+			lockmode = RowExclusiveLock;
+		}
+		else
+			lockmode = AccessExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3372,6 +3432,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3380,23 +3441,39 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	/*
+	 * Truncate GTT only clears local data, so only low-level locks
+	 * need to be held.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		lockmode = AccessShareLock;
+	else
+		lockmode = AccessExclusiveLock;
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	/*
+	 * After the data is cleaned up on the GTT, the transaction information
+	 * for the data(stored in local hash table) is also need reset.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(RelationGetRelid(rel), 0, 0, 0, RecentXmin, GetOldestMultiXactId());
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index c255806e38c..7a52322ede4 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -54,6 +54,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"
@@ -734,6 +735,25 @@ index_create(Relation heapRelation,
 	TransactionId relfrozenxid;
 	MultiXactId relminmxid;
 
+	/* For global temporary table only */
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temporary table with concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot reindex global temporary tables concurrently")));
+
+		/*
+		 * For the case that some backend is applied relcache message to create
+		 * an index on a global temporary table, if this table in the current
+		 * backend are not initialized, the creation of index storage on the
+		 * table are also skipped.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/* constraint flags can only be set when a constraint is requested */
 	Assert((constr_flags == 0) ||
 		   ((flags & INDEX_CREATE_ADD_CONSTRAINT) != 0));
@@ -2117,7 +2137,7 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 * lock (see comments in RemoveRelations), and a non-concurrent DROP is
 	 * more efficient.
 	 */
-	Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+	Assert(!RelpersistenceTsTemp(get_rel_persistence(indexId)) ||
 		   (!concurrent && !concurrent_lock_mode));
 
 	/*
@@ -2149,6 +2169,21 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/*
+	 * Allow to drop index on global temporary table when only current
+	 * backend use it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation) &&
+		is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+	{
+		ereport(ERROR,
+			(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+			 errmsg("cannot drop index %s on global temporary table %s",
+					RelationGetRelationName(userIndexRelation), RelationGetRelationName(userHeapRelation)),
+					errdetail("Because the index is created on the global temporary table and other backend attached it."),
+					errhint("Please try detach all sessions using this temporary table and try again.")));
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2757,6 +2792,7 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = RELATION_IS_GLOBAL_TEMP(rel);
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2851,20 +2887,37 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
-		{
-			rd_rel->relpages = (int32) relpages;
-			dirty = true;
-		}
-		if (rd_rel->reltuples != (float4) reltuples)
+		/* For global temporary table */
+		if (is_gtt)
 		{
-			rd_rel->reltuples = (float4) reltuples;
-			dirty = true;
+			/* Update GTT'statistics into local relcache */
+			rel->rd_rel->relpages = (int32) relpages;
+			rel->rd_rel->reltuples = (float4) reltuples;
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+
+			/* Update GTT'statistics into local hashtable */
+			up_gtt_relstats(RelationGetRelid(rel), relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+		else
 		{
-			rd_rel->relallvisible = (int32) relallvisible;
-			dirty = true;
+			if (rd_rel->relpages != (int32) relpages)
+			{
+				rd_rel->relpages = (int32) relpages;
+				dirty = true;
+			}
+
+			if (rd_rel->reltuples != (float4) reltuples)
+			{
+				rd_rel->reltuples = (float4) reltuples;
+				dirty = true;
+			}
+
+			if (rd_rel->relallvisible != (int32) relallvisible)
+			{
+				rd_rel->relallvisible = (int32) relallvisible;
+				dirty = true;
+			}
 		}
 	}
 
@@ -2977,6 +3030,26 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, progress_index, progress_vals);
 	}
 
+	/* For build index on global temporary table */
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		/*
+		 * If the storage for the index in this session is not initialized,
+		 * it needs to be created.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			/* Before create init storage, fix the local Relcache first */
+			force_enable_gtt_index(indexRelation);
+
+			Assert(gtt_storage_attached(RelationGetRelid(heapRelation)));
+
+			/* Init storage for index */
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3518,6 +3591,8 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	PGRUsage	ru0;
 	bool		progress = ((params->options & REINDEXOPT_REPORT_PROGRESS) != 0);
 	bool		set_tablespace = false;
+	LOCKMODE	lockmode_on_heap = ShareLock;
+	LOCKMODE	lockmode_on_index = AccessExclusiveLock;
 
 	pg_rusage_init(&ru0);
 
@@ -3531,10 +3606,29 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	if (!OidIsValid(heapId))
 		return;
 
+	/*
+	 * For reindex on global temporary table, If the storage for the index
+	 * in current backend is not initialized, nothing is done.
+	 */
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		if (!gtt_storage_attached(indexId))
+		{
+			/* Suppress use of the target index while rebuilding it */
+			SetReindexProcessing(heapId, indexId);
+			/* Re-allow use of target index */
+			ResetReindexProcessing();
+			return;
+		}
+
+		lockmode_on_heap = AccessShareLock;
+		lockmode_on_index = AccessShareLock;
+	}
+
 	if ((params->options & REINDEXOPT_MISSING_OK) != 0)
-		heapRelation = try_table_open(heapId, ShareLock);
+		heapRelation = try_table_open(heapId, lockmode_on_heap);
 	else
-		heapRelation = table_open(heapId, ShareLock);
+		heapRelation = table_open(heapId, lockmode_on_heap);
 
 	/* if relation is gone, leave */
 	if (!heapRelation)
@@ -3560,7 +3654,7 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	 * Open the target index relation and get an exclusive lock on it, to
 	 * ensure that no one else is touching this particular index.
 	 */
-	iRel = index_open(indexId, AccessExclusiveLock);
+	iRel = index_open(indexId, lockmode_on_index);
 
 	if (progress)
 		pgstat_progress_update_param(PROGRESS_CREATEIDX_ACCESS_METHOD_OID,
@@ -3811,6 +3905,12 @@ reindex_relation(Oid relid, int flags, ReindexParams *params)
 	bool		result;
 	ListCell   *indexId;
 	int			i;
+	LOCKMODE	lockmode;
+
+	if (flags & REINDEX_REL_PROCESS_GLOBAL_TEMP)
+		lockmode = AccessShareLock;
+	else
+		lockmode = ShareLock;
 
 	/*
 	 * Open and lock the relation.  ShareLock is sufficient since we only need
@@ -3818,9 +3918,9 @@ reindex_relation(Oid relid, int flags, ReindexParams *params)
 	 * should match ReindexTable().
 	 */
 	if ((params->options & REINDEXOPT_MISSING_OK) != 0)
-		rel = try_table_open(relid, ShareLock);
+		rel = try_table_open(relid, lockmode);
 	else
-		rel = table_open(relid, ShareLock);
+		rel = table_open(relid, lockmode);
 
 	/* if relation is gone, leave */
 	if (!rel)
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 4de8400fd0f..fe3fcc712cb 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -656,6 +656,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/* Do not allow create global temporary table in temporary schemas */
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp table in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index c5ad28d71fe..707068a6fd8 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,6 +27,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -61,6 +62,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			temprelOid;			/* InvalidOid if not a global temporary rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -115,7 +117,7 @@ AddPendingSync(const RelFileNode *rnode)
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -126,7 +128,12 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/*
+		 * Global temporary table and local temporary table use same
+		 * design on storage module.
+		 */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -154,6 +161,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
@@ -165,6 +173,21 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		AddPendingSync(&rnode);
 	}
 
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		Assert(rel && RELATION_IS_GLOBAL_TEMP(rel));
+
+		/*
+		 * Remember the reloid of global temporary table, which is used for
+		 * transaction commit or rollback.
+		 * see smgrDoPendingDeletes.
+		 */
+		pending->temprelOid = RelationGetRelid(rel);
+
+		/* Remember global temporary table storage info to localhash */
+		remember_gtt_storage_info(rnode, rel);
+	}
+
 	return srel;
 }
 
@@ -201,11 +224,20 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/*
+	 * Remember the reloid of global temporary table, which is used for
+	 * transaction commit or rollback.
+	 * see smgrDoPendingDeletes.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->temprelOid = RelationGetRelid(rel);
+
 	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
@@ -618,6 +650,7 @@ smgrDoPendingDeletes(bool isCommit)
 	int			nrels = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -647,14 +680,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->temprelOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -664,12 +701,21 @@ smgrDoPendingDeletes(bool isCommit)
 
 	if (nrels > 0)
 	{
+		int	i;
+
 		smgrdounlinkall(srels, nrels, false);
 
-		for (int i = 0; i < nrels; i++)
+		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* Delete global temporary table info in localhash */
+			if (gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 00000000000..4fc4678c4c7
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1508 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  The body implementation of Global Temparary table.
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *	  See src/backend/catalog/GTT_README for Global temparary table's
+ *	  requirements and design.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_info_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+
+int		vacuum_gtt_defer_check_age = 0;
+
+/*
+ * The Global temporary table's shared hash table data structure
+ */
+typedef struct gtt_ctl_data
+{
+	LWLock		lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+/* record this global temporary table in which backends are being used */
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+/*
+ * The Global temporary table's local hash table data structure
+ */
+/* Record the storage information and statistical information of the global temporary table */
+typedef struct
+{
+	Oid			relfilenode;
+	Oid			spcnode;
+
+	/* pg_class relstat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+
+	/* pg_statistic column stat */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_free_statistics(gtt_relfilenode *rnode);
+static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok);
+static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok);
+static Bitmapset *copy_active_gtt_bitmap(Oid relid);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+/*
+ * Calculate shared hash table entry size for GTT.
+ */
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	/* hash entry header size */
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	/*
+	 * hash entry data size
+	 * this is a bitmap in shared memory, each backend have a bit.
+	 */
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+/*
+ * Calculate shared hash table max size for GTT.
+ */
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	/* shared hash header size */
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	/* hash entry size */
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	/* max size */
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+/*
+ * Initialization shared hash table for GTT.
+ */
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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();
+	}
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(gtt_fnode);
+	info.entrysize = action_gtt_shared_hash_entry_size();
+	active_gtt_shared_hash =
+		ShmemInitHash("active gtt shared hash",
+						gtt_shared_ctl->max_entry,
+						gtt_shared_ctl->max_entry,
+						&info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+/*
+ * Record GTT relid to shared hash table, which means that current backend is using this GTT.
+ */
+static void
+gtt_storage_checkin(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			found;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *)&(fnode), 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_global_temporary_table.")));
+	}
+
+	if (!found)
+	{
+		int			wordnum;
+
+		/* init bitmap */
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	/* record itself in bitmap */
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+/*
+ * Remove the GTT relid record from the shared hash table which means that current backend is
+ * not use this GTT.
+ */
+static void
+gtt_storage_checkout(Oid relid, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		if (isCommit)
+			elog(WARNING, "relid %u not exist in gtt shared hash when drop local storage", relid);
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+	/* remove itself from bitmap */
+	bms_del_member(entry->map, MyBackendId);
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return;
+}
+
+/*
+ * Gets usage information for a GTT from shared hash table.
+ * The information is in the form of bitmap.
+ * Quickly copy the entire bitmap from shared memory and return it.
+ * that to avoid holding locks for a long time.
+ */
+static Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	Bitmapset		*map_copy = NULL;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry)
+	{
+		Assert(entry->map);
+		/* copy the entire bitmap */
+		if (!bms_is_empty(entry->map))
+			map_copy = bms_copy(entry->map);
+	}
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return map_copy;
+}
+
+/*
+ * Check if there are other backends using this GTT besides the current backend.
+ */
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			in_use = false;
+	int			num_use = 0;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		return false;
+	}
+
+	Assert(entry->map);
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+	/* how many backend are using this GTT */
+	num_use = bms_num_members(entry->map);
+	if (num_use == 0)
+		in_use = false;
+	else if (num_use == 1)
+	{
+		/* check if this is itself */
+		if(bms_is_member(MyBackendId, entry->map))
+			in_use = false;
+		else
+			in_use = true;
+	}
+	else
+		in_use = true;
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return in_use;
+}
+
+/*
+ * Record GTT information to local hash.
+ * They include GTT storage info, transaction info and statistical info.
+ */
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+	gtt_local_hash_entry		*entry;
+	MemoryContext			oldcontext;
+	gtt_relfilenode			*new_node = NULL;
+	Oid				relid = RelationGetRelid(rel);
+	int				natts = 0;
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	/* First time through: initialize the hash table */
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		HASHCTL		ctl;
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_info_context =
+			AllocSetContextCreate(CacheMemoryContext,
+								"gtt info context",
+								ALLOCSET_DEFAULT_SIZES);
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		ctl.hcxt = gtt_info_context;
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+	}
+
+	Assert(CacheMemoryContext);
+	Assert(gtt_info_context);
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	entry = gtt_search_by_relid(relid, true);
+	if (!entry)
+	{
+		bool		found = false;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash,
+						(void *) &relid, HASH_ENTER, &found);
+
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			/* record the on commit clause */
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS, true);
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			gtt_storage_checkin(relid);
+		}
+	}
+
+	/* record storage info relstat columnstats and transaction info to relfilenode list */
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	new_node->natts = 0;
+	new_node->attnum = NULL;
+	new_node->att_stat_tups = NULL;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* init column stats structure */
+	natts = RelationGetNumberOfAttributes(rel);
+	new_node->attnum = palloc0(sizeof(int) * natts);
+	new_node->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+	new_node->natts = natts;
+
+	/* only heap rel or toast rel have transaction info */
+	if (entry->relkind == RELKIND_RELATION ||
+		entry->relkind == RELKIND_TOASTVALUE)
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+
+	/* Registration callbacks are used to trigger cleanup during process exit */
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+/*
+ * Remove GTT information from local hash when transaction commit/rollback.
+ */
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	gtt_relfilenode		*d_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+		return;
+	}
+
+	d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+		else
+		{
+			/* rollback transaction */
+			if (entry->relfilenode_list == NIL)
+			{
+				if (entry->relkind == RELKIND_RELATION ||
+					entry->relkind == RELKIND_SEQUENCE)
+					gtt_storage_checkout(relid, isCommit);
+
+				hash_search(gtt_storage_local_hash,
+						(void *) &(relid), HASH_REMOVE, NULL);
+			}
+
+			return;
+		}
+	}
+
+	/* Clean up transaction info from Local order list and MyProc */
+	if (entry->relkind == RELKIND_RELATION ||
+		entry->relkind == RELKIND_TOASTVALUE)
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+
+		/* this is valid relfrozenxid */
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	/* delete relfilenode from rel entry */
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	gtt_free_statistics(d_rnode);
+
+	if (entry->relfilenode_list == NIL)
+	{
+		/* tell shared hash that current backend will no longer use this GTT */
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(relid, isCommit);
+
+		hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_REMOVE, NULL);
+	}
+
+	return;
+}
+
+/*
+ * Check if current backend is using this GTT.
+ */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool			found = false;
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (!OidIsValid(relid))
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry)
+		found = true;
+
+	return found;
+}
+
+/*
+ * When backend exit, bulk cleaning all GTT storage and local buffer of this backend.
+ */
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS			status;
+	gtt_local_hash_entry	*entry;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	/* Need to ensure we have a usable transaction. */
+	AbortOutOfAnyTransaction();
+
+	/* Search all relfilenode for GTT in current backend */
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel[1];
+			RelFileNode		rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel[0] = smgropen(rnode, MyBackendId);
+			smgrdounlinkall(srel, 1, false);
+			smgrclose(srel[0]);
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(entry->relid, false);
+
+		hash_search(gtt_storage_local_hash, (void *) &(entry->relid), HASH_REMOVE, NULL);
+	}
+
+	/* set to global area */
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;
+
+	return;
+}
+
+/*
+ * Update GTT relstats(relpage/reltuple/relallvisible)
+ * to local hash.
+ */
+void
+up_gtt_relstats(Oid relid,
+					BlockNumber num_pages,
+					double num_tuples,
+					BlockNumber num_all_visible_pages,
+					TransactionId relfrozenxid,
+					TransactionId relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (!OidIsValid(relid))
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (num_pages > 0 &&
+		gtt_rnode->relpages != (int32)num_pages)
+		gtt_rnode->relpages = (int32)num_pages;
+
+	if (num_tuples > 0 &&
+		gtt_rnode->reltuples != (float4)num_tuples)
+		gtt_rnode->reltuples = (float4)num_tuples;
+
+	/* only heap contain transaction information and relallvisible */
+	if (entry->relkind == RELKIND_RELATION ||
+		entry->relkind == RELKIND_TOASTVALUE)
+	{
+		if (num_all_visible_pages > 0 &&
+			gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+		{
+			gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+		}
+
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNextTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			/* set to local order list */
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			/* set to global area */
+			set_gtt_session_relfrozenxid();
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search GTT relstats(relpage/reltuple/relallvisible)
+ * from local has.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update GTT info(definition is same as pg_statistic)
+ * to local hash.
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+	MemoryContext		oldcontext;
+	bool		found = false;
+	int			i = 0;
+
+	/* not support whole row or system column */
+	if (attnum <= 0)
+		return;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return;
+
+	Assert(entry->relid == reloid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	/* switch context to gtt_info_context for store tuple at heap_form_tuple */
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == 0)
+		{
+			Assert(gtt_rnode->att_stat_tups[i] == NULL);
+			gtt_rnode->attnum[i] = attnum;
+			gtt_rnode->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+			found = true;
+			break;
+		}
+		else if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			heap_freetuple(gtt_rnode->att_stat_tups[i]);
+			gtt_rnode->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+			found = true;
+			break;
+		}
+	}
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!found)
+		elog(WARNING, "analyze can not update relid %u column %d statistics after add or drop column, try truncate table first", reloid, attnum);
+
+	return;
+}
+
+/*
+ * Search GTT statistic info(definition is same as pg_statistic)
+ * from local hash.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	int			i = 0;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	/* not support whole row or system column */
+	if (attnum <= 0)
+		return NULL;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return NULL;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return NULL;
+
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			return gtt_rnode->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Insert a RelfrozenXID into the list and keep the list in order.
+ */
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int		i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Remove a RelfrozenXID from order list gtt_session_relfrozenxid_list.
+ */
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+/*
+ * Update of backend Level oldest relfrozenxid to MyProc.
+ * This makes each backend's oldest RelFrozenxID globally visible.
+ */
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list != NIL)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	if (MyProc->backend_gtt_frozenxid != gtt_frozenxid)
+		MyProc->backend_gtt_frozenxid = gtt_frozenxid;
+}
+
+/*
+ * Get GTT column level data statistics.
+ */
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate		*tupstore;
+	HeapTuple		tuple;
+	Relation		rel = NULL;
+	Oid			reloid = PG_GETARG_OID(0);
+	int			attnum = PG_GETARG_INT32(1);
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	if (!RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	/* get data from local hash */
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum		values[Natts_pg_statistic];
+		bool		isnull[Natts_pg_statistic];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, AccessShareLock);
+	relation_close(pg_tatistic, AccessShareLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get GTT table level data statistics.
+ */
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate	*tupstore;
+	TupleDesc	tupdesc;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid			reloid = PG_GETARG_OID(0);
+	Oid			relnode = 0;
+	BlockNumber		relpages = 0;
+	BlockNumber		relallvisible = 0;
+	uint32			relfrozenxid = 0;
+	uint32			relminmxid = 0;
+	double			reltuples = 0;
+	Relation		rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	if (!RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[6];
+		bool	isnull[6];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get a list of backend pids that are currently using this GTT.
+ */
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	PGPROC			*proc = NULL;
+	Bitmapset		*map = NULL;
+	Tuplestorestate		*tupstore;
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	HeapTuple		tuple;
+	Oid			reloid = PG_GETARG_OID(0);
+	Relation		rel = NULL;
+	int				backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	if (!RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	/* get data from share hash */
+	map = copy_active_gtt_bitmap(reloid);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			/* backendid map to process pid */
+			proc = BackendIdGetProc(backendid);
+			if (proc && proc->pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+				pid_t	pid = proc->pid;
+
+				memset(isnull, 0, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, AccessShareLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get backend level oldest relfrozenxid of each backend using GTT in current database.
+ */
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate		*tupstore;
+	int			*pids = NULL;
+	uint32			*xids = NULL;
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	HeapTuple		tuple;
+	int			num_xid = MaxBackends + 1;
+	int			i = 0;
+	int			j = 0;
+	uint32			oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	if (pids == NULL || xids == NULL)
+		elog(ERROR, "out of memory");
+
+	/* Get backend level oldest relfrozenxid in all backend that in MyDatabaseId use GTT */
+	oldest = list_all_backend_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		/* save oldest relfrozenxid */
+		pids[i] = 0;
+		xids[i] = oldest;
+		i++;
+
+		/* save relfrozenxid for each session */
+		for (j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, 0, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+/*
+ * In order to build the GTT index, force enable GTT'index.
+ */
+void
+force_enable_gtt_index(Relation index)
+{
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(RelationGetRelid(index)));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+/*
+ * Fix the local state of the GTT's index.
+ */
+void
+gtt_fix_index_backend_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid heapOid = index->rd_index->indrelid;
+
+	/* Must be GTT */
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	/*
+	 * If this GTT is not initialized in the current backend,
+	 * its index status is temporarily set to invalid(local relcache).
+	 */
+	if (gtt_storage_attached(heapOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+/*
+ * During the SQL initialization of the executor (InitPlan)
+ * Initialize storage of GTT GTT'indexes and build empty index.
+ */
+void
+init_gtt_storage(CmdType operation, Relation relation)
+{
+	Oid			toastrelid;
+	List		*indexoidlist = NIL;
+	ListCell	*l;
+
+	if (!(operation == CMD_INSERT))
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	/* Each GTT is initialized once in each backend */
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	/* init heap storage */
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	indexoidlist = RelationGetIndexList(relation);
+	foreach(l, indexoidlist)
+	{
+		Oid			indexOid = lfirst_oid(l);
+		Relation	index = index_open(indexOid, RowExclusiveLock);
+		IndexInfo	*info = BuildDummyIndexInfo(index);
+
+		index_build(relation, index, info, true, false);
+		/* after build index, index re-enabled */
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+		index_close(index, NoLock);
+	}
+	list_free(indexoidlist);
+
+	/* rebuild index for global temp toast table */
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+
+		/* init index storage */
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid			indexId = lfirst_oid(indlist);
+			Relation	index = index_open(indexId, RowExclusiveLock);
+			IndexInfo	*info = BuildDummyIndexInfo(index);
+
+			/* build empty index */
+			index_build(toastrel, index, info, true, false);
+			Assert(index->rd_index->indisvalid);
+			Assert(index->rd_index->indislive);
+			Assert(index->rd_index->indisready);
+			index_close(index, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+/*
+ * Release the data structure memory used to store GTT storage info.
+ */
+static void
+gtt_free_statistics(gtt_relfilenode *rnode)
+{
+	int i;
+
+	Assert(rnode);
+
+	for (i = 0; i < rnode->natts; i++)
+	{
+		if (rnode->att_stat_tups[i])
+		{
+			heap_freetuple(rnode->att_stat_tups[i]);
+			rnode->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (rnode->attnum)
+		pfree(rnode->attnum);
+
+	if (rnode->att_stat_tups)
+		pfree(rnode->att_stat_tups);
+
+	pfree(rnode);
+
+	return;
+}
+
+/*
+ * Get the current relfilenode of this GTT.
+ */
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
+/*
+ * Get a relfilenode used by this GTT during the transaction life cycle.
+ */
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok)
+{
+	gtt_relfilenode		*rnode = NULL;
+	ListCell		*lc;
+
+	Assert(entry);
+
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == relfilenode)
+		{
+			rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (!missing_ok && rnode == NULL)
+		elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+	return rnode;
+}
+
+/*
+ * Get one GTT info from local hash.
+ */
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry == NULL && !missing_ok)
+		elog(ERROR, "relid %u not found in local hash", relid);
+
+	return entry;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index eb560955cda..abfd0f6e03f 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 4928702aec0..fc6e64a79b6 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -104,7 +105,7 @@ static int	acquire_inherited_sample_rows(Relation onerel, int elevel,
 										  HeapTuple *rows, int targrows,
 										  double *totalrows, double *totaldeadrows);
 static void update_attstats(Oid relid, bool inh,
-							int natts, VacAttrStats **vacattrstats);
+							int natts, VacAttrStats **vacattrstats, char relpersistence);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
@@ -185,6 +186,17 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -601,14 +613,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]));
 		}
 
 		/*
@@ -1622,7 +1635,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;
@@ -1724,31 +1737,48 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/*
+		 * For global temporary table,
+		 * Update column statistic to localhash, not catalog.
+		 */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 9d22f648a84..a44eefa1f6b 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/toasting.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
@@ -390,6 +391,22 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+	{
+		if (gtt_storage_attached(RelationGetRelid(OldHeap)))
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("not support cluster global temporary table yet")));
+
+		relation_close(OldHeap, AccessExclusiveLock);
+		pgstat_progress_end_command();
+		return;
+	}
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
@@ -585,6 +602,8 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
 	TransactionId frozenXid;
 	MultiXactId cutoffMulti;
 
+	Assert(!RELATION_IS_GLOBAL_TEMP(OldHeap));
+
 	/* Mark the correct index as clustered */
 	if (OidIsValid(indexOid))
 		mark_index_clustered(OldHeap, indexOid, true);
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 53f48531419..c03191cce94 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -289,7 +289,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP_ON_CURRENT_SESSION(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, whereClause,
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index f366a818a14..fb0e9349a6e 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -30,6 +30,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/copyfrom_internal.h"
 #include "commands/progress.h"
@@ -652,7 +653,7 @@ CopyFrom(CopyFromState cstate)
 	 */
 	ExecInitRangeTable(estate, cstate->range_table);
 	resultRelInfo = target_resultRelInfo = makeNode(ResultRelInfo);
-	ExecInitResultRelation(estate, resultRelInfo, 1);
+	ExecInitResultRelation(estate, resultRelInfo, 1, CMD_INSERT);
 
 	/* Verify the named relation is a valid target for INSERT */
 	CheckValidResultRel(resultRelInfo, CMD_INSERT);
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 09828517153..b151fbdd80f 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -38,6 +38,7 @@
 #include "commands/prepare.h"
 #include "commands/tablecmds.h"
 #include "commands/view.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
@@ -520,6 +521,8 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 	 */
 	intoRelationDesc = table_open(intoRelationAddr.objectId, AccessExclusiveLock);
 
+	init_gtt_storage(CMD_INSERT, intoRelationDesc);
+
 	/*
 	 * Make sure the constructed table does not have RLS enabled.
 	 *
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index c14ca27c5ed..d65ae895356 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -111,6 +111,7 @@ struct ReindexIndexCallbackState
 {
 	ReindexParams params;		/* options from statement */
 	Oid			locked_table_oid;	/* tracks previously locked table */
+	LOCKMODE	lockmode;
 };
 
 /*
@@ -570,7 +571,7 @@ DefineIndex(Oid relationId,
 	 * is more efficient.  Do this before any use of the concurrent option is
 	 * done.
 	 */
-	if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+	if (stmt->concurrent && !RelpersistenceTsTemp(get_rel_persistence(relationId)))
 		concurrent = true;
 	else
 		concurrent = false;
@@ -2581,9 +2582,9 @@ ReindexIndex(RangeVar *indexRelation, ReindexParams *params, bool isTopLevel)
 	 */
 	state.params = *params;
 	state.locked_table_oid = InvalidOid;
+	state.lockmode = AccessShareLock;
 	indOid = RangeVarGetRelidExtended(indexRelation,
-									  (params->options & REINDEXOPT_CONCURRENTLY) != 0 ?
-									  ShareUpdateExclusiveLock : AccessExclusiveLock,
+									  AccessShareLock,
 									  0,
 									  RangeVarCallbackForReindexIndex,
 									  &state);
@@ -2594,11 +2595,25 @@ ReindexIndex(RangeVar *indexRelation, ReindexParams *params, bool isTopLevel)
 	 */
 	persistence = get_rel_persistence(indOid);
 	relkind = get_rel_relkind(indOid);
+	if (persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		LOCKMODE	lockmode;
+
+		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0)
+			lockmode = ShareUpdateExclusiveLock;
+		else
+			lockmode = AccessExclusiveLock;
+
+		/* lock heap first */
+		Assert(OidIsValid(state.locked_table_oid));
+		LockRelationOid(state.locked_table_oid, lockmode);
+		LockRelationOid(indOid, lockmode);
+	}
 
 	if (relkind == RELKIND_PARTITIONED_INDEX)
 		ReindexPartitions(indOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 persistence != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(persistence))
 		ReindexRelationConcurrently(indOid, params);
 	else
 	{
@@ -2620,15 +2635,7 @@ RangeVarCallbackForReindexIndex(const RangeVar *relation,
 {
 	char		relkind;
 	struct ReindexIndexCallbackState *state = arg;
-	LOCKMODE	table_lockmode;
-
-	/*
-	 * Lock level here should match table lock in reindex_index() for
-	 * non-concurrent case and table locks used by index_concurrently_*() for
-	 * concurrent case.
-	 */
-	table_lockmode = (state->params.options & REINDEXOPT_CONCURRENTLY) != 0 ?
-		ShareUpdateExclusiveLock : ShareLock;
+	LOCKMODE	table_lockmode = state->lockmode;
 
 	/*
 	 * If we previously locked some other index's heap, and the name we're
@@ -2689,6 +2696,8 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 {
 	Oid			heapOid;
 	bool		result;
+	char		relpersistence;
+	int 		reindex_flags = 0;
 
 	/*
 	 * The lock level used here should match reindex_relation().
@@ -2699,15 +2708,27 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 	 * locks on our temporary table.
 	 */
 	heapOid = RangeVarGetRelidExtended(relation,
-									   (params->options & REINDEXOPT_CONCURRENTLY) != 0 ?
-									   ShareUpdateExclusiveLock : ShareLock,
+									   AccessShareLock,
 									   0,
 									   RangeVarCallbackOwnsTable, NULL);
 
+	relpersistence = get_rel_persistence(heapOid);
+	if (relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		LOCKMODE	lockmode;
+
+		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0)
+			lockmode = ShareUpdateExclusiveLock;
+		else
+			lockmode = ShareLock;
+
+		LockRelationOid(heapOid, lockmode);
+	}
+
 	if (get_rel_relkind(heapOid) == RELKIND_PARTITIONED_TABLE)
 		ReindexPartitions(heapOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(relpersistence))
 	{
 		result = ReindexRelationConcurrently(heapOid, params);
 
@@ -2721,9 +2742,14 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 		ReindexParams newparams = *params;
 
 		newparams.options |= REINDEXOPT_REPORT_PROGRESS;
+
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			reindex_flags |= REINDEX_REL_PROCESS_GLOBAL_TEMP;
+
+		reindex_flags |= REINDEX_REL_PROCESS_TOAST;
+		reindex_flags |= REINDEX_REL_CHECK_CONSTRAINTS;
 		result = reindex_relation(heapOid,
-								  REINDEX_REL_PROCESS_TOAST |
-								  REINDEX_REL_CHECK_CONSTRAINTS,
+								  reindex_flags,
 								  &newparams);
 		if (!result)
 			ereport(NOTICE,
@@ -3122,7 +3148,7 @@ ReindexMultipleInternal(List *relids, ReindexParams *params)
 			   relkind != RELKIND_PARTITIONED_TABLE);
 
 		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			relpersistence != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(relpersistence))
 		{
 			ReindexParams newparams = *params;
 
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 62465bacd81..ef37f79ba68 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -51,12 +51,32 @@ LockTableCommand(LockStmt *lockstmt)
 		RangeVar   *rv = (RangeVar *) lfirst(p);
 		bool		recurse = rv->inh;
 		Oid			reloid;
+		LOCKMODE	lockmode = lockstmt->mode;
+		char		relpersistence;
 
-		reloid = RangeVarGetRelidExtended(rv, lockstmt->mode,
-										  lockstmt->nowait ? RVR_NOWAIT : 0,
+		reloid = RangeVarGetRelidExtended(rv, NoLock, 0,
 										  RangeVarCallbackForLockTable,
 										  (void *) &lockstmt->mode);
 
+		relpersistence = get_rel_persistence(reloid);
+		if (relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			if (!lockstmt->nowait)
+				LockRelationOid(reloid, lockmode);
+			else if (!ConditionalLockRelationOid(reloid, lockmode))
+			{
+				/* try to throw error by name; relation could be deleted... */
+				char	   *relname = get_rel_name(reloid);
+
+				if (!relname)
+					return;		/* child concurrently dropped, just skip it */
+				ereport(ERROR,
+						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						 errmsg("could not obtain lock on relation \"%s\"",
+								relname)));
+			}
+		}
+
 		if (get_rel_relkind(reloid) == RELKIND_VIEW)
 			LockViewRecurse(reloid, lockstmt->mode, lockstmt->nowait, NIL);
 		else if (recurse)
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 72bfdc07a49..e5257f610f6 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -220,9 +223,12 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	rel = table_open(seqoid, AccessExclusiveLock);
 	tupDesc = RelationGetDescr(rel);
 
-	/* now initialize the sequence's data */
-	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	if (!RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		/* now initialize the sequence's data */
+		tuple = heap_form_tuple(tupDesc, value, null);
+		fill_seq_with_data(rel, tuple);
+	}
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -275,8 +281,6 @@ ResetSequence(Oid seq_relid)
 	Buffer		buf;
 	HeapTupleData seqdatatuple;
 	HeapTuple	tuple;
-	HeapTuple	pgstuple;
-	Form_pg_sequence pgsform;
 	int64		startv;
 
 	/*
@@ -287,12 +291,7 @@ ResetSequence(Oid seq_relid)
 	init_sequence(seq_relid, &elm, &seq_rel);
 	(void) read_seq_tuple(seq_rel, &buf, &seqdatatuple);
 
-	pgstuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seq_relid));
-	if (!HeapTupleIsValid(pgstuple))
-		elog(ERROR, "cache lookup failed for sequence %u", seq_relid);
-	pgsform = (Form_pg_sequence) GETSTRUCT(pgstuple);
-	startv = pgsform->seqstart;
-	ReleaseSysCache(pgstuple);
+	startv = get_seqence_start_value(seq_relid);
 
 	/*
 	 * Copy the existing sequence tuple.
@@ -451,6 +450,15 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 
 	init_sequence(relid, &elm, &seqrel);
 
+	if (RELATION_IS_GLOBAL_TEMP(seqrel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(seqrel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary sequence %s when other backend attached it.",
+						RelationGetRelationName(seqrel))));
+	}
+
 	rel = table_open(SequenceRelationId, RowExclusiveLock);
 	seqtuple = SearchSysCacheCopy1(SEQRELID,
 								   ObjectIdGetDatum(relid));
@@ -611,7 +619,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +944,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1153,6 +1161,14 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	/* Initializes the storage for sequence which the global temporary table belongs. */
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1927,3 +1943,58 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+/*
+ * Get the startValue of the sequence from syscache.
+ */
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+/*
+ * Initialize sequence which global temporary table belongs.
+ */
+void
+gtt_init_seq(Relation rel)
+{
+	Datum		value[SEQ_COL_LASTCOL];
+	bool		null[SEQ_COL_LASTCOL];
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL - 1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+	null[SEQ_COL_LASTVAL - 1] = false;
+
+	value[SEQ_COL_LOG - 1] = Int64GetDatum((int64)0);
+	null[SEQ_COL_LOG - 1] = false;
+
+	value[SEQ_COL_CALLED - 1] = BoolGetDatum(false);
+	null[SEQ_COL_CALLED - 1] = false;
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d675d261f75..584c70b79b6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -118,6 +119,7 @@ typedef struct OnCommitItem
 	 */
 	SubTransactionId creating_subid;
 	SubTransactionId deleting_subid;
+	bool			 is_global_temp;
 } OnCommitItem;
 
 static List *on_commits = NIL;
@@ -602,7 +604,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
 static char GetAttributeCompression(Oid atttypid, char *compression);
-
+static OnCommitAction gtt_oncommit_option(List *options);
 
 /* ----------------------------------------------------------------
  *		DefineRelation
@@ -647,6 +649,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -658,7 +661,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * Check consistency of arguments
 	 */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !RelpersistenceTsTemp(stmt->relation->relpersistence))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -688,7 +691,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	if (RelpersistenceTsTemp(stmt->relation->relpersistence)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -789,6 +792,50 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* For global temporary table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		if (!(relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE))
+			elog(ERROR, "Only support global temporary regular table.");
+
+		/* Check parent table */
+		if (inheritOids)
+			elog(ERROR, "Not support global temporary partition table or inherit table.");
+
+		/* Check oncommit clause and save to reloptions */
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temporary table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1415,7 +1462,7 @@ RemoveRelations(DropStmt *drop)
 		 * relation persistence cannot be known without its OID.
 		 */
 		if (drop->concurrent &&
-			get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(get_rel_persistence(relOid)))
 		{
 			Assert(list_length(drop->objects) == 1 &&
 				   drop->removeType == OBJECT_INDEX);
@@ -1624,9 +1671,9 @@ ExecuteTruncate(TruncateStmt *stmt)
 		Relation	rel;
 		bool		recurse = rv->inh;
 		Oid			myrelid;
-		LOCKMODE	lockmode = AccessExclusiveLock;
+		LOCKMODE	lockmode;
 
-		myrelid = RangeVarGetRelidExtended(rv, lockmode,
+		myrelid = RangeVarGetRelidExtended(rv, AccessShareLock,
 										   0, RangeVarCallbackForTruncate,
 										   NULL);
 
@@ -1634,9 +1681,21 @@ ExecuteTruncate(TruncateStmt *stmt)
 		if (list_member_oid(relids, myrelid))
 			continue;
 
-		/* open the relation, we already hold a lock on it */
+		/* open the relation, we need hold a low-level lock first */
 		rel = table_open(myrelid, NoLock);
 
+		/*
+		 * Truncate global temp table only cleans up the data in current backend,
+		 * only low-level locks are required.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel))
+			lockmode = AccessShareLock;
+		else
+		{
+			lockmode = AccessExclusiveLock;
+			LockRelationOid(myrelid, lockmode);
+		}
+
 		/*
 		 * RangeVarGetRelidExtended() has done most checks with its callback,
 		 * but other checks with the now-opened Relation remain.
@@ -1886,6 +1945,7 @@ ExecuteTruncateGuts(List *explicit_rels,
 	foreach(cell, rels)
 	{
 		Relation	rel = (Relation) lfirst(cell);
+		LOCKMODE	lockmode;
 
 		/* Skip partitioned tables as there is nothing to do */
 		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
@@ -1936,6 +1996,19 @@ ExecuteTruncateGuts(List *explicit_rels,
 			continue;
 		}
 
+		/*
+		 * Skip the global temporary table that is not initialized for storage
+		 * in current backend.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel))
+		{
+			lockmode = AccessShareLock;
+			if (!gtt_storage_attached(RelationGetRelid(rel)))
+				continue;
+		}
+		else
+			lockmode = AccessExclusiveLock;
+
 		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
@@ -1954,6 +2027,7 @@ ExecuteTruncateGuts(List *explicit_rels,
 			Oid			heap_relid;
 			Oid			toast_relid;
 			ReindexParams reindex_params = {0};
+			int			reindex_flags = 0;
 
 			/*
 			 * This effectively deletes all rows in the table, and may be done
@@ -1981,17 +2055,21 @@ ExecuteTruncateGuts(List *explicit_rels,
 			if (OidIsValid(toast_relid))
 			{
 				Relation	toastrel = relation_open(toast_relid,
-													 AccessExclusiveLock);
+													 lockmode);
 
 				RelationSetNewRelfilenode(toastrel,
 										  toastrel->rd_rel->relpersistence);
 				table_close(toastrel, NoLock);
 			}
 
+			reindex_flags = REINDEX_REL_PROCESS_TOAST;
+			if (RELATION_IS_GLOBAL_TEMP(rel))
+				reindex_flags |= REINDEX_REL_PROCESS_GLOBAL_TEMP;
+
 			/*
 			 * Reconstruct the indexes to match, and we're done.
 			 */
-			reindex_relation(heap_relid, REINDEX_REL_PROCESS_TOAST,
+			reindex_relation(heap_relid, reindex_flags,
 							 &reindex_params);
 		}
 
@@ -4032,6 +4110,16 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temporary table only current backend use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -5363,6 +5451,24 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 
 			rel = table_open(tab->relid, NoLock);
 			find_composite_type_dependencies(rel->rd_rel->reltype, rel, NULL);
+
+			if (RELATION_IS_GLOBAL_TEMP(rel) && tab->rewrite > 0)
+			{
+				if (tab->chgPersistence)
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot change global temporary table persistence setting")));
+
+				if(gtt_storage_attached(tab->relid))
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("Only support alter global temporary table in an empty context."),
+						 errhint("Please create a new connection and execute ALTER TABLE on the new connection.")));
+
+				/* There is no need to override the whole temp table */
+				tab->rewrite = 0;
+			}
+
 			table_close(rel, NoLock);
 		}
 
@@ -5414,6 +5520,8 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot rewrite temporary tables of other sessions")));
 
+			Assert(!RELATION_IS_GLOBAL_TEMP(OldHeap));
+
 			/*
 			 * Select destination tablespace (same as original unless user
 			 * requested a change)
@@ -9045,6 +9153,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -13082,7 +13196,9 @@ TryReuseIndex(Oid oldId, IndexStmt *stmt)
 		Relation	irel = index_open(oldId, NoLock);
 
 		/* If it's a partitioned index, there is no storage to share. */
-		if (irel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
+		/* multiple global temp table are not allow use same relfilenode */
+		if (irel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX &&
+			!RELATION_IS_GLOBAL_TEMP(irel))
 		{
 			stmt->oldNode = irel->rd_node.relNode;
 			stmt->oldCreateSubid = irel->rd_createSubid;
@@ -13744,6 +13860,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -13943,6 +14062,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temporary table");
+
 	/* Check first if relation can be moved to new tablespace */
 	if (!CheckRelationTableSpaceMove(rel, newTableSpace))
 	{
@@ -14246,7 +14368,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(RelationGetSmgr(rel), dstrel, MAIN_FORKNUM,
@@ -15850,6 +15972,7 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
 	switch (rel->rd_rel->relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -16291,7 +16414,7 @@ AlterSeqNamespaces(Relation classRel, Relation rel,
  * Register a newly-created relation's ON COMMIT action.
  */
 void
-register_on_commit_action(Oid relid, OnCommitAction action)
+register_on_commit_action(Oid relid, OnCommitAction action, bool is_gloal_temp)
 {
 	OnCommitItem *oc;
 	MemoryContext oldcxt;
@@ -16310,6 +16433,7 @@ register_on_commit_action(Oid relid, OnCommitAction action)
 	oc->oncommit = action;
 	oc->creating_subid = GetCurrentSubTransactionId();
 	oc->deleting_subid = InvalidSubTransactionId;
+	oc->is_global_temp = is_gloal_temp;
 
 	/*
 	 * We use lcons() here so that ON COMMIT actions are processed in reverse
@@ -16355,6 +16479,7 @@ PreCommit_on_commit_actions(void)
 	ListCell   *l;
 	List	   *oids_to_truncate = NIL;
 	List	   *oids_to_drop = NIL;
+	List	   *oids_to_truncate_gtt = NIL;
 
 	foreach(l, on_commits)
 	{
@@ -16378,7 +16503,12 @@ PreCommit_on_commit_actions(void)
 				 * tables, as they must still be empty.
 				 */
 				if ((MyXactFlags & XACT_FLAGS_ACCESSEDTEMPNAMESPACE))
-					oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid);
+				{
+					if (oc->is_global_temp)
+						oids_to_truncate_gtt = lappend_oid(oids_to_truncate_gtt, oc->relid);
+					else
+						oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid);
+				}
 				break;
 			case ONCOMMIT_DROP:
 				oids_to_drop = lappend_oid(oids_to_drop, oc->relid);
@@ -16395,7 +16525,10 @@ PreCommit_on_commit_actions(void)
 	 * exists at truncation time.
 	 */
 	if (oids_to_truncate != NIL)
-		heap_truncate(oids_to_truncate);
+		heap_truncate(oids_to_truncate, false);
+
+	if (oids_to_truncate_gtt != NIL)
+		heap_truncate(oids_to_truncate_gtt, true);
 
 	if (oids_to_drop != NIL)
 	{
@@ -17394,6 +17527,13 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot attach temporary relation of another session as partition")));
 
+	/* If the parent is permanent, so must be all of its partitions. */
+	if (attachrel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach a global temporary relation as partition of permanent relation \"%s\"",
+						RelationGetRelationName(rel))));
+
 	/* Check if there are any columns in attachrel that aren't in the parent */
 	tupleDesc = RelationGetDescr(attachrel);
 	natts = tupleDesc->natts;
@@ -18864,3 +19004,40 @@ GetAttributeCompression(Oid atttypid, char *compression)
 
 	return cmethod;
 }
+
+/*
+ * Parse the on commit clause for the temporary table
+ */
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			bool	res = false;
+			char	*sval = defGetString(def);
+
+			/* It has to be a Boolean value */
+			if (!parse_bool(sval, &res))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
+
+			if (res)
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 5c4bc15b441..3a861c47946 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"
@@ -1315,6 +1316,27 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = RELATION_IS_GLOBAL_TEMP(relation);
+
+	 /* For global temporary table */
+	if (is_gtt)
+	{
+		/* Store relation statistics and transaction information to the localhash */
+		up_gtt_relstats(RelationGetRelid(relation),
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+
+		/* Update relation statistics to local relcache */
+		relation->rd_rel->relpages = (int32) num_pages;
+		relation->rd_rel->reltuples = (float4) num_tuples;
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+		if (TransactionIdIsNormal(frozenxid))
+			relation->rd_rel->relfrozenxid = frozenxid;
+
+		if (MultiXactIdIsValid(minmulti))
+			relation->rd_rel->relminmxid = minmulti;
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1328,17 +1350,23 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (!is_gtt &&
+		pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (!is_gtt &&
+		pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (!is_gtt &&
+		pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1351,7 +1379,15 @@ vac_update_relstats(Relation relation,
 		/*
 		 * If we didn't find any indexes, reset relhasindex.
 		 */
-		if (pgcform->relhasindex && !hasindex)
+		if (is_gtt &&
+			RelationGetIndexList(relation) != NIL)
+		{
+			/*
+			 * Global temporary tables may contain indexes that are not valid locally.
+			 * The catalog should not be updated based on local invalid index.
+			 */
+		}
+		else if (pgcform->relhasindex && !hasindex)
 		{
 			pgcform->relhasindex = false;
 			dirty = true;
@@ -1383,7 +1419,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNextTransactionId(),
@@ -1394,7 +1431,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1502,6 +1540,13 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/*
+		 * The relfrozenxid for a global temporary talble is stored in localhash,
+		 * not pg_class, See list_all_session_gtt_frozenxids()
+		 */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1559,6 +1604,43 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/* If enable global temporary table */
+	if (max_active_gtt > 0)
+	{
+		TransactionId	safe_age;
+		/*  */
+		TransactionId	oldest_gtt_frozenxid =
+			list_all_backend_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid))
+		{
+			safe_age = oldest_gtt_frozenxid + vacuum_gtt_defer_check_age;
+			if (safe_age < FirstNormalTransactionId)
+				safe_age += FirstNormalTransactionId;
+
+			/*
+			 * We tolerate that the minimum age of gtt is less than
+			 * the minimum age of conventional tables, otherwise it will
+			 * throw warning message.
+			 */
+			if (TransactionIdIsNormal(safe_age) &&
+				TransactionIdPrecedes(safe_age, newFrozenXid))
+			{
+				ereport(WARNING,
+					(errmsg("global temp table oldest relfrozenxid %u is the oldest in the entire db",
+							oldest_gtt_frozenxid),
+					 errdetail("The oldest relfrozenxid in pg_class is %u", newFrozenXid),
+					 errhint("If they differ greatly, please consider cleaning up the data in global temp table.")));
+			}
+
+			/*
+			 * We need to ensure that the clog required by gtt is not cleand.
+			 */
+			if (TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+				newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1910,6 +1992,19 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	/*
+	 * Skip those global temporary table that are not initialized in
+	 * current backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel) &&
+		!gtt_storage_attached(RelationGetRelid(rel)))
+	{
+		relation_close(rel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 4df05a0b33d..4c181e2e14e 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -527,6 +527,12 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	/* Global temporary table are not sensible. */
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b3ce4bae530..611e3f18a70 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -784,6 +784,10 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		/* This is one kind of temp table */
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 5c723bc54e1..191e0f6fd21 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 4ab1302313f..b6dfb46fd57 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -50,6 +50,7 @@
 #include "access/table.h"
 #include "access/tableam.h"
 #include "access/transam.h"
+#include "catalog/storage_gtt.h"
 #include "executor/executor.h"
 #include "executor/execPartition.h"
 #include "jit/jit.h"
@@ -832,7 +833,7 @@ ExecGetRangeTableRelation(EState *estate, Index rti)
  */
 void
 ExecInitResultRelation(EState *estate, ResultRelInfo *resultRelInfo,
-					   Index rti)
+					   Index rti, CmdType operation)
 {
 	Relation	resultRelationDesc;
 
@@ -843,6 +844,9 @@ ExecInitResultRelation(EState *estate, ResultRelInfo *resultRelInfo,
 					  NULL,
 					  estate->es_instrument);
 
+	/* Check and init global temporary table storage in current backend */
+	init_gtt_storage(operation, resultRelationDesc);
+
 	if (estate->es_result_relations == NULL)
 		estate->es_result_relations = (ResultRelInfo **)
 			palloc0(estate->es_range_table_size * sizeof(ResultRelInfo *));
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d328856ae5b..c540874789b 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -38,6 +38,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -2754,13 +2755,13 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	{
 		mtstate->rootResultRelInfo = makeNode(ResultRelInfo);
 		ExecInitResultRelation(estate, mtstate->rootResultRelInfo,
-							   node->rootRelation);
+							   node->rootRelation, operation);
 	}
 	else
 	{
 		mtstate->rootResultRelInfo = mtstate->resultRelInfo;
 		ExecInitResultRelation(estate, mtstate->resultRelInfo,
-							   linitial_int(node->resultRelations));
+							   linitial_int(node->resultRelations), operation);
 	}
 
 	/* set up epqstate with dummy subplan data for the moment */
@@ -2788,7 +2789,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 		if (resultRelInfo != mtstate->rootResultRelInfo)
 		{
-			ExecInitResultRelation(estate, resultRelInfo, resultRelation);
+			ExecInitResultRelation(estate, resultRelInfo, resultRelation, operation);
 
 			/*
 			 * For child result relations, store the root result relation
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 296dd75c1b6..d971aea2546 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -48,7 +48,7 @@
 #include "partitioning/partprune.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/lsyscache.h"
-
+#include "utils/rel.h"
 
 /* results of subquery_is_pushdown_safe */
 typedef struct pushdown_safety_info
@@ -619,7 +619,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			if (RelpersistenceTsTemp(get_rel_persistence(rte->relid)))
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index bd01ec0526f..ff4e81ca2cf 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6071,7 +6071,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
-	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+	if (RELATION_IS_TEMP(heap) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index c5194fdbbf2..38d7c658541 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -30,6 +30,7 @@
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temporary table in current backend */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 146ee8dd1ea..2d4e9393f00 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2907,6 +2907,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 a6d0cefa6bb..947634626d6 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3417,17 +3417,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; }
@@ -11755,19 +11749,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 c5c3f26ecf1..2a2b2789077 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -82,6 +82,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);
 
 
 /*
@@ -3665,3 +3666,53 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * Like function isQueryUsingTempRelation_walker
+ * return true if any relation underlying
+ * the query is a global temporary table.
+ */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* Check if the query uses global temporary table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 313d7b6ff02..38e0e162e82 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -447,6 +447,13 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	/*
+	 * If a sequence is bound to a global temporary table, then the sequence
+	 * must been "global temporary"
+	 */
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
@@ -3326,6 +3333,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		cxt.isforeign = false;
 	}
 	cxt.relation = stmt->relation;
+	/* Sets the table persistence to the context */
+	cxt.relation->relpersistence = RelationGetRelPersistence(rel);
 	cxt.rel = rel;
 	cxt.inhRelations = NIL;
 	cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 96332320a73..75ed4d0ae9e 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2115,6 +2115,14 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/*
+			 * Aotuvacuum cannot vacuum the private data stored in each backend
+			 * that belongs to global temporary table, so skip them.
+			 */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2181,7 +2189,7 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		if (RelpersistenceTsTemp(classForm->relpersistence))
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 08ebabfe96a..c346c59c7f4 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlogutils.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -2934,6 +2935,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * Returns 0 if this global temporary table is not initialized in current
+	 * backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 9fa3e0631e6..cc3eb928bc6 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -23,6 +23,7 @@
 #include "access/syncscan.h"
 #include "access/twophase.h"
 #include "commands/async.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
@@ -143,6 +144,7 @@ CalculateShmemSize(int *num_semaphores)
 	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
@@ -246,6 +248,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* For global temporary table shared hashtable */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index a9945c80eb4..63d9d2ee80f 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -65,6 +65,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -5161,3 +5162,82 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temporary table.
+ */
+int
+list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct		*arrayP = NULL;
+	TransactionId		result = InvalidTransactionId;
+	int			index = 0;
+	int			i = 0;
+	uint8		flags = 0;
+
+	if (n)
+		*n = 0;
+
+	/* return 0 if feature is disabled */
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+	}
+
+	/* Disable in standby node */
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	arrayP = procArray;
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		uint8           statusFlags = ProcGlobal->statusFlags[index];
+
+		if (statusFlags & flags)
+			continue;
+
+		/* Fetch all backend that is belonging to MyDatabaseId */
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->backend_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->backend_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->backend_gtt_frozenxid, result))
+				result = proc->backend_gtt_frozenxid;
+
+			/* save backend pid and backend level oldest relfrozenxid */
+			if (pids)
+				pids[i] = proc->pid;
+
+			if (xids)
+				xids[i] = proc->backend_gtt_frozenxid;
+
+			i++;
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (n)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 862097352bb..4edd3b31f7a 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -176,7 +176,9 @@ static const char *const BuiltinTrancheNames[] = {
 	/* LWTRANCHE_PARALLEL_APPEND: */
 	"ParallelAppend",
 	/* LWTRANCHE_PER_XACT_PREDICATE_LIST: */
-	"PerXactPredicateList"
+	"PerXactPredicateList",
+	/* LWTRANCHE_GTT_CTL */
+	"GlobalTempTableControl"
 };
 
 StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index b7d9da0aa9f..8051f2053f9 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -393,6 +393,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;	/* init backend level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
@@ -578,6 +579,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;	/* init backend level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index d5a7fb13f3c..8225cf6219f 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -982,6 +982,13 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/*
+			 * For global temporary table ,each backend has its own storage,
+			 * also only sees its own storage. Use Backendid to identify them.
+			 */
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 10895fb2876..66255eb7604 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -108,6 +108,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -5115,12 +5116,26 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								/* For global temporary table, get statistic data from localhash */
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -5368,15 +5383,28 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6820,6 +6848,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;
@@ -6837,6 +6866,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6848,6 +6885,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;
@@ -6863,6 +6902,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7781,6 +7828,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))
@@ -7793,6 +7842,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7805,6 +7863,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.
@@ -7824,6 +7884,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 4ebaa552a27..78c33d2ac87 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -3113,6 +3114,19 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		/* For global temporary table, get statistic data from localhash */
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 9fa9e671a11..65b40e58943 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -65,6 +65,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1152,6 +1153,36 @@ retry:
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+				TransactionId	relfrozenxid = InvalidTransactionId;
+				MultiXactId 	relminmxid = InvalidMultiXactId;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+
+				/* For global temporary table, get relstat data from localhash */
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								&relfrozenxid,
+								&relminmxid);
+
+				/* And put them to local relcache */
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+				if (TransactionIdIsNormal(relfrozenxid))
+					relation->rd_rel->relfrozenxid = relfrozenxid;
+
+				if (MultiXactIdIsValid(relminmxid))
+					relation->rd_rel->relminmxid = relminmxid;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1209,6 +1240,8 @@ retry:
 		case RELKIND_PARTITIONED_INDEX:
 			Assert(relation->rd_rel->relam != InvalidOid);
 			RelationInitIndexAccessInfo(relation);
+			/* The state of the global temporary table's index may need to be set */
+			gtt_fix_index_backend_state(relation);
 			break;
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
@@ -1351,7 +1384,22 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+			/*
+			 * For global temporary table, get the latest relfilenode
+			 * from localhash and put it in relcache.
+			 */
+			if (OidIsValid(newrelnode) &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2302,6 +2350,9 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		/* The state of the global temporary table's index may need to be set */
+		gtt_fix_index_backend_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3582,6 +3633,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3691,28 +3746,39 @@ void
 RelationSetNewRelfilenode(Relation relation, char persistence)
 {
 	Oid			newrelfilenode;
-	Relation	pg_class;
-	HeapTuple	tuple;
+	Relation	pg_class = NULL;
+	HeapTuple	tuple = NULL;
 	Form_pg_class classform;
 	MultiXactId minmulti = InvalidMultiXactId;
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
 
+	/*
+	 * For global temporary table, storage information for the table is
+	 * maintained locally, not in catalog.
+	 */
+	bool		update_catalog = !RELATION_IS_GLOBAL_TEMP(relation);
+
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
 
-	/*
-	 * Get a writable copy of the pg_class tuple for the given relation.
-	 */
-	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+	memset(&classform, 0, sizeof(classform));
 
-	tuple = SearchSysCacheCopy1(RELOID,
-								ObjectIdGetDatum(RelationGetRelid(relation)));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "could not find tuple for relation %u",
-			 RelationGetRelid(relation));
-	classform = (Form_pg_class) GETSTRUCT(tuple);
+	if (update_catalog)
+	{
+		/*
+		 * Get a writable copy of the pg_class tuple for the given relation.
+		 */
+		pg_class = table_open(RelationRelationId, RowExclusiveLock);
+
+		tuple = SearchSysCacheCopy1(RELOID,
+									ObjectIdGetDatum(RelationGetRelid(relation)));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u",
+				 RelationGetRelid(relation));
+		classform = (Form_pg_class) GETSTRUCT(tuple);
+	}
 
 	/*
 	 * Schedule unlinking of the old storage at transaction commit.
@@ -3738,7 +3804,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;
@@ -3758,6 +3824,18 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 			break;
 	}
 
+	/* For global temporary table */
+	if (!update_catalog)
+	{
+		Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+		Assert(RELATION_IS_GLOBAL_TEMP(relation));
+		Assert(!RelationIsMapped(relation));
+
+		/* Make cache invalid and set new relnode to local cache. */
+		CacheInvalidateRelcache(relation);
+		relation->rd_node.relNode = relnode;
+	}
 	/*
 	 * If we're dealing with a mapped index, pg_class.relfilenode doesn't
 	 * change; instead we have to send the update to the relation mapper.
@@ -3767,7 +3845,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	 * possibly-inaccurate values of relpages etc, but those will be fixed up
 	 * later.
 	 */
-	if (RelationIsMapped(relation))
+	else if (RelationIsMapped(relation))
 	{
 		/* This case is only supported for indexes */
 		Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3813,9 +3891,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 		CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 	}
 
-	heap_freetuple(tuple);
+	if (update_catalog)
+	{
+		heap_freetuple(tuple);
 
-	table_close(pg_class, RowExclusiveLock);
+		table_close(pg_class, RowExclusiveLock);
+	}
 
 	/*
 	 * Make the pg_class row change or relation map change visible.  This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index e91d5a3cfda..b8ec6b3ca46 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -44,6 +44,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "commands/prepare.h"
 #include "commands/trigger.h"
@@ -153,6 +154,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temporary table feature.
+ * table schema are still saved in catalog.
+ *
+ * num > 0 means allows the database to manage multiple active tables at the same time.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2128,6 +2141,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 "
@@ -2698,6 +2720,16 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"vacuum_gtt_defer_check_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("The defer check age of GTT, used to check expired data after vacuum."),
+			NULL
+		},
+		&vacuum_gtt_defer_check_age,
+		10000, 0, 1000000,
+		NULL, NULL, NULL
+	},
+
 	/*
 	 * See also CheckRequiredParameterValues() if this parameter changes
 	 */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 7e98371d253..35a065b01eb 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2504,6 +2504,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
 		dopt->no_unlogged_table_data)
 		return;
 
+	/* Don't dump data in global temporary table/sequence */
+	if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		return;
+
 	/* Check that the data is not explicitly excluded */
 	if (simple_oid_list_member(&tabledata_exclude_oids,
 							   tbinfo->dobj.catId.oid))
@@ -15815,6 +15819,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
 		char	   *foreign = "";
+		char	   *table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15868,9 +15873,15 @@ dumpTableSchema(Archive *fout, const 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);
 
@@ -16234,6 +16245,15 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 			}
 		}
 
+		/*
+		 * Transaction information for the global temporary table is not stored
+		 * in the pg_class.
+		 */
+		if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			Assert(tbinfo->frozenxid == 0);
+			Assert(tbinfo->minmxid == 0);
+		}
 		/*
 		 * In binary_upgrade mode, arrange to restore the old relfrozenxid and
 		 * relminmxid of all vacuumable relations.  (While vacuum.c processes
@@ -16241,7 +16261,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		 * of other relations; so this "if" lacks RELKIND_TOASTVALUE, and the
 		 * child toast table is handled below.)
 		 */
-		if (dopt->binary_upgrade &&
+		else if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
 			 tbinfo->relkind == RELKIND_MATVIEW))
 		{
@@ -17244,6 +17264,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	PQExpBuffer query = createPQExpBuffer();
 	PQExpBuffer delqry = createPQExpBuffer();
 	char	   *qseqname;
+	bool	   global_temp_seq = false;
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
@@ -17253,9 +17274,12 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
-						  "FROM pg_catalog.pg_sequence "
-						  "WHERE seqrelid = '%u'::oid",
+						  "seqcache, seqcycle, "
+						  "c.relpersistence "
+						  "FROM pg_catalog.pg_sequence s, "
+						  "pg_catalog.pg_class c "
+						  "WHERE seqrelid = '%u'::oid "
+						  "and s.seqrelid = c.oid",
 						  tbinfo->dobj.catId.oid);
 	}
 	else if (fout->remoteVersion >= 80400)
@@ -17300,6 +17324,9 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
 
+	if (fout->remoteVersion >= 140000)
+		global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
 	if (strcmp(seqtype, "smallint") == 0)
@@ -17377,9 +17404,13 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	}
 	else
 	{
-		appendPQExpBuffer(query,
-						  "CREATE SEQUENCE %s\n",
-						  fmtQualifiedDumpable(tbinfo));
+		appendPQExpBuffer(query, "CREATE ");
+
+		if (global_temp_seq)
+			appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+		appendPQExpBuffer(query, "SEQUENCE %s\n",
+								  fmtQualifiedDumpable(tbinfo));
 
 		if (strcmp(seqtype, "bigint") != 0)
 			appendPQExpBuffer(query, "    AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index ad5f3919956..f3819860096 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -88,7 +88,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -178,7 +178,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 5d9a26cf822..2de11d5d707 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -447,8 +449,17 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
 			 "         ON c.relnamespace = n.oid "
 			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
+			 CppAsString2(RELKIND_MATVIEW) ") AND ");
+
+	if (skip_gtt)
+	{
+		/* exclude global temp tables */
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+			"    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND ");
+	}
+
 	/* exclude possible orphaned temp tables */
+	snprintf(query + strlen(query), sizeof(query) - strlen(query),
 			 "    ((n.nspname !~ '^pg_temp_' AND "
 			 "      n.nspname !~ '^pg_toast_temp_' AND "
 			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 3628bd74a7b..bbb9b5ea13d 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -407,7 +407,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -645,7 +645,10 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+			/* exclude global temp tables */
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -656,7 +659,10 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+		/* exclude global temp tables */
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index ca0795f68ff..018a2effd4b 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -389,7 +389,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index ea721d963a7..5967885dad1 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -4089,7 +4089,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		if (pset.sversion >= 90100)
 		{
 			appendPQExpBuffer(&buf,
-							  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+							  gettext_noop("session"),
 							  gettext_noop("permanent"),
 							  gettext_noop("temporary"),
 							  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index fa2e19593c5..a3e7f1380f7 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1059,6 +1059,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2614,6 +2616,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2844,6 +2849,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE", "SEQUENCE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 6ce480b49c1..dda3f3c5a60 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -85,7 +85,7 @@ extern Oid	heap_create_with_catalog(const char *relname,
 
 extern void heap_drop_with_catalog(Oid relid);
 
-extern void heap_truncate(List *relids);
+extern void heap_truncate(List *relids, bool is_global_temp);
 
 extern void heap_truncate_one_rel(Relation rel);
 
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 008f723e104..875b1003899 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -157,6 +157,7 @@ extern void reindex_index(Oid indexId, bool skip_constraint_checks,
 #define REINDEX_REL_CHECK_CONSTRAINTS		0x04
 #define REINDEX_REL_FORCE_INDEXES_UNLOGGED	0x08
 #define REINDEX_REL_FORCE_INDEXES_PERMANENT 0x10
+#define REINDEX_REL_PROCESS_GLOBAL_TEMP		0x20
 
 extern bool reindex_relation(Oid relid, int flags, ReindexParams *params);
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index fef9945ed8f..9176b7dcc07 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -172,6 +172,7 @@ DECLARE_INDEX(pg_class_tblspc_relfilenode_index, 3455, ClassTblspcRelfilenodeInd
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 6412f369f18..54f6adec4ab 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5739,6 +5739,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '9874',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '9875',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '9876',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '9877',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 0ab32b44e91..92e9f8ba485 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,7 +22,7 @@
 /* GUC variables */
 extern int	wal_skip_threshold;
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationPreTruncate(Relation rel);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 00000000000..cc023da8acd
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,45 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern int		vacuum_gtt_defer_check_age;
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Oid relid,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void force_enable_gtt_index(Relation index);
+extern void gtt_fix_index_backend_state(Relation index);
+extern void init_gtt_storage(CmdType operation, Relation relation);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index 40544dd4c70..7b66d808fc5 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 336549cc5f0..3e8167134b7 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -86,7 +86,7 @@ extern void find_composite_type_dependencies(Oid typeOid,
 
 extern void check_of_type(HeapTuple typetuple);
 
-extern void register_on_commit_action(Oid relid, OnCommitAction action);
+extern void register_on_commit_action(Oid relid, OnCommitAction action, bool is_gloal_temp);
 extern void remove_on_commit_action(Oid relid);
 
 extern void PreCommit_on_commit_actions(void);
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index cd57a704adc..717632637a9 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -575,7 +575,7 @@ exec_rt_fetch(Index rti, EState *estate)
 
 extern Relation ExecGetRangeTableRelation(EState *estate, Index rti);
 extern void ExecInitResultRelation(EState *estate, ResultRelInfo *resultRelInfo,
-								   Index rti);
+								   Index rti, CmdType operation);
 
 extern int	executor_errposition(EState *estate, int location);
 
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 8336c2c5a29..bddcfe7256d 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index c86ccdaf608..6b395551c1d 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -399,6 +399,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index a8f052e4845..4b4ed1a13aa 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -189,6 +189,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_SHARED_TIDBITMAP,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_PER_XACT_PREDICATE_LIST,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index cfabfdbedf1..e8fe452017f 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -164,6 +164,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId backend_gtt_frozenxid;	/* backend level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index b01fa52139a..8efffa55ac5 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -94,4 +94,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index aa18d304ac0..524c9d7de3f 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -288,6 +288,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 b4faa1c1238..a74558a8383 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	SMgrRelation rd_smgr;		/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -326,6 +326,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -608,11 +609,13 @@ RelationGetSmgr(Relation rel)
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -620,6 +623,7 @@ RelationGetSmgr(Relation rel)
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -632,6 +636,30 @@ RelationGetSmgr(Relation rel)
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP_ON_CURRENT_SESSION
+ *		Test a rel is either local temp relation of this session
+ *		or global temp relation.
+ */
+#define RELATION_IS_TEMP_ON_CURRENT_SESSION(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is local temp relation or global temporary relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RelpersistenceTsTemp
+ *		Test a relpersistence is local temp relation or global temporary relation.
+ */
+#define RelpersistenceTsTemp(relpersistence) \
+	(relpersistence == RELPERSISTENCE_TEMP || \
+	 relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -677,6 +705,19 @@ RelationGetSmgr(Relation rel)
 	 (relation)->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&	\
 	 !IsCatalogRelation(relation))
 
+/* For global temporary table */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/* Get on commit clause value only for global temporary table */
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+/* Get relpersistence for relation */
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
-- 
2.30.1 (Apple Git-130)

#344wenjing zeng
wjzeng2012@gmail.com
In reply to: wenjing (#343)
4 attachment(s)
Re: [Proposal] Global temporary tables

Post GTT v63 to fixed conflicts with the latest code.

Hi Andrew

Have you found any new bugs recently?

Wenjing

Show quoted text

2021年11月20日 01:31,wenjing <wjzeng2012@gmail.com> 写道:

Andrew Bille <andrewbille@gmail.com <mailto:andrewbille@gmail.com>> 于2021年11月15日周一 下午6:34写道:
Thanks for the patches. The feature has become much more stable.
However, there is another simple case that generates an error:
Master with v61 patches

CREATE GLOBAL TEMPORARY TABLE t AS SELECT 1 AS a;
ERROR: could not open file "base/13560/t3_16384": No such file or directory
Thank you for pointing out that this part is not reasonable enough.
This issue has been fixed in v62.
Looking forward to your reply.

Wenjing

Andrew

On Thu, Nov 11, 2021 at 3:15 PM wenjing <wjzeng2012@gmail.com <mailto:wjzeng2012@gmail.com>> wrote:
Fixed a bug in function pg_gtt_attached_pid.
Looking forward to your reply.

Wenjing

<0001-gtt-v62-reademe.patch><0004-gtt-v62-regress.patch><0002-gtt-v62-doc.patch><0003-gtt-v62-implementation.patch>

Attachments:

0001-gtt-v63-reademe.patchapplication/octet-stream; name=0001-gtt-v63-reademe.patch; x-unix-mode=0644Download
diff --git a/README.gtt.txt b/README.gtt.txt
new file mode 100644
index 00000000000..d181df9acd7
--- /dev/null
+++ b/README.gtt.txt
@@ -0,0 +1,172 @@
+Global Temporary Table(GTT)
+=========================================
+
+Feature description
+-----------------------------------------
+
+Previously, temporary tables are defined once and automatically
+exist (starting with empty contents) in every session before using them.
+
+The temporary table implementation in PostgreSQL, known as Local temp tables(LTT),
+did not fully comply with the SQL standard. This version added the support of
+Global Temporary Table .
+
+The metadata of Global Temporary Table is persistent and shared among sessions.
+The data stored in the Global temporary table is independent of sessions. This
+means, when a session creates a Global Temporary Table and writes some data.
+Other sessions cannot see those data, but they have an empty Global Temporary
+Table with same schema.
+
+Like local temporary table, Global Temporary Table supports ON COMMIT PRESERVE ROWS
+or ON COMMIT DELETE ROWS clause, so that data in the temporary table can be
+cleaned up or preserved automatically when a session exits or a transaction COMMITs.
+
+Unlike Local Temporary Table, Global Temporary Table does not support
+ON COMMIT DROP clauses.
+
+In following paragraphs, we use GTT for Global Temporary Table and LTT for
+local temporary table.
+
+Main design ideas
+-----------------------------------------
+In general, GTT and LTT use the same storage and buffer design and
+implementation. The storage files for both types of temporary tables are named
+as t_backendid_relfilenode, and the local buffer is used to cache the data.
+
+The schema of GTTs is shared among sessions while their data are not. We build
+a new mechanisms to manage those non-shared data and their statistics.
+Here is the summary of changes:
+
+1) CATALOG
+GTTs store session-specific data. The storage information of GTTs'data, their
+transaction information, and their statistics are not stored in the catalog.
+
+2) STORAGE INFO & STATISTICS INFO & TRANSACTION INFO
+In order to maintain durability and availability of GTTs'session-specific data,
+their storage information, statistics, and transaction information is managed
+in a local hash table tt_storage_local_hash.
+
+3) DDL
+Currently, GTT supports almost all table'DDL except CLUSTER/VACUUM FULL.
+Part of the DDL behavior is limited by shared definitions and multiple copies of
+local data, and we added some structures to handle this.
+
+A shared hash table active_gtt_shared_hash is added to track the state of the
+GTT in a different session. This information is recorded in the hash table
+during the DDL execution of the GTT.
+
+The data stored in a GTT can only be modified or accessed by owning session.
+The statements that only modify data in a GTT do not need a high level of
+table locking. The operations making those changes include truncate GTT,
+reindex GTT, and lock GTT.
+
+4) MVCC commit log(clog) cleanup
+Each GTT in a session has its own piece of data, and they have their own
+transaction information. We set up data structures to track and maintain
+this information. The cleaning of CLOGs also needs to consider the transaction
+information of GTT.
+
+Detailed design
+-----------------------------------------
+
+1. CATALOG
+1.1 relpersistence
+define RELPERSISTENCEGLOBALTEMP 'g'
+Mark Global Temporary Table in pg_class relpersistence to 'g'. The relpersistence
+of indexes created on the GTT, sequences on GTT and toast tables on GTT are
+also set to 'g'
+
+1.2 on commit clause
+LTT's status associated with on commit DELETE ROWS and on commit PRESERVE ROWS
+is not stored in catalog. Instead, GTTs need a bool value on_commit_delete_rows
+in reloptions which is shared among sessions.
+
+1.3 gram.y
+GTT is already supported in syntax tree. We remove the warning message
+"GLOBAL is deprecated in temporary table creation" and mark
+relpersistence = RELPERSISTENCEGLOBALTEMP.
+
+2. STORAGE INFO & STATISTICS INFO & TRANSACTION INFO
+2.1. gtt_storage_local_hash
+Each backend creates a local hashtable gtt_storage_local_hash to track a GTT's
+storage file information, statistics, and transaction information.
+
+2.2 GTT storage file info track
+1) When one session inserts data into a GTT for the first time, record the
+storage info to gtt_storage_local_hash.
+2) Use beforeshmemexit to ensure that all files of session GTT are deleted when
+the session exits.
+
+2.3 statistics info
+1) relpages reltuples relallvisible relfilenode
+2) The statistics of each column from pg_statistic
+All the above information is stored in gtt_storage_local_hash.
+When doing vacuum or analyze, GTT's statistic is updated, which is used by
+the SQL planner.
+The statistics summarizes only data in the current session.
+
+2.3 transaction info track
+frozenxid minmulti from pg_class is stored to gtt_storage_local_hash.
+
+3 DDL
+3.1. active_gtt_shared_hash
+This is the hash table created in shared memory to trace the GTT files initialized
+in each session. Each hash entry contains a bitmap that records the backendid of
+the initialized GTT file. With this hash table, we know which backend/session
+is using this GTT. Such information is used during GTT's DDL operations.
+
+3.2 DROP GTT
+One GTT is allowed to be deleted when there is only one session using the table
+and the session is the current session. After holding the lock on GTT,
+active_gtt_shared_hash is checked to ensure that.
+
+3.3 ALTER GTT/DROP INDEX ON GTT
+Same as drop GTT.
+
+3.4 CREATE INDEX ON GTT
+1) create index on GTT statements build index based on local data in a session.
+2) After the index is created, record the index metadata to the catalog.
+3) Other sessions can enable or disable the local GTT index.
+
+3.5 TRUNCATE/REINDEX GTT
+The SQL truncate/reindex command open the GTT using AccessShareLock lock,
+not AccessExclusiveLock, because this command only cleans up local data and
+local buffers in current session. This allows these operations to be executed
+concurrently between sessions, unlike normal tables.
+
+3.6 LOCK GTT
+A lock GTT statement does not hold any relation lock.
+
+3.7 CLUSTER GTT/VACUUM FULL GTT
+The current version does not support.
+
+4 MVCC commit log(clog) cleanup
+
+The GTT storage file contains transaction information. Queries for GTT data rely
+on transaction information such as clog. The transaction information required by
+each session may be completely different. We need to ensure that the transaction
+information of the GTT data is not cleaned up during its lifetime and that
+transaction resources are recycled at the instance level.
+
+4.1 The session level GTT oldest frozenxid
+1) To manage all GTT transaction information, add session level oldest frozenxid
+in each session. When one GTT is created or removed, record the session level
+oldest frozenxid and store it in MyProc.
+2) When vacuum advances the database's frozenxid, session level oldest frozenxid
+should be considered. This is acquired by searching all of MyProc. This way,
+we can avoid the clog required by GTTs to be cleaned.
+
+4.2 vacuum GTT
+Users can perform vacuum over a GTT to clean up local data in the GTT.
+
+4.3 autovacuum GTT
+Autovacuum skips all GTTs, because the data in GTTs is only visible in current session.
+
+5 OTHERS
+5.1 Parallel query
+Planner does not produce parallel query plans for SQL related to GTT. Because
+GTT private data cannot be accessed across processes.
+
+5.2 WAL and Logical replication
+Like LTT, the DML on GTT does not record WAL and is not parsed or replay by
+the logical replication.
\ No newline at end of file
-- 
2.30.1 (Apple Git-130)

0002-gtt-v63-doc.patchapplication/octet-stream; name=0002-gtt-v63-doc.patch; x-unix-mode=0644Download
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 473a0a4aeb..e510bde8ac 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -169,32 +169,67 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     <listitem>
      <para>
       If specified, the table is created as a temporary table.
-      Temporary tables are automatically dropped at the end of a
-      session, or optionally at the end of the current transaction
-      (see <literal>ON COMMIT</literal> below).  The default
-      search_path includes the temporary schema first and so identically
-      named existing permanent tables are not chosen for new plans
+      Optionally, <literal>GLOBAL</literal> or <literal>LOCAL</literal>
+      can be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
+      They represent two types of temporary tables supported by <productname>PostgreSQL</productname>:
+      global temporary table and local temporary table. Without specified
+      GLOBAL or LOCAL, a local temporary table is created by default.
+     </para>
+
+    <para>
+     Both types of temporary tables’ data are truncated at the
+     end of a session or optionally at the end of the current transaction.
+     (see <literal>ON COMMIT</literal> below). For global temporary table,
+     its schema is reserved and reused by future sessions or transactions.
+     For local temporary table, both its data and its schema are dropped.
+    </para>
+
+    <variablelist>
+     <varlistentry>
+      <term><literal>Global Temporary Table</literal></term>
+      <listitem>
+       <para>
+        Global temporary table are defined just once and automatically exist
+        (starting with empty contents) in every session that needs them.
+        The schema definition of temporary tables is persistent and shared among sessions.
+        However, the data in temporary tables are kept private to sessions themselves,
+        even though they use same name and same schema.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>Local Temporary Table</literal></term>
+     <listitem>
+     <para>
+      Local temporary table are automatically dropped at the end of a
+      session (include schema and data). Future sessions need to create
+      their own temporary tables when they are used.
+     </para>
+     <para>
+      The default search_path includes the temporary schema first and so
+      identically named existing permanent tables are not chosen for new plans
       while the temporary table exists, unless they are referenced
       with schema-qualified names. Any indexes created on a temporary
       table are automatically temporary as well.
      </para>
+     </listitem>
+     </varlistentry>
+    </variablelist>
 
-     <para>
-      The <link linkend="autovacuum">autovacuum daemon</link> cannot
-      access and therefore cannot vacuum or analyze temporary tables.
-      For this reason, appropriate vacuum and analyze operations should be
-      performed via session SQL commands.  For example, if a temporary
-      table is going to be used in complex queries, it is wise to run
-      <command>ANALYZE</command> on the temporary table after it is populated.
-     </para>
+    <para>
+     The <link linkend="autovacuum">autovacuum daemon</link> cannot
+     access and therefore cannot vacuum or analyze temporary tables.
+     For this reason, appropriate vacuum and analyze operations should be
+     performed via session SQL commands.  For example, if a temporary
+     table is going to be used in complex queries, it is wise to run
+     <command>ANALYZE</command> on the temporary table after it is populated.
+    </para>
+    <para>
+     The Temporary table resembles the SQL standard, but has some differences.
+     see <xref linkend="sql-createtable-compatibility"/> below.
+    </para>
 
-     <para>
-      Optionally, <literal>GLOBAL</literal> or <literal>LOCAL</literal>
-      can be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
-      This presently makes no difference in <productname>PostgreSQL</productname>
-      and is deprecated; see
-      <xref linkend="sql-createtable-compatibility"/> below.
-     </para>
     </listitem>
    </varlistentry>
 
@@ -2133,13 +2168,17 @@ CREATE TABLE cities_partdef
    <title>Temporary Tables</title>
 
    <para>
-    Although the syntax of <literal>CREATE TEMPORARY TABLE</literal>
-    resembles that of the SQL standard, the effect is not the same.  In the
-    standard,
-    temporary tables are defined just once and automatically exist (starting
-    with empty contents) in every session that needs them.
-    <productname>PostgreSQL</productname> instead
-    requires each session to issue its own <literal>CREATE TEMPORARY
+    Although the syntax of <literal>CREATE GLOBAL/LOCAL TEMPORARY TABLE</literal>
+    resembles that of the SQL standard, the effect is not the same.
+    The global temporary table follows the SQL standards while local temporary
+    table does not.
+   </para>
+
+   <para>
+    First, in the standard, both global and local temporary tables are defined just
+    once and automatically exist (starting with empty contents) in every session
+    that needs them. For local temporary tables, <productname>PostgreSQL</productname>
+    instead requires each session to issue its own <literal>CREATE LOCAL TEMPORARY
     TABLE</literal> command for each temporary table to be used.  This allows
     different sessions to use the same temporary table name for different
     purposes, whereas the standard's approach constrains all instances of a
@@ -2147,29 +2186,14 @@ CREATE TABLE cities_partdef
    </para>
 
    <para>
-    The standard's definition of the behavior of temporary tables is
-    widely ignored.  <productname>PostgreSQL</productname>'s behavior
-    on this point is similar to that of several other SQL databases.
-   </para>
-
-   <para>
-    The SQL standard also distinguishes between global and local temporary
+    Second, the SQL standard distinguishes between global and local temporary
     tables, where a local temporary table has a separate set of contents for
     each SQL module within each session, though its definition is still shared
-    across sessions.  Since <productname>PostgreSQL</productname> does not
+    across sessions. Since <productname>PostgreSQL</productname> does not
     support SQL modules, this distinction is not relevant in
     <productname>PostgreSQL</productname>.
    </para>
 
-   <para>
-    For compatibility's sake, <productname>PostgreSQL</productname> will
-    accept the <literal>GLOBAL</literal> and <literal>LOCAL</literal> keywords
-    in a temporary table declaration, but they currently have no effect.
-    Use of these keywords is discouraged, since future versions of
-    <productname>PostgreSQL</productname> might adopt a more
-    standard-compliant interpretation of their meaning.
-   </para>
-
    <para>
     The <literal>ON COMMIT</literal> clause for temporary tables
     also resembles the SQL standard, but has some differences.
@@ -2177,7 +2201,8 @@ CREATE TABLE cities_partdef
     default behavior is <literal>ON COMMIT DELETE ROWS</literal>.  However, the
     default behavior in <productname>PostgreSQL</productname> is
     <literal>ON COMMIT PRESERVE ROWS</literal>.  The <literal>ON COMMIT
-    DROP</literal> option does not exist in SQL.
+    DROP</literal> option does not exist in SQL and is not supported by
+    global temporary table.
    </para>
   </refsect2>
 
-- 
2.30.1 (Apple Git-130)

0003-gtt-v63-implementation.patchapplication/octet-stream; name=0003-gtt-v63-implementation.patch; x-unix-mode=0644Download
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index a23d0182fc0..74a28d7de8d 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -18,6 +18,7 @@
 #include "access/toast_internals.h"
 #include "access/visibilitymap.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "funcapi.h"
 #include "miscadmin.h"
 #include "storage/bufmgr.h"
@@ -226,6 +227,8 @@ verify_heapam(PG_FUNCTION_ARGS)
 	BlockNumber last_block;
 	BlockNumber nblocks;
 	const char *skip;
+	TransactionId	relfrozenxid = InvalidTransactionId;
+	MultiXactId		relminmxid = InvalidMultiXactId;
 
 	/* Check to see if caller supports us returning a tuplestore */
 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -340,6 +343,13 @@ verify_heapam(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(ctx.rel) &&
+		!gtt_storage_attached(RelationGetRelid(ctx.rel)))
+	{
+		relation_close(ctx.rel, AccessShareLock);
+		PG_RETURN_NULL();
+	}
+
 	/* Early exit if the relation is empty */
 	nblocks = RelationGetNumberOfBlocks(ctx.rel);
 	if (!nblocks)
@@ -407,9 +417,25 @@ verify_heapam(PG_FUNCTION_ARGS)
 
 	update_cached_xid_range(&ctx);
 	update_cached_mxid_range(&ctx);
-	ctx.relfrozenxid = ctx.rel->rd_rel->relfrozenxid;
+
+	if (RELATION_IS_GLOBAL_TEMP(ctx.rel))
+	{
+		get_gtt_relstats(RelationGetRelid(ctx.rel),
+						NULL,
+						NULL,
+						NULL,
+						&relfrozenxid,
+						&relminmxid);
+	}
+	else
+	{
+		relfrozenxid = ctx.rel->rd_rel->relfrozenxid;
+		relminmxid = ctx.rel->rd_rel->relminmxid;
+	}
+
+	ctx.relfrozenxid = relfrozenxid;
 	ctx.relfrozenfxid = FullTransactionIdFromXidAndCtx(ctx.relfrozenxid, &ctx);
-	ctx.relminmxid = ctx.rel->rd_rel->relminmxid;
+	ctx.relminmxid = relminmxid;
 
 	if (TransactionIdIsNormal(ctx.relfrozenxid))
 		ctx.oldest_xid = ctx.relfrozenxid;
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index b5602f53233..21b2d2a9527 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -159,6 +159,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * In order to avoid consistency problems, the global temporary table
+	 * uses ShareUpdateExclusiveLock.
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temporary table on commit options",
+			RELOPT_KIND_HEAP,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},
 	/* list terminator */
 	{{NULL}}
 };
@@ -1834,6 +1847,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 94dbabc1988..f31bb2f318c 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1024,7 +1024,7 @@ gistproperty(Oid index_oid, int attno,
 XLogRecPtr
 gistGetFakeLSN(Relation rel)
 {
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	if (RELATION_IS_TEMP(rel))
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 81c7da7ec69..febc98e1b9c 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -152,7 +152,7 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	if (!RELATION_IS_TEMP(index))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 0b4a46b31ba..0d32b5d2d93 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -52,6 +52,7 @@
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/atomics.h"
@@ -5825,6 +5826,19 @@ heap_abort_speculative(Relation relation, ItemPointer tid)
 	BlockNumber block;
 	Buffer		buffer;
 	TransactionId prune_xid;
+	TransactionId relfrozenxid = InvalidTransactionId;
+
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		get_gtt_relstats(RelationGetRelid(relation),
+						NULL,
+						NULL,
+						NULL,
+						&relfrozenxid,
+						NULL);
+	}
+	else
+		relfrozenxid = relation->rd_rel->relfrozenxid;
 
 	Assert(ItemPointerIsValid(tid));
 
@@ -5877,8 +5891,8 @@ heap_abort_speculative(Relation relation, ItemPointer tid)
 	 * TransactionXmin, so there's no race here).
 	 */
 	Assert(TransactionIdIsValid(TransactionXmin));
-	if (TransactionIdPrecedes(TransactionXmin, relation->rd_rel->relfrozenxid))
-		prune_xid = relation->rd_rel->relfrozenxid;
+	if (TransactionIdPrecedes(TransactionXmin, relfrozenxid))
+		prune_xid = relfrozenxid;
 	else
 		prune_xid = TransactionXmin;
 	PageSetPrunable(page, prune_xid);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 9befe012a9e..26fce0c4b83 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -593,7 +593,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
@@ -645,7 +645,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(RelationGetSmgr(rel), dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index db6becfed54..f43ebc13e6e 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -47,6 +47,7 @@
 #include "access/xlog.h"
 #include "catalog/index.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -496,6 +497,25 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 	TransactionId OldestXmin;
 	TransactionId FreezeLimit;
 	MultiXactId MultiXactCutoff;
+	TransactionId	relfrozenxid = InvalidTransactionId;
+	MultiXactId		relminmxid = InvalidMultiXactId;
+	double			reltuples = 0;
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		get_gtt_relstats(RelationGetRelid(rel),
+						NULL,
+						&reltuples,
+						NULL,
+						&relfrozenxid,
+						&relminmxid);
+	}
+	else
+	{
+		relfrozenxid = rel->rd_rel->relfrozenxid;
+		relminmxid = rel->rd_rel->relminmxid;
+		reltuples = rel->rd_rel->reltuples;
+	}
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
@@ -531,9 +551,9 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 	 * table's minimum MultiXactId is older than or equal to the requested
 	 * mxid full-table scan limit; or if DISABLE_PAGE_SKIPPING was specified.
 	 */
-	aggressive = TransactionIdPrecedesOrEquals(rel->rd_rel->relfrozenxid,
+	aggressive = TransactionIdPrecedesOrEquals(relfrozenxid,
 											   xidFullScanLimit);
-	aggressive |= MultiXactIdPrecedesOrEquals(rel->rd_rel->relminmxid,
+	aggressive |= MultiXactIdPrecedesOrEquals(relminmxid,
 											  mxactFullScanLimit);
 	if (params->options & VACOPT_DISABLE_PAGE_SKIPPING)
 		aggressive = true;
@@ -580,9 +600,9 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 	}
 
 	vacrel->bstrategy = bstrategy;
-	vacrel->relfrozenxid = rel->rd_rel->relfrozenxid;
-	vacrel->relminmxid = rel->rd_rel->relminmxid;
-	vacrel->old_live_tuples = rel->rd_rel->reltuples;
+	vacrel->relfrozenxid = relfrozenxid;
+	vacrel->relminmxid = relminmxid;
+	vacrel->old_live_tuples = reltuples;
 
 	/* Set cutoffs for entire VACUUM */
 	vacrel->OldestXmin = OldestXmin;
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 0aea476b8ce..1e8a5d6882c 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -677,6 +678,14 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		/*
+		 * If a global temporary table storage file is not initialized in the
+		 * current backend, its index does not have a root page, just returns 0.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 4e6efda97f3..ae12fd91a1b 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -44,6 +44,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index aa7d4d5456b..595cb03eb4a 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -504,6 +504,7 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 6780ec53b7c..8aaa54e9a34 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -62,6 +62,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,
@@ -343,13 +345,26 @@ heap_create(const char *relname,
 	if (!RELKIND_HAS_TABLESPACE(relkind))
 		reltablespace = InvalidOid;
 
+	/* For global temporary table, even if the storage is not initialized,
+	 * the relfilenode needs to be generated and put into the catalog.
+	 */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		create_storage = false;
+		if (OidIsValid(relfilenode))
+			elog(ERROR, "global temporary table can not reuse an existing relfilenode");
+
+		relfilenode = relid;
+	}
 	/*
 	 * Decide whether to create storage. If caller passed a valid relfilenode,
 	 * storage is already created, so don't do it here.  Also don't create it
 	 * for relkinds without physical storage.
 	 */
-	if (!RELKIND_HAS_STORAGE(relkind) || OidIsValid(relfilenode))
+	else if (!RELKIND_HAS_STORAGE(relkind) || OidIsValid(relfilenode))
+	{
 		create_storage = false;
+	}
 	else
 	{
 		create_storage = true;
@@ -397,7 +412,7 @@ heap_create(const char *relname,
 											relpersistence,
 											relfrozenxid, relminmxid);
 		else if (RELKIND_HAS_STORAGE(rel->rd_rel->relkind))
-			RelationCreateStorage(rel->rd_node, relpersistence);
+			RelationCreateStorage(rel->rd_node, relpersistence, rel);
 		else
 			Assert(false);
 	}
@@ -407,7 +422,8 @@ heap_create(const char *relname,
 	 * protected by the existence of a physical file; but for relations with
 	 * no files, add a pg_shdepend entry to account for that.
 	 */
-	if (!create_storage && reltablespace != InvalidOid)
+	if (!create_storage && reltablespace != InvalidOid &&
+		relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
 		recordDependencyOnTablespace(RelationRelationId, relid,
 									 reltablespace);
 
@@ -964,6 +980,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -989,8 +1006,21 @@ AddNewRelationTuple(Relation pg_class_desc,
 		new_rel_reltup->reltuples = 1;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/*
+	 * The transaction information of the global temporary table is stored
+	 * in the local hash table, not in catalog.
+	 */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1371,6 +1401,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1455,8 +1486,9 @@ heap_create_with_catalog(const char *relname,
 	/*
 	 * If there's a special on-commit action, remember it
 	 */
-	if (oncommit != ONCOMMIT_NOOP)
-		register_on_commit_action(relid, oncommit);
+	if (oncommit != ONCOMMIT_NOOP &&
+		relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+		register_on_commit_action(relid, oncommit, false);
 
 	/*
 	 * ok, the relation has been cataloged, so close our relations and return
@@ -1953,6 +1985,19 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/*
+	 * Only when other sessions are not using this Global temporary table,
+	 * is it allowed to DROP it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot drop global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3239,7 +3284,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3251,7 +3296,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3287,7 +3332,7 @@ RelationTruncateIndexes(Relation heapRelation)
  * ON COMMIT truncation of temporary tables, where it doesn't matter.
  */
 void
-heap_truncate(List *relids)
+heap_truncate(List *relids, bool is_global_temp)
 {
 	List	   *relations = NIL;
 	ListCell   *cell;
@@ -3297,8 +3342,23 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode;
+
+		/*
+		 * Truncate global temporary table only clears local data,
+		 * so only low-level locks need to be held.
+		 */
+		if (is_global_temp)
+		{
+			if (!gtt_storage_attached(rid))
+				continue;
 
-		rel = table_open(rid, AccessExclusiveLock);
+			lockmode = RowExclusiveLock;
+		}
+		else
+			lockmode = AccessExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3331,6 +3391,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3339,23 +3400,39 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	/*
+	 * Truncate GTT only clears local data, so only low-level locks
+	 * need to be held.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		lockmode = AccessShareLock;
+	else
+		lockmode = AccessExclusiveLock;
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	/*
+	 * After the data is cleaned up on the GTT, the transaction information
+	 * for the data(stored in local hash table) is also need reset.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(RelationGetRelid(rel), 0, 0, 0, RecentXmin, GetOldestMultiXactId());
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 1757cd3446f..50119909f79 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -54,6 +54,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"
@@ -734,6 +735,25 @@ index_create(Relation heapRelation,
 	TransactionId relfrozenxid;
 	MultiXactId relminmxid;
 
+	/* For global temporary table only */
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temporary table with concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot reindex global temporary tables concurrently")));
+
+		/*
+		 * For the case that some backend is applied relcache message to create
+		 * an index on a global temporary table, if this table in the current
+		 * backend are not initialized, the creation of index storage on the
+		 * table are also skipped.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/* constraint flags can only be set when a constraint is requested */
 	Assert((constr_flags == 0) ||
 		   ((flags & INDEX_CREATE_ADD_CONSTRAINT) != 0));
@@ -2119,7 +2139,7 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 * lock (see comments in RemoveRelations), and a non-concurrent DROP is
 	 * more efficient.
 	 */
-	Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+	Assert(!RelpersistenceTsTemp(get_rel_persistence(indexId)) ||
 		   (!concurrent && !concurrent_lock_mode));
 
 	/*
@@ -2151,6 +2171,21 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/*
+	 * Allow to drop index on global temporary table when only current
+	 * backend use it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation) &&
+		is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+	{
+		ereport(ERROR,
+			(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+			 errmsg("cannot drop index %s on global temporary table %s",
+					RelationGetRelationName(userIndexRelation), RelationGetRelationName(userHeapRelation)),
+					errdetail("Because the index is created on the global temporary table and other backend attached it."),
+					errhint("Please try detach all sessions using this temporary table and try again.")));
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2759,6 +2794,7 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = RELATION_IS_GLOBAL_TEMP(rel);
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2853,20 +2889,37 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
-		{
-			rd_rel->relpages = (int32) relpages;
-			dirty = true;
-		}
-		if (rd_rel->reltuples != (float4) reltuples)
+		/* For global temporary table */
+		if (is_gtt)
 		{
-			rd_rel->reltuples = (float4) reltuples;
-			dirty = true;
+			/* Update GTT'statistics into local relcache */
+			rel->rd_rel->relpages = (int32) relpages;
+			rel->rd_rel->reltuples = (float4) reltuples;
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+
+			/* Update GTT'statistics into local hashtable */
+			up_gtt_relstats(RelationGetRelid(rel), relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+		else
 		{
-			rd_rel->relallvisible = (int32) relallvisible;
-			dirty = true;
+			if (rd_rel->relpages != (int32) relpages)
+			{
+				rd_rel->relpages = (int32) relpages;
+				dirty = true;
+			}
+
+			if (rd_rel->reltuples != (float4) reltuples)
+			{
+				rd_rel->reltuples = (float4) reltuples;
+				dirty = true;
+			}
+
+			if (rd_rel->relallvisible != (int32) relallvisible)
+			{
+				rd_rel->relallvisible = (int32) relallvisible;
+				dirty = true;
+			}
 		}
 	}
 
@@ -2979,6 +3032,26 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, progress_index, progress_vals);
 	}
 
+	/* For build index on global temporary table */
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		/*
+		 * If the storage for the index in this session is not initialized,
+		 * it needs to be created.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			/* Before create init storage, fix the local Relcache first */
+			force_enable_gtt_index(indexRelation);
+
+			Assert(gtt_storage_attached(RelationGetRelid(heapRelation)));
+
+			/* Init storage for index */
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3520,6 +3593,8 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	PGRUsage	ru0;
 	bool		progress = ((params->options & REINDEXOPT_REPORT_PROGRESS) != 0);
 	bool		set_tablespace = false;
+	LOCKMODE	lockmode_on_heap = ShareLock;
+	LOCKMODE	lockmode_on_index = AccessExclusiveLock;
 
 	pg_rusage_init(&ru0);
 
@@ -3533,10 +3608,29 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	if (!OidIsValid(heapId))
 		return;
 
+	/*
+	 * For reindex on global temporary table, If the storage for the index
+	 * in current backend is not initialized, nothing is done.
+	 */
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		if (!gtt_storage_attached(indexId))
+		{
+			/* Suppress use of the target index while rebuilding it */
+			SetReindexProcessing(heapId, indexId);
+			/* Re-allow use of target index */
+			ResetReindexProcessing();
+			return;
+		}
+
+		lockmode_on_heap = AccessShareLock;
+		lockmode_on_index = AccessShareLock;
+	}
+
 	if ((params->options & REINDEXOPT_MISSING_OK) != 0)
-		heapRelation = try_table_open(heapId, ShareLock);
+		heapRelation = try_table_open(heapId, lockmode_on_heap);
 	else
-		heapRelation = table_open(heapId, ShareLock);
+		heapRelation = table_open(heapId, lockmode_on_heap);
 
 	/* if relation is gone, leave */
 	if (!heapRelation)
@@ -3562,7 +3656,7 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	 * Open the target index relation and get an exclusive lock on it, to
 	 * ensure that no one else is touching this particular index.
 	 */
-	iRel = index_open(indexId, AccessExclusiveLock);
+	iRel = index_open(indexId, lockmode_on_index);
 
 	if (progress)
 		pgstat_progress_update_param(PROGRESS_CREATEIDX_ACCESS_METHOD_OID,
@@ -3813,6 +3907,12 @@ reindex_relation(Oid relid, int flags, ReindexParams *params)
 	bool		result;
 	ListCell   *indexId;
 	int			i;
+	LOCKMODE	lockmode;
+
+	if (flags & REINDEX_REL_PROCESS_GLOBAL_TEMP)
+		lockmode = AccessShareLock;
+	else
+		lockmode = ShareLock;
 
 	/*
 	 * Open and lock the relation.  ShareLock is sufficient since we only need
@@ -3820,9 +3920,9 @@ reindex_relation(Oid relid, int flags, ReindexParams *params)
 	 * should match ReindexTable().
 	 */
 	if ((params->options & REINDEXOPT_MISSING_OK) != 0)
-		rel = try_table_open(relid, ShareLock);
+		rel = try_table_open(relid, lockmode);
 	else
-		rel = table_open(relid, ShareLock);
+		rel = table_open(relid, lockmode);
 
 	/* if relation is gone, leave */
 	if (!rel)
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 4de8400fd0f..fe3fcc712cb 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -656,6 +656,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/* Do not allow create global temporary table in temporary schemas */
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp table in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index c5ad28d71fe..707068a6fd8 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,6 +27,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -61,6 +62,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			temprelOid;			/* InvalidOid if not a global temporary rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -115,7 +117,7 @@ AddPendingSync(const RelFileNode *rnode)
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -126,7 +128,12 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/*
+		 * Global temporary table and local temporary table use same
+		 * design on storage module.
+		 */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -154,6 +161,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
@@ -165,6 +173,21 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		AddPendingSync(&rnode);
 	}
 
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		Assert(rel && RELATION_IS_GLOBAL_TEMP(rel));
+
+		/*
+		 * Remember the reloid of global temporary table, which is used for
+		 * transaction commit or rollback.
+		 * see smgrDoPendingDeletes.
+		 */
+		pending->temprelOid = RelationGetRelid(rel);
+
+		/* Remember global temporary table storage info to localhash */
+		remember_gtt_storage_info(rnode, rel);
+	}
+
 	return srel;
 }
 
@@ -201,11 +224,20 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/*
+	 * Remember the reloid of global temporary table, which is used for
+	 * transaction commit or rollback.
+	 * see smgrDoPendingDeletes.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->temprelOid = RelationGetRelid(rel);
+
 	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
@@ -618,6 +650,7 @@ smgrDoPendingDeletes(bool isCommit)
 	int			nrels = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -647,14 +680,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->temprelOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -664,12 +701,21 @@ smgrDoPendingDeletes(bool isCommit)
 
 	if (nrels > 0)
 	{
+		int	i;
+
 		smgrdounlinkall(srels, nrels, false);
 
-		for (int i = 0; i < nrels; i++)
+		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* Delete global temporary table info in localhash */
+			if (gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 00000000000..4fc4678c4c7
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1508 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  The body implementation of Global Temparary table.
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *	  See src/backend/catalog/GTT_README for Global temparary table's
+ *	  requirements and design.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_info_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+
+int		vacuum_gtt_defer_check_age = 0;
+
+/*
+ * The Global temporary table's shared hash table data structure
+ */
+typedef struct gtt_ctl_data
+{
+	LWLock		lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+/* record this global temporary table in which backends are being used */
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+/*
+ * The Global temporary table's local hash table data structure
+ */
+/* Record the storage information and statistical information of the global temporary table */
+typedef struct
+{
+	Oid			relfilenode;
+	Oid			spcnode;
+
+	/* pg_class relstat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+
+	/* pg_statistic column stat */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_free_statistics(gtt_relfilenode *rnode);
+static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok);
+static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok);
+static Bitmapset *copy_active_gtt_bitmap(Oid relid);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+/*
+ * Calculate shared hash table entry size for GTT.
+ */
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	/* hash entry header size */
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	/*
+	 * hash entry data size
+	 * this is a bitmap in shared memory, each backend have a bit.
+	 */
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+/*
+ * Calculate shared hash table max size for GTT.
+ */
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	/* shared hash header size */
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	/* hash entry size */
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	/* max size */
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+/*
+ * Initialization shared hash table for GTT.
+ */
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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();
+	}
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(gtt_fnode);
+	info.entrysize = action_gtt_shared_hash_entry_size();
+	active_gtt_shared_hash =
+		ShmemInitHash("active gtt shared hash",
+						gtt_shared_ctl->max_entry,
+						gtt_shared_ctl->max_entry,
+						&info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+/*
+ * Record GTT relid to shared hash table, which means that current backend is using this GTT.
+ */
+static void
+gtt_storage_checkin(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			found;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *)&(fnode), 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_global_temporary_table.")));
+	}
+
+	if (!found)
+	{
+		int			wordnum;
+
+		/* init bitmap */
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	/* record itself in bitmap */
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+/*
+ * Remove the GTT relid record from the shared hash table which means that current backend is
+ * not use this GTT.
+ */
+static void
+gtt_storage_checkout(Oid relid, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		if (isCommit)
+			elog(WARNING, "relid %u not exist in gtt shared hash when drop local storage", relid);
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+	/* remove itself from bitmap */
+	bms_del_member(entry->map, MyBackendId);
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return;
+}
+
+/*
+ * Gets usage information for a GTT from shared hash table.
+ * The information is in the form of bitmap.
+ * Quickly copy the entire bitmap from shared memory and return it.
+ * that to avoid holding locks for a long time.
+ */
+static Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	Bitmapset		*map_copy = NULL;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry)
+	{
+		Assert(entry->map);
+		/* copy the entire bitmap */
+		if (!bms_is_empty(entry->map))
+			map_copy = bms_copy(entry->map);
+	}
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return map_copy;
+}
+
+/*
+ * Check if there are other backends using this GTT besides the current backend.
+ */
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			in_use = false;
+	int			num_use = 0;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		return false;
+	}
+
+	Assert(entry->map);
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+	/* how many backend are using this GTT */
+	num_use = bms_num_members(entry->map);
+	if (num_use == 0)
+		in_use = false;
+	else if (num_use == 1)
+	{
+		/* check if this is itself */
+		if(bms_is_member(MyBackendId, entry->map))
+			in_use = false;
+		else
+			in_use = true;
+	}
+	else
+		in_use = true;
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return in_use;
+}
+
+/*
+ * Record GTT information to local hash.
+ * They include GTT storage info, transaction info and statistical info.
+ */
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+	gtt_local_hash_entry		*entry;
+	MemoryContext			oldcontext;
+	gtt_relfilenode			*new_node = NULL;
+	Oid				relid = RelationGetRelid(rel);
+	int				natts = 0;
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	/* First time through: initialize the hash table */
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		HASHCTL		ctl;
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_info_context =
+			AllocSetContextCreate(CacheMemoryContext,
+								"gtt info context",
+								ALLOCSET_DEFAULT_SIZES);
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		ctl.hcxt = gtt_info_context;
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+	}
+
+	Assert(CacheMemoryContext);
+	Assert(gtt_info_context);
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	entry = gtt_search_by_relid(relid, true);
+	if (!entry)
+	{
+		bool		found = false;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash,
+						(void *) &relid, HASH_ENTER, &found);
+
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			/* record the on commit clause */
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS, true);
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			gtt_storage_checkin(relid);
+		}
+	}
+
+	/* record storage info relstat columnstats and transaction info to relfilenode list */
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	new_node->natts = 0;
+	new_node->attnum = NULL;
+	new_node->att_stat_tups = NULL;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* init column stats structure */
+	natts = RelationGetNumberOfAttributes(rel);
+	new_node->attnum = palloc0(sizeof(int) * natts);
+	new_node->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+	new_node->natts = natts;
+
+	/* only heap rel or toast rel have transaction info */
+	if (entry->relkind == RELKIND_RELATION ||
+		entry->relkind == RELKIND_TOASTVALUE)
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+
+	/* Registration callbacks are used to trigger cleanup during process exit */
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+/*
+ * Remove GTT information from local hash when transaction commit/rollback.
+ */
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	gtt_relfilenode		*d_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+		return;
+	}
+
+	d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+		else
+		{
+			/* rollback transaction */
+			if (entry->relfilenode_list == NIL)
+			{
+				if (entry->relkind == RELKIND_RELATION ||
+					entry->relkind == RELKIND_SEQUENCE)
+					gtt_storage_checkout(relid, isCommit);
+
+				hash_search(gtt_storage_local_hash,
+						(void *) &(relid), HASH_REMOVE, NULL);
+			}
+
+			return;
+		}
+	}
+
+	/* Clean up transaction info from Local order list and MyProc */
+	if (entry->relkind == RELKIND_RELATION ||
+		entry->relkind == RELKIND_TOASTVALUE)
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+
+		/* this is valid relfrozenxid */
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	/* delete relfilenode from rel entry */
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	gtt_free_statistics(d_rnode);
+
+	if (entry->relfilenode_list == NIL)
+	{
+		/* tell shared hash that current backend will no longer use this GTT */
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(relid, isCommit);
+
+		hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_REMOVE, NULL);
+	}
+
+	return;
+}
+
+/*
+ * Check if current backend is using this GTT.
+ */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool			found = false;
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (!OidIsValid(relid))
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry)
+		found = true;
+
+	return found;
+}
+
+/*
+ * When backend exit, bulk cleaning all GTT storage and local buffer of this backend.
+ */
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS			status;
+	gtt_local_hash_entry	*entry;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	/* Need to ensure we have a usable transaction. */
+	AbortOutOfAnyTransaction();
+
+	/* Search all relfilenode for GTT in current backend */
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel[1];
+			RelFileNode		rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel[0] = smgropen(rnode, MyBackendId);
+			smgrdounlinkall(srel, 1, false);
+			smgrclose(srel[0]);
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(entry->relid, false);
+
+		hash_search(gtt_storage_local_hash, (void *) &(entry->relid), HASH_REMOVE, NULL);
+	}
+
+	/* set to global area */
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;
+
+	return;
+}
+
+/*
+ * Update GTT relstats(relpage/reltuple/relallvisible)
+ * to local hash.
+ */
+void
+up_gtt_relstats(Oid relid,
+					BlockNumber num_pages,
+					double num_tuples,
+					BlockNumber num_all_visible_pages,
+					TransactionId relfrozenxid,
+					TransactionId relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (!OidIsValid(relid))
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (num_pages > 0 &&
+		gtt_rnode->relpages != (int32)num_pages)
+		gtt_rnode->relpages = (int32)num_pages;
+
+	if (num_tuples > 0 &&
+		gtt_rnode->reltuples != (float4)num_tuples)
+		gtt_rnode->reltuples = (float4)num_tuples;
+
+	/* only heap contain transaction information and relallvisible */
+	if (entry->relkind == RELKIND_RELATION ||
+		entry->relkind == RELKIND_TOASTVALUE)
+	{
+		if (num_all_visible_pages > 0 &&
+			gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+		{
+			gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+		}
+
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNextTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			/* set to local order list */
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			/* set to global area */
+			set_gtt_session_relfrozenxid();
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search GTT relstats(relpage/reltuple/relallvisible)
+ * from local has.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update GTT info(definition is same as pg_statistic)
+ * to local hash.
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+	MemoryContext		oldcontext;
+	bool		found = false;
+	int			i = 0;
+
+	/* not support whole row or system column */
+	if (attnum <= 0)
+		return;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return;
+
+	Assert(entry->relid == reloid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	/* switch context to gtt_info_context for store tuple at heap_form_tuple */
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == 0)
+		{
+			Assert(gtt_rnode->att_stat_tups[i] == NULL);
+			gtt_rnode->attnum[i] = attnum;
+			gtt_rnode->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+			found = true;
+			break;
+		}
+		else if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			heap_freetuple(gtt_rnode->att_stat_tups[i]);
+			gtt_rnode->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+			found = true;
+			break;
+		}
+	}
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!found)
+		elog(WARNING, "analyze can not update relid %u column %d statistics after add or drop column, try truncate table first", reloid, attnum);
+
+	return;
+}
+
+/*
+ * Search GTT statistic info(definition is same as pg_statistic)
+ * from local hash.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	int			i = 0;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	/* not support whole row or system column */
+	if (attnum <= 0)
+		return NULL;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return NULL;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return NULL;
+
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			return gtt_rnode->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Insert a RelfrozenXID into the list and keep the list in order.
+ */
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int		i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Remove a RelfrozenXID from order list gtt_session_relfrozenxid_list.
+ */
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+/*
+ * Update of backend Level oldest relfrozenxid to MyProc.
+ * This makes each backend's oldest RelFrozenxID globally visible.
+ */
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list != NIL)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	if (MyProc->backend_gtt_frozenxid != gtt_frozenxid)
+		MyProc->backend_gtt_frozenxid = gtt_frozenxid;
+}
+
+/*
+ * Get GTT column level data statistics.
+ */
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate		*tupstore;
+	HeapTuple		tuple;
+	Relation		rel = NULL;
+	Oid			reloid = PG_GETARG_OID(0);
+	int			attnum = PG_GETARG_INT32(1);
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	if (!RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	/* get data from local hash */
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum		values[Natts_pg_statistic];
+		bool		isnull[Natts_pg_statistic];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, AccessShareLock);
+	relation_close(pg_tatistic, AccessShareLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get GTT table level data statistics.
+ */
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate	*tupstore;
+	TupleDesc	tupdesc;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid			reloid = PG_GETARG_OID(0);
+	Oid			relnode = 0;
+	BlockNumber		relpages = 0;
+	BlockNumber		relallvisible = 0;
+	uint32			relfrozenxid = 0;
+	uint32			relminmxid = 0;
+	double			reltuples = 0;
+	Relation		rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	if (!RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[6];
+		bool	isnull[6];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get a list of backend pids that are currently using this GTT.
+ */
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	PGPROC			*proc = NULL;
+	Bitmapset		*map = NULL;
+	Tuplestorestate		*tupstore;
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	HeapTuple		tuple;
+	Oid			reloid = PG_GETARG_OID(0);
+	Relation		rel = NULL;
+	int				backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	if (!RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	/* get data from share hash */
+	map = copy_active_gtt_bitmap(reloid);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			/* backendid map to process pid */
+			proc = BackendIdGetProc(backendid);
+			if (proc && proc->pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+				pid_t	pid = proc->pid;
+
+				memset(isnull, 0, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, AccessShareLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get backend level oldest relfrozenxid of each backend using GTT in current database.
+ */
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate		*tupstore;
+	int			*pids = NULL;
+	uint32			*xids = NULL;
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	HeapTuple		tuple;
+	int			num_xid = MaxBackends + 1;
+	int			i = 0;
+	int			j = 0;
+	uint32			oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	if (pids == NULL || xids == NULL)
+		elog(ERROR, "out of memory");
+
+	/* Get backend level oldest relfrozenxid in all backend that in MyDatabaseId use GTT */
+	oldest = list_all_backend_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		/* save oldest relfrozenxid */
+		pids[i] = 0;
+		xids[i] = oldest;
+		i++;
+
+		/* save relfrozenxid for each session */
+		for (j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, 0, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+/*
+ * In order to build the GTT index, force enable GTT'index.
+ */
+void
+force_enable_gtt_index(Relation index)
+{
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(RelationGetRelid(index)));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+/*
+ * Fix the local state of the GTT's index.
+ */
+void
+gtt_fix_index_backend_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid heapOid = index->rd_index->indrelid;
+
+	/* Must be GTT */
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	/*
+	 * If this GTT is not initialized in the current backend,
+	 * its index status is temporarily set to invalid(local relcache).
+	 */
+	if (gtt_storage_attached(heapOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+/*
+ * During the SQL initialization of the executor (InitPlan)
+ * Initialize storage of GTT GTT'indexes and build empty index.
+ */
+void
+init_gtt_storage(CmdType operation, Relation relation)
+{
+	Oid			toastrelid;
+	List		*indexoidlist = NIL;
+	ListCell	*l;
+
+	if (!(operation == CMD_INSERT))
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	/* Each GTT is initialized once in each backend */
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	/* init heap storage */
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	indexoidlist = RelationGetIndexList(relation);
+	foreach(l, indexoidlist)
+	{
+		Oid			indexOid = lfirst_oid(l);
+		Relation	index = index_open(indexOid, RowExclusiveLock);
+		IndexInfo	*info = BuildDummyIndexInfo(index);
+
+		index_build(relation, index, info, true, false);
+		/* after build index, index re-enabled */
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+		index_close(index, NoLock);
+	}
+	list_free(indexoidlist);
+
+	/* rebuild index for global temp toast table */
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+
+		/* init index storage */
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid			indexId = lfirst_oid(indlist);
+			Relation	index = index_open(indexId, RowExclusiveLock);
+			IndexInfo	*info = BuildDummyIndexInfo(index);
+
+			/* build empty index */
+			index_build(toastrel, index, info, true, false);
+			Assert(index->rd_index->indisvalid);
+			Assert(index->rd_index->indislive);
+			Assert(index->rd_index->indisready);
+			index_close(index, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+/*
+ * Release the data structure memory used to store GTT storage info.
+ */
+static void
+gtt_free_statistics(gtt_relfilenode *rnode)
+{
+	int i;
+
+	Assert(rnode);
+
+	for (i = 0; i < rnode->natts; i++)
+	{
+		if (rnode->att_stat_tups[i])
+		{
+			heap_freetuple(rnode->att_stat_tups[i]);
+			rnode->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (rnode->attnum)
+		pfree(rnode->attnum);
+
+	if (rnode->att_stat_tups)
+		pfree(rnode->att_stat_tups);
+
+	pfree(rnode);
+
+	return;
+}
+
+/*
+ * Get the current relfilenode of this GTT.
+ */
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
+/*
+ * Get a relfilenode used by this GTT during the transaction life cycle.
+ */
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok)
+{
+	gtt_relfilenode		*rnode = NULL;
+	ListCell		*lc;
+
+	Assert(entry);
+
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == relfilenode)
+		{
+			rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (!missing_ok && rnode == NULL)
+		elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+	return rnode;
+}
+
+/*
+ * Get one GTT info from local hash.
+ */
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry == NULL && !missing_ok)
+		elog(ERROR, "relid %u not found in local hash", relid);
+
+	return entry;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 61b515cdb85..dcf2bdbbde3 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index cd77907fc74..446a713a9b1 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -105,7 +106,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);
 
@@ -186,6 +187,17 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -602,14 +614,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]));
 		}
 
 		/*
@@ -1623,7 +1636,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;
@@ -1725,31 +1738,48 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/*
+		 * For global temporary table,
+		 * Update column statistic to localhash, not catalog.
+		 */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 9d22f648a84..a44eefa1f6b 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/toasting.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
@@ -390,6 +391,22 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+	{
+		if (gtt_storage_attached(RelationGetRelid(OldHeap)))
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("not support cluster global temporary table yet")));
+
+		relation_close(OldHeap, AccessExclusiveLock);
+		pgstat_progress_end_command();
+		return;
+	}
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
@@ -585,6 +602,8 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
 	TransactionId frozenXid;
 	MultiXactId cutoffMulti;
 
+	Assert(!RELATION_IS_GLOBAL_TEMP(OldHeap));
+
 	/* Mark the correct index as clustered */
 	if (OidIsValid(indexOid))
 		mark_index_clustered(OldHeap, indexOid, true);
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 53f48531419..c03191cce94 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -289,7 +289,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP_ON_CURRENT_SESSION(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, whereClause,
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index f366a818a14..fb0e9349a6e 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -30,6 +30,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/copyfrom_internal.h"
 #include "commands/progress.h"
@@ -652,7 +653,7 @@ CopyFrom(CopyFromState cstate)
 	 */
 	ExecInitRangeTable(estate, cstate->range_table);
 	resultRelInfo = target_resultRelInfo = makeNode(ResultRelInfo);
-	ExecInitResultRelation(estate, resultRelInfo, 1);
+	ExecInitResultRelation(estate, resultRelInfo, 1, CMD_INSERT);
 
 	/* Verify the named relation is a valid target for INSERT */
 	CheckValidResultRel(resultRelInfo, CMD_INSERT);
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 09828517153..b151fbdd80f 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -38,6 +38,7 @@
 #include "commands/prepare.h"
 #include "commands/tablecmds.h"
 #include "commands/view.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
@@ -520,6 +521,8 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 	 */
 	intoRelationDesc = table_open(intoRelationAddr.objectId, AccessExclusiveLock);
 
+	init_gtt_storage(CMD_INSERT, intoRelationDesc);
+
 	/*
 	 * Make sure the constructed table does not have RLS enabled.
 	 *
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 8d3104821ee..ae570b07c1e 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -111,6 +111,7 @@ struct ReindexIndexCallbackState
 {
 	ReindexParams params;		/* options from statement */
 	Oid			locked_table_oid;	/* tracks previously locked table */
+	LOCKMODE	lockmode;
 };
 
 /*
@@ -570,7 +571,7 @@ DefineIndex(Oid relationId,
 	 * is more efficient.  Do this before any use of the concurrent option is
 	 * done.
 	 */
-	if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+	if (stmt->concurrent && !RelpersistenceTsTemp(get_rel_persistence(relationId)))
 		concurrent = true;
 	else
 		concurrent = false;
@@ -2581,9 +2582,9 @@ ReindexIndex(RangeVar *indexRelation, ReindexParams *params, bool isTopLevel)
 	 */
 	state.params = *params;
 	state.locked_table_oid = InvalidOid;
+	state.lockmode = AccessShareLock;
 	indOid = RangeVarGetRelidExtended(indexRelation,
-									  (params->options & REINDEXOPT_CONCURRENTLY) != 0 ?
-									  ShareUpdateExclusiveLock : AccessExclusiveLock,
+									  AccessShareLock,
 									  0,
 									  RangeVarCallbackForReindexIndex,
 									  &state);
@@ -2594,11 +2595,25 @@ ReindexIndex(RangeVar *indexRelation, ReindexParams *params, bool isTopLevel)
 	 */
 	persistence = get_rel_persistence(indOid);
 	relkind = get_rel_relkind(indOid);
+	if (persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		LOCKMODE	lockmode;
+
+		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0)
+			lockmode = ShareUpdateExclusiveLock;
+		else
+			lockmode = AccessExclusiveLock;
+
+		/* lock heap first */
+		Assert(OidIsValid(state.locked_table_oid));
+		LockRelationOid(state.locked_table_oid, lockmode);
+		LockRelationOid(indOid, lockmode);
+	}
 
 	if (relkind == RELKIND_PARTITIONED_INDEX)
 		ReindexPartitions(indOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 persistence != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(persistence))
 		ReindexRelationConcurrently(indOid, params);
 	else
 	{
@@ -2620,15 +2635,7 @@ RangeVarCallbackForReindexIndex(const RangeVar *relation,
 {
 	char		relkind;
 	struct ReindexIndexCallbackState *state = arg;
-	LOCKMODE	table_lockmode;
-
-	/*
-	 * Lock level here should match table lock in reindex_index() for
-	 * non-concurrent case and table locks used by index_concurrently_*() for
-	 * concurrent case.
-	 */
-	table_lockmode = (state->params.options & REINDEXOPT_CONCURRENTLY) != 0 ?
-		ShareUpdateExclusiveLock : ShareLock;
+	LOCKMODE	table_lockmode = state->lockmode;
 
 	/*
 	 * If we previously locked some other index's heap, and the name we're
@@ -2689,6 +2696,8 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 {
 	Oid			heapOid;
 	bool		result;
+	char		relpersistence;
+	int 		reindex_flags = 0;
 
 	/*
 	 * The lock level used here should match reindex_relation().
@@ -2699,15 +2708,27 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 	 * locks on our temporary table.
 	 */
 	heapOid = RangeVarGetRelidExtended(relation,
-									   (params->options & REINDEXOPT_CONCURRENTLY) != 0 ?
-									   ShareUpdateExclusiveLock : ShareLock,
+									   AccessShareLock,
 									   0,
 									   RangeVarCallbackOwnsTable, NULL);
 
+	relpersistence = get_rel_persistence(heapOid);
+	if (relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		LOCKMODE	lockmode;
+
+		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0)
+			lockmode = ShareUpdateExclusiveLock;
+		else
+			lockmode = ShareLock;
+
+		LockRelationOid(heapOid, lockmode);
+	}
+
 	if (get_rel_relkind(heapOid) == RELKIND_PARTITIONED_TABLE)
 		ReindexPartitions(heapOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(relpersistence))
 	{
 		result = ReindexRelationConcurrently(heapOid, params);
 
@@ -2721,9 +2742,14 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 		ReindexParams newparams = *params;
 
 		newparams.options |= REINDEXOPT_REPORT_PROGRESS;
+
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			reindex_flags |= REINDEX_REL_PROCESS_GLOBAL_TEMP;
+
+		reindex_flags |= REINDEX_REL_PROCESS_TOAST;
+		reindex_flags |= REINDEX_REL_CHECK_CONSTRAINTS;
 		result = reindex_relation(heapOid,
-								  REINDEX_REL_PROCESS_TOAST |
-								  REINDEX_REL_CHECK_CONSTRAINTS,
+								  reindex_flags,
 								  &newparams);
 		if (!result)
 			ereport(NOTICE,
@@ -3119,7 +3145,7 @@ ReindexMultipleInternal(List *relids, ReindexParams *params)
 		Assert(!RELKIND_HAS_PARTITIONS(relkind));
 
 		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			relpersistence != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(relpersistence))
 		{
 			ReindexParams newparams = *params;
 
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 62465bacd81..ef37f79ba68 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -51,12 +51,32 @@ LockTableCommand(LockStmt *lockstmt)
 		RangeVar   *rv = (RangeVar *) lfirst(p);
 		bool		recurse = rv->inh;
 		Oid			reloid;
+		LOCKMODE	lockmode = lockstmt->mode;
+		char		relpersistence;
 
-		reloid = RangeVarGetRelidExtended(rv, lockstmt->mode,
-										  lockstmt->nowait ? RVR_NOWAIT : 0,
+		reloid = RangeVarGetRelidExtended(rv, NoLock, 0,
 										  RangeVarCallbackForLockTable,
 										  (void *) &lockstmt->mode);
 
+		relpersistence = get_rel_persistence(reloid);
+		if (relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			if (!lockstmt->nowait)
+				LockRelationOid(reloid, lockmode);
+			else if (!ConditionalLockRelationOid(reloid, lockmode))
+			{
+				/* try to throw error by name; relation could be deleted... */
+				char	   *relname = get_rel_name(reloid);
+
+				if (!relname)
+					return;		/* child concurrently dropped, just skip it */
+				ereport(ERROR,
+						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						 errmsg("could not obtain lock on relation \"%s\"",
+								relname)));
+			}
+		}
+
 		if (get_rel_relkind(reloid) == RELKIND_VIEW)
 			LockViewRecurse(reloid, lockstmt->mode, lockstmt->nowait, NIL);
 		else if (recurse)
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 72bfdc07a49..e5257f610f6 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -220,9 +223,12 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	rel = table_open(seqoid, AccessExclusiveLock);
 	tupDesc = RelationGetDescr(rel);
 
-	/* now initialize the sequence's data */
-	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	if (!RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		/* now initialize the sequence's data */
+		tuple = heap_form_tuple(tupDesc, value, null);
+		fill_seq_with_data(rel, tuple);
+	}
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -275,8 +281,6 @@ ResetSequence(Oid seq_relid)
 	Buffer		buf;
 	HeapTupleData seqdatatuple;
 	HeapTuple	tuple;
-	HeapTuple	pgstuple;
-	Form_pg_sequence pgsform;
 	int64		startv;
 
 	/*
@@ -287,12 +291,7 @@ ResetSequence(Oid seq_relid)
 	init_sequence(seq_relid, &elm, &seq_rel);
 	(void) read_seq_tuple(seq_rel, &buf, &seqdatatuple);
 
-	pgstuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seq_relid));
-	if (!HeapTupleIsValid(pgstuple))
-		elog(ERROR, "cache lookup failed for sequence %u", seq_relid);
-	pgsform = (Form_pg_sequence) GETSTRUCT(pgstuple);
-	startv = pgsform->seqstart;
-	ReleaseSysCache(pgstuple);
+	startv = get_seqence_start_value(seq_relid);
 
 	/*
 	 * Copy the existing sequence tuple.
@@ -451,6 +450,15 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 
 	init_sequence(relid, &elm, &seqrel);
 
+	if (RELATION_IS_GLOBAL_TEMP(seqrel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(seqrel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary sequence %s when other backend attached it.",
+						RelationGetRelationName(seqrel))));
+	}
+
 	rel = table_open(SequenceRelationId, RowExclusiveLock);
 	seqtuple = SearchSysCacheCopy1(SEQRELID,
 								   ObjectIdGetDatum(relid));
@@ -611,7 +619,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +944,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1153,6 +1161,14 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	/* Initializes the storage for sequence which the global temporary table belongs. */
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1927,3 +1943,58 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+/*
+ * Get the startValue of the sequence from syscache.
+ */
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+/*
+ * Initialize sequence which global temporary table belongs.
+ */
+void
+gtt_init_seq(Relation rel)
+{
+	Datum		value[SEQ_COL_LASTCOL];
+	bool		null[SEQ_COL_LASTCOL];
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL - 1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+	null[SEQ_COL_LASTVAL - 1] = false;
+
+	value[SEQ_COL_LOG - 1] = Int64GetDatum((int64)0);
+	null[SEQ_COL_LOG - 1] = false;
+
+	value[SEQ_COL_CALLED - 1] = BoolGetDatum(false);
+	null[SEQ_COL_CALLED - 1] = false;
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index bf42587e383..94cd1f8306d 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -118,6 +119,7 @@ typedef struct OnCommitItem
 	 */
 	SubTransactionId creating_subid;
 	SubTransactionId deleting_subid;
+	bool			 is_global_temp;
 } OnCommitItem;
 
 static List *on_commits = NIL;
@@ -607,7 +609,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
 static char GetAttributeCompression(Oid atttypid, char *compression);
-
+static OnCommitAction gtt_oncommit_option(List *options);
 
 /* ----------------------------------------------------------------
  *		DefineRelation
@@ -652,6 +654,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -663,7 +666,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * Check consistency of arguments
 	 */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !RelpersistenceTsTemp(stmt->relation->relpersistence))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -693,7 +696,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	if (RelpersistenceTsTemp(stmt->relation->relpersistence)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -794,6 +797,50 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* For global temporary table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		if (!(relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE))
+			elog(ERROR, "Only support global temporary regular table.");
+
+		/* Check parent table */
+		if (inheritOids)
+			elog(ERROR, "Not support global temporary partition table or inherit table.");
+
+		/* Check oncommit clause and save to reloptions */
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temporary table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1418,7 +1465,7 @@ RemoveRelations(DropStmt *drop)
 		 * relation persistence cannot be known without its OID.
 		 */
 		if (drop->concurrent &&
-			get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(get_rel_persistence(relOid)))
 		{
 			Assert(list_length(drop->objects) == 1 &&
 				   drop->removeType == OBJECT_INDEX);
@@ -1627,9 +1674,9 @@ ExecuteTruncate(TruncateStmt *stmt)
 		Relation	rel;
 		bool		recurse = rv->inh;
 		Oid			myrelid;
-		LOCKMODE	lockmode = AccessExclusiveLock;
+		LOCKMODE	lockmode;
 
-		myrelid = RangeVarGetRelidExtended(rv, lockmode,
+		myrelid = RangeVarGetRelidExtended(rv, AccessShareLock,
 										   0, RangeVarCallbackForTruncate,
 										   NULL);
 
@@ -1637,9 +1684,21 @@ ExecuteTruncate(TruncateStmt *stmt)
 		if (list_member_oid(relids, myrelid))
 			continue;
 
-		/* open the relation, we already hold a lock on it */
+		/* open the relation, we need hold a low-level lock first */
 		rel = table_open(myrelid, NoLock);
 
+		/*
+		 * Truncate global temp table only cleans up the data in current backend,
+		 * only low-level locks are required.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel))
+			lockmode = AccessShareLock;
+		else
+		{
+			lockmode = AccessExclusiveLock;
+			LockRelationOid(myrelid, lockmode);
+		}
+
 		/*
 		 * RangeVarGetRelidExtended() has done most checks with its callback,
 		 * but other checks with the now-opened Relation remain.
@@ -1889,6 +1948,7 @@ ExecuteTruncateGuts(List *explicit_rels,
 	foreach(cell, rels)
 	{
 		Relation	rel = (Relation) lfirst(cell);
+		LOCKMODE	lockmode;
 
 		/* Skip partitioned tables as there is nothing to do */
 		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
@@ -1939,6 +1999,19 @@ ExecuteTruncateGuts(List *explicit_rels,
 			continue;
 		}
 
+		/*
+		 * Skip the global temporary table that is not initialized for storage
+		 * in current backend.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel))
+		{
+			lockmode = AccessShareLock;
+			if (!gtt_storage_attached(RelationGetRelid(rel)))
+				continue;
+		}
+		else
+			lockmode = AccessExclusiveLock;
+
 		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
@@ -1957,6 +2030,7 @@ ExecuteTruncateGuts(List *explicit_rels,
 			Oid			heap_relid;
 			Oid			toast_relid;
 			ReindexParams reindex_params = {0};
+			int			reindex_flags = 0;
 
 			/*
 			 * This effectively deletes all rows in the table, and may be done
@@ -1984,17 +2058,21 @@ ExecuteTruncateGuts(List *explicit_rels,
 			if (OidIsValid(toast_relid))
 			{
 				Relation	toastrel = relation_open(toast_relid,
-													 AccessExclusiveLock);
+													 lockmode);
 
 				RelationSetNewRelfilenode(toastrel,
 										  toastrel->rd_rel->relpersistence);
 				table_close(toastrel, NoLock);
 			}
 
+			reindex_flags = REINDEX_REL_PROCESS_TOAST;
+			if (RELATION_IS_GLOBAL_TEMP(rel))
+				reindex_flags |= REINDEX_REL_PROCESS_GLOBAL_TEMP;
+
 			/*
 			 * Reconstruct the indexes to match, and we're done.
 			 */
-			reindex_relation(heap_relid, REINDEX_REL_PROCESS_TOAST,
+			reindex_relation(heap_relid, reindex_flags,
 							 &reindex_params);
 		}
 
@@ -4035,6 +4113,16 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temporary table only current backend use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -5366,6 +5454,24 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 
 			rel = table_open(tab->relid, NoLock);
 			find_composite_type_dependencies(rel->rd_rel->reltype, rel, NULL);
+
+			if (RELATION_IS_GLOBAL_TEMP(rel) && tab->rewrite > 0)
+			{
+				if (tab->chgPersistence)
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot change global temporary table persistence setting")));
+
+				if(gtt_storage_attached(tab->relid))
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("Only support alter global temporary table in an empty context."),
+						 errhint("Please create a new connection and execute ALTER TABLE on the new connection.")));
+
+				/* There is no need to override the whole temp table */
+				tab->rewrite = 0;
+			}
+
 			table_close(rel, NoLock);
 		}
 
@@ -5417,6 +5523,8 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot rewrite temporary tables of other sessions")));
 
+			Assert(!RELATION_IS_GLOBAL_TEMP(OldHeap));
+
 			/*
 			 * Select destination tablespace (same as original unless user
 			 * requested a change)
@@ -9064,6 +9172,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -13179,7 +13293,9 @@ TryReuseIndex(Oid oldId, IndexStmt *stmt)
 		Relation	irel = index_open(oldId, NoLock);
 
 		/* If it's a partitioned index, there is no storage to share. */
-		if (irel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
+		/* multiple global temp table are not allow use same relfilenode */
+		if (irel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX &&
+			!RELATION_IS_GLOBAL_TEMP(irel))
 		{
 			stmt->oldNode = irel->rd_node.relNode;
 			stmt->oldCreateSubid = irel->rd_createSubid;
@@ -13841,6 +13957,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -14040,6 +14159,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temporary table");
+
 	/* Check first if relation can be moved to new tablespace */
 	if (!CheckRelationTableSpaceMove(rel, newTableSpace))
 	{
@@ -14341,7 +14463,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(RelationGetSmgr(rel), dstrel, MAIN_FORKNUM,
@@ -15945,6 +16067,7 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
 	switch (rel->rd_rel->relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -16386,7 +16509,7 @@ AlterSeqNamespaces(Relation classRel, Relation rel,
  * Register a newly-created relation's ON COMMIT action.
  */
 void
-register_on_commit_action(Oid relid, OnCommitAction action)
+register_on_commit_action(Oid relid, OnCommitAction action, bool is_gloal_temp)
 {
 	OnCommitItem *oc;
 	MemoryContext oldcxt;
@@ -16405,6 +16528,7 @@ register_on_commit_action(Oid relid, OnCommitAction action)
 	oc->oncommit = action;
 	oc->creating_subid = GetCurrentSubTransactionId();
 	oc->deleting_subid = InvalidSubTransactionId;
+	oc->is_global_temp = is_gloal_temp;
 
 	/*
 	 * We use lcons() here so that ON COMMIT actions are processed in reverse
@@ -16450,6 +16574,7 @@ PreCommit_on_commit_actions(void)
 	ListCell   *l;
 	List	   *oids_to_truncate = NIL;
 	List	   *oids_to_drop = NIL;
+	List	   *oids_to_truncate_gtt = NIL;
 
 	foreach(l, on_commits)
 	{
@@ -16473,7 +16598,12 @@ PreCommit_on_commit_actions(void)
 				 * tables, as they must still be empty.
 				 */
 				if ((MyXactFlags & XACT_FLAGS_ACCESSEDTEMPNAMESPACE))
-					oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid);
+				{
+					if (oc->is_global_temp)
+						oids_to_truncate_gtt = lappend_oid(oids_to_truncate_gtt, oc->relid);
+					else
+						oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid);
+				}
 				break;
 			case ONCOMMIT_DROP:
 				oids_to_drop = lappend_oid(oids_to_drop, oc->relid);
@@ -16490,7 +16620,10 @@ PreCommit_on_commit_actions(void)
 	 * exists at truncation time.
 	 */
 	if (oids_to_truncate != NIL)
-		heap_truncate(oids_to_truncate);
+		heap_truncate(oids_to_truncate, false);
+
+	if (oids_to_truncate_gtt != NIL)
+		heap_truncate(oids_to_truncate_gtt, true);
 
 	if (oids_to_drop != NIL)
 	{
@@ -17489,6 +17622,13 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot attach temporary relation of another session as partition")));
 
+	/* If the parent is permanent, so must be all of its partitions. */
+	if (attachrel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach a global temporary relation as partition of permanent relation \"%s\"",
+						RelationGetRelationName(rel))));
+
 	/* Check if there are any columns in attachrel that aren't in the parent */
 	tupleDesc = RelationGetDescr(attachrel);
 	natts = tupleDesc->natts;
@@ -18959,3 +19099,40 @@ GetAttributeCompression(Oid atttypid, char *compression)
 
 	return cmethod;
 }
+
+/*
+ * Parse the on commit clause for the temporary table
+ */
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			bool	res = false;
+			char	*sval = defGetString(def);
+
+			/* It has to be a Boolean value */
+			if (!parse_bool(sval, &res))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
+
+			if (res)
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 5c4bc15b441..3a861c47946 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"
@@ -1315,6 +1316,27 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = RELATION_IS_GLOBAL_TEMP(relation);
+
+	 /* For global temporary table */
+	if (is_gtt)
+	{
+		/* Store relation statistics and transaction information to the localhash */
+		up_gtt_relstats(RelationGetRelid(relation),
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+
+		/* Update relation statistics to local relcache */
+		relation->rd_rel->relpages = (int32) num_pages;
+		relation->rd_rel->reltuples = (float4) num_tuples;
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+		if (TransactionIdIsNormal(frozenxid))
+			relation->rd_rel->relfrozenxid = frozenxid;
+
+		if (MultiXactIdIsValid(minmulti))
+			relation->rd_rel->relminmxid = minmulti;
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1328,17 +1350,23 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (!is_gtt &&
+		pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (!is_gtt &&
+		pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (!is_gtt &&
+		pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1351,7 +1379,15 @@ vac_update_relstats(Relation relation,
 		/*
 		 * If we didn't find any indexes, reset relhasindex.
 		 */
-		if (pgcform->relhasindex && !hasindex)
+		if (is_gtt &&
+			RelationGetIndexList(relation) != NIL)
+		{
+			/*
+			 * Global temporary tables may contain indexes that are not valid locally.
+			 * The catalog should not be updated based on local invalid index.
+			 */
+		}
+		else if (pgcform->relhasindex && !hasindex)
 		{
 			pgcform->relhasindex = false;
 			dirty = true;
@@ -1383,7 +1419,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNextTransactionId(),
@@ -1394,7 +1431,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1502,6 +1540,13 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/*
+		 * The relfrozenxid for a global temporary talble is stored in localhash,
+		 * not pg_class, See list_all_session_gtt_frozenxids()
+		 */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1559,6 +1604,43 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/* If enable global temporary table */
+	if (max_active_gtt > 0)
+	{
+		TransactionId	safe_age;
+		/*  */
+		TransactionId	oldest_gtt_frozenxid =
+			list_all_backend_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid))
+		{
+			safe_age = oldest_gtt_frozenxid + vacuum_gtt_defer_check_age;
+			if (safe_age < FirstNormalTransactionId)
+				safe_age += FirstNormalTransactionId;
+
+			/*
+			 * We tolerate that the minimum age of gtt is less than
+			 * the minimum age of conventional tables, otherwise it will
+			 * throw warning message.
+			 */
+			if (TransactionIdIsNormal(safe_age) &&
+				TransactionIdPrecedes(safe_age, newFrozenXid))
+			{
+				ereport(WARNING,
+					(errmsg("global temp table oldest relfrozenxid %u is the oldest in the entire db",
+							oldest_gtt_frozenxid),
+					 errdetail("The oldest relfrozenxid in pg_class is %u", newFrozenXid),
+					 errhint("If they differ greatly, please consider cleaning up the data in global temp table.")));
+			}
+
+			/*
+			 * We need to ensure that the clog required by gtt is not cleand.
+			 */
+			if (TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+				newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1910,6 +1992,19 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	/*
+	 * Skip those global temporary table that are not initialized in
+	 * current backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel) &&
+		!gtt_storage_attached(RelationGetRelid(rel)))
+	{
+		relation_close(rel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 4df05a0b33d..4c181e2e14e 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -527,6 +527,12 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	/* Global temporary table are not sensible. */
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b3ce4bae530..611e3f18a70 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -784,6 +784,10 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		/* This is one kind of temp table */
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 5c723bc54e1..191e0f6fd21 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 4ab1302313f..b6dfb46fd57 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -50,6 +50,7 @@
 #include "access/table.h"
 #include "access/tableam.h"
 #include "access/transam.h"
+#include "catalog/storage_gtt.h"
 #include "executor/executor.h"
 #include "executor/execPartition.h"
 #include "jit/jit.h"
@@ -832,7 +833,7 @@ ExecGetRangeTableRelation(EState *estate, Index rti)
  */
 void
 ExecInitResultRelation(EState *estate, ResultRelInfo *resultRelInfo,
-					   Index rti)
+					   Index rti, CmdType operation)
 {
 	Relation	resultRelationDesc;
 
@@ -843,6 +844,9 @@ ExecInitResultRelation(EState *estate, ResultRelInfo *resultRelInfo,
 					  NULL,
 					  estate->es_instrument);
 
+	/* Check and init global temporary table storage in current backend */
+	init_gtt_storage(operation, resultRelationDesc);
+
 	if (estate->es_result_relations == NULL)
 		estate->es_result_relations = (ResultRelInfo **)
 			palloc0(estate->es_range_table_size * sizeof(ResultRelInfo *));
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d328856ae5b..c540874789b 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -38,6 +38,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -2754,13 +2755,13 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	{
 		mtstate->rootResultRelInfo = makeNode(ResultRelInfo);
 		ExecInitResultRelation(estate, mtstate->rootResultRelInfo,
-							   node->rootRelation);
+							   node->rootRelation, operation);
 	}
 	else
 	{
 		mtstate->rootResultRelInfo = mtstate->resultRelInfo;
 		ExecInitResultRelation(estate, mtstate->resultRelInfo,
-							   linitial_int(node->resultRelations));
+							   linitial_int(node->resultRelations), operation);
 	}
 
 	/* set up epqstate with dummy subplan data for the moment */
@@ -2788,7 +2789,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 		if (resultRelInfo != mtstate->rootResultRelInfo)
 		{
-			ExecInitResultRelation(estate, resultRelInfo, resultRelation);
+			ExecInitResultRelation(estate, resultRelInfo, resultRelation, operation);
 
 			/*
 			 * For child result relations, store the root result relation
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 296dd75c1b6..d971aea2546 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -48,7 +48,7 @@
 #include "partitioning/partprune.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/lsyscache.h"
-
+#include "utils/rel.h"
 
 /* results of subquery_is_pushdown_safe */
 typedef struct pushdown_safety_info
@@ -619,7 +619,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			if (RelpersistenceTsTemp(get_rel_persistence(rte->relid)))
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index bd01ec0526f..ff4e81ca2cf 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6071,7 +6071,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
-	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+	if (RELATION_IS_TEMP(heap) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 564a38a13e9..1cf2d31b034 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -30,6 +30,7 @@
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temporary table in current backend */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 146ee8dd1ea..2d4e9393f00 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2907,6 +2907,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 3d4dd43e47b..6af3302e908 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3434,17 +3434,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; }
@@ -11845,19 +11839,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 c5c3f26ecf1..2a2b2789077 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -82,6 +82,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);
 
 
 /*
@@ -3665,3 +3666,53 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * Like function isQueryUsingTempRelation_walker
+ * return true if any relation underlying
+ * the query is a global temporary table.
+ */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* Check if the query uses global temporary table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 2d857a301bb..c52bb35a3a8 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -447,6 +447,13 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	/*
+	 * If a sequence is bound to a global temporary table, then the sequence
+	 * must been "global temporary"
+	 */
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
@@ -3326,6 +3333,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		cxt.isforeign = false;
 	}
 	cxt.relation = stmt->relation;
+	/* Sets the table persistence to the context */
+	cxt.relation->relpersistence = RelationGetRelPersistence(rel);
 	cxt.rel = rel;
 	cxt.inhRelations = NIL;
 	cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index f6d05628764..0bd3914d706 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2116,6 +2116,14 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/*
+			 * Aotuvacuum cannot vacuum the private data stored in each backend
+			 * that belongs to global temporary table, so skip them.
+			 */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2182,7 +2190,7 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		if (RelpersistenceTsTemp(classForm->relpersistence))
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index b4532948d3f..1325f5e12ad 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlogutils.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -2934,7 +2935,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
-	if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind))
+	/*
+	 * Returns 0 if this global temporary table is not initialized in current
+	 * backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+	else if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind))
 	{
 		/*
 		 * Not every table AM uses BLCKSZ wide fixed size blocks.
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 9fa3e0631e6..cc3eb928bc6 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -23,6 +23,7 @@
 #include "access/syncscan.h"
 #include "access/twophase.h"
 #include "commands/async.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
@@ -143,6 +144,7 @@ CalculateShmemSize(int *num_semaphores)
 	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
@@ -246,6 +248,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* For global temporary table shared hashtable */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index a9945c80eb4..63d9d2ee80f 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -65,6 +65,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -5161,3 +5162,82 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temporary table.
+ */
+int
+list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct		*arrayP = NULL;
+	TransactionId		result = InvalidTransactionId;
+	int			index = 0;
+	int			i = 0;
+	uint8		flags = 0;
+
+	if (n)
+		*n = 0;
+
+	/* return 0 if feature is disabled */
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+	}
+
+	/* Disable in standby node */
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	arrayP = procArray;
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		uint8           statusFlags = ProcGlobal->statusFlags[index];
+
+		if (statusFlags & flags)
+			continue;
+
+		/* Fetch all backend that is belonging to MyDatabaseId */
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->backend_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->backend_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->backend_gtt_frozenxid, result))
+				result = proc->backend_gtt_frozenxid;
+
+			/* save backend pid and backend level oldest relfrozenxid */
+			if (pids)
+				pids[i] = proc->pid;
+
+			if (xids)
+				xids[i] = proc->backend_gtt_frozenxid;
+
+			i++;
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (n)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 862097352bb..4edd3b31f7a 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -176,7 +176,9 @@ static const char *const BuiltinTrancheNames[] = {
 	/* LWTRANCHE_PARALLEL_APPEND: */
 	"ParallelAppend",
 	/* LWTRANCHE_PER_XACT_PREDICATE_LIST: */
-	"PerXactPredicateList"
+	"PerXactPredicateList",
+	/* LWTRANCHE_GTT_CTL */
+	"GlobalTempTableControl"
 };
 
 StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index d1d3cd0dc88..fdc8f8e70bb 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -391,6 +391,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;	/* init backend level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
@@ -576,6 +577,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;	/* init backend level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index d5a7fb13f3c..8225cf6219f 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -982,6 +982,13 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/*
+			 * For global temporary table ,each backend has its own storage,
+			 * also only sees its own storage. Use Backendid to identify them.
+			 */
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 10895fb2876..66255eb7604 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -108,6 +108,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -5115,12 +5116,26 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								/* For global temporary table, get statistic data from localhash */
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -5368,15 +5383,28 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6820,6 +6848,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;
@@ -6837,6 +6866,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6848,6 +6885,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;
@@ -6863,6 +6902,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7781,6 +7828,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))
@@ -7793,6 +7842,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7805,6 +7863,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.
@@ -7824,6 +7884,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 9176514a962..6df1675b1f8 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -3113,6 +3114,19 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		/* For global temporary table, get statistic data from localhash */
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 105d8d4601c..18656f48442 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -65,6 +65,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1152,6 +1153,36 @@ retry:
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+				TransactionId	relfrozenxid = InvalidTransactionId;
+				MultiXactId 	relminmxid = InvalidMultiXactId;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+
+				/* For global temporary table, get relstat data from localhash */
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								&relfrozenxid,
+								&relminmxid);
+
+				/* And put them to local relcache */
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+				if (TransactionIdIsNormal(relfrozenxid))
+					relation->rd_rel->relfrozenxid = relfrozenxid;
+
+				if (MultiXactIdIsValid(relminmxid))
+					relation->rd_rel->relminmxid = relminmxid;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1212,6 +1243,10 @@ retry:
 	else
 		Assert(relation->rd_rel->relam == InvalidOid);
 
+	/* The state of the global temporary table's index may need to be set */
+	if (relation->rd_rel->relkind == RELKIND_INDEX)
+		gtt_fix_index_backend_state(relation);
+
 	/* extract reloptions if any */
 	RelationParseRelOptions(relation, pg_class_tuple);
 
@@ -1335,7 +1370,22 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+			/*
+			 * For global temporary table, get the latest relfilenode
+			 * from localhash and put it in relcache.
+			 */
+			if (OidIsValid(newrelnode) &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2288,6 +2338,9 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		/* The state of the global temporary table's index may need to be set */
+		gtt_fix_index_backend_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3568,6 +3621,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3674,28 +3731,39 @@ void
 RelationSetNewRelfilenode(Relation relation, char persistence)
 {
 	Oid			newrelfilenode;
-	Relation	pg_class;
-	HeapTuple	tuple;
+	Relation	pg_class = NULL;
+	HeapTuple	tuple = NULL;
 	Form_pg_class classform;
 	MultiXactId minmulti = InvalidMultiXactId;
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
 
+	/*
+	 * For global temporary table, storage information for the table is
+	 * maintained locally, not in catalog.
+	 */
+	bool		update_catalog = !RELATION_IS_GLOBAL_TEMP(relation);
+
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
 
-	/*
-	 * Get a writable copy of the pg_class tuple for the given relation.
-	 */
-	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+	memset(&classform, 0, sizeof(classform));
 
-	tuple = SearchSysCacheCopy1(RELOID,
-								ObjectIdGetDatum(RelationGetRelid(relation)));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "could not find tuple for relation %u",
-			 RelationGetRelid(relation));
-	classform = (Form_pg_class) GETSTRUCT(tuple);
+	if (update_catalog)
+	{
+		/*
+		 * Get a writable copy of the pg_class tuple for the given relation.
+		 */
+		pg_class = table_open(RelationRelationId, RowExclusiveLock);
+
+		tuple = SearchSysCacheCopy1(RELOID,
+									ObjectIdGetDatum(RelationGetRelid(relation)));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u",
+				 RelationGetRelid(relation));
+		classform = (Form_pg_class) GETSTRUCT(tuple);
+	}
 
 	/*
 	 * Schedule unlinking of the old storage at transaction commit.
@@ -3724,7 +3792,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);
 	}
 	else
@@ -3734,6 +3802,18 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 			 RelationGetRelationName(relation));
 	}
 
+	/* For global temporary table */
+	if (!update_catalog)
+	{
+		Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+		Assert(RELATION_IS_GLOBAL_TEMP(relation));
+		Assert(!RelationIsMapped(relation));
+
+		/* Make cache invalid and set new relnode to local cache. */
+		CacheInvalidateRelcache(relation);
+		relation->rd_node.relNode = relnode;
+	}
 	/*
 	 * If we're dealing with a mapped index, pg_class.relfilenode doesn't
 	 * change; instead we have to send the update to the relation mapper.
@@ -3743,7 +3823,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	 * possibly-inaccurate values of relpages etc, but those will be fixed up
 	 * later.
 	 */
-	if (RelationIsMapped(relation))
+	else if (RelationIsMapped(relation))
 	{
 		/* This case is only supported for indexes */
 		Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3789,9 +3869,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 		CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 	}
 
-	heap_freetuple(tuple);
+	if (update_catalog)
+	{
+		heap_freetuple(tuple);
 
-	table_close(pg_class, RowExclusiveLock);
+		table_close(pg_class, RowExclusiveLock);
+	}
 
 	/*
 	 * Make the pg_class row change or relation map change visible.  This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 7b030463013..d650382b2a8 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -44,6 +44,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "commands/prepare.h"
 #include "commands/trigger.h"
@@ -153,6 +154,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temporary table feature.
+ * table schema are still saved in catalog.
+ *
+ * num > 0 means allows the database to manage multiple active tables at the same time.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2131,6 +2144,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("Sets the amount of time to wait before forcing a "
@@ -2702,6 +2724,16 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"vacuum_gtt_defer_check_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("The defer check age of GTT, used to check expired data after vacuum."),
+			NULL
+		},
+		&vacuum_gtt_defer_check_age,
+		10000, 0, 1000000,
+		NULL, NULL, NULL
+	},
+
 	/*
 	 * See also CheckRequiredParameterValues() if this parameter changes
 	 */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ac291fbef21..5a2751ea4de 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2525,6 +2525,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
 		dopt->no_unlogged_table_data)
 		return;
 
+	/* Don't dump data in global temporary table/sequence */
+	if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		return;
+
 	/* Check that the data is not explicitly excluded */
 	if (simple_oid_list_member(&tabledata_exclude_oids,
 							   tbinfo->dobj.catId.oid))
@@ -14883,6 +14887,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
 		char	   *foreign = "";
+		char	   *table_type = NULL;
 
 		/*
 		 * Set reltypename, and collect any relkind-specific data that we
@@ -14958,9 +14963,15 @@ dumpTableSchema(Archive *fout, const 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);
 
@@ -15328,6 +15339,15 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 			}
 		}
 
+		/*
+		 * Transaction information for the global temporary table is not stored
+		 * in the pg_class.
+		 */
+		if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			Assert(tbinfo->frozenxid == 0);
+			Assert(tbinfo->minmxid == 0);
+		}
 		/*
 		 * In binary_upgrade mode, arrange to restore the old relfrozenxid and
 		 * relminmxid of all vacuumable relations.  (While vacuum.c processes
@@ -15335,7 +15355,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		 * of other relations; so this "if" lacks RELKIND_TOASTVALUE, and the
 		 * child toast table is handled below.)
 		 */
-		if (dopt->binary_upgrade &&
+		else if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
 			 tbinfo->relkind == RELKIND_MATVIEW))
 		{
@@ -16355,6 +16375,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	PQExpBuffer query = createPQExpBuffer();
 	PQExpBuffer delqry = createPQExpBuffer();
 	char	   *qseqname;
+	bool	   global_temp_seq = false;
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
@@ -16364,9 +16385,12 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
-						  "FROM pg_catalog.pg_sequence "
-						  "WHERE seqrelid = '%u'::oid",
+						  "seqcache, seqcycle, "
+						  "c.relpersistence "
+						  "FROM pg_catalog.pg_sequence s, "
+						  "pg_catalog.pg_class c "
+						  "WHERE seqrelid = '%u'::oid "
+						  "and s.seqrelid = c.oid",
 						  tbinfo->dobj.catId.oid);
 	}
 	else
@@ -16403,6 +16427,9 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
 
+	if (fout->remoteVersion >= 140000)
+		global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
 	if (strcmp(seqtype, "smallint") == 0)
@@ -16480,9 +16507,13 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	}
 	else
 	{
-		appendPQExpBuffer(query,
-						  "CREATE SEQUENCE %s\n",
-						  fmtQualifiedDumpable(tbinfo));
+		appendPQExpBuffer(query, "CREATE ");
+
+		if (global_temp_seq)
+			appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+		appendPQExpBuffer(query, "SEQUENCE %s\n",
+								  fmtQualifiedDumpable(tbinfo));
 
 		if (strcmp(seqtype, "bigint") != 0)
 			appendPQExpBuffer(query, "    AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index bc5fbd93c6c..2fe6a7c0959 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -88,7 +88,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -174,7 +174,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 1dbc09e6422..fb55f0b638e 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -298,9 +298,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -310,7 +312,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -395,7 +397,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -438,8 +440,17 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
 			 "         ON c.relnamespace = n.oid "
 			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
+			 CppAsString2(RELKIND_MATVIEW) ") AND ");
+
+	if (skip_gtt)
+	{
+		/* exclude global temp tables */
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+			"    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND ");
+	}
+
 	/* exclude possible orphaned temp tables */
+	snprintf(query + strlen(query), sizeof(query) - strlen(query),
 			 "    ((n.nspname !~ '^pg_temp_' AND "
 			 "      n.nspname !~ '^pg_toast_temp_' AND "
 			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index f85cb2e2620..e3b0a1f161b 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -410,7 +410,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -648,7 +648,10 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+			/* exclude global temp tables */
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -659,7 +662,10 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+		/* exclude global temp tables */
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 22169f10021..fb4c1f748ca 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -378,7 +378,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index c28788e84fa..d086d2f9dfa 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3739,7 +3739,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		 * Show whether a relation is permanent, temporary, or unlogged.
 		 */
 		appendPQExpBuffer(&buf,
-						  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+						  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+						  gettext_noop("session"),
 						  gettext_noop("permanent"),
 						  gettext_noop("temporary"),
 						  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b524dc87fc1..73ca85d1fab 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1059,6 +1059,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2624,6 +2626,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2854,6 +2859,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE", "SEQUENCE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 6ce480b49c1..dda3f3c5a60 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -85,7 +85,7 @@ extern Oid	heap_create_with_catalog(const char *relname,
 
 extern void heap_drop_with_catalog(Oid relid);
 
-extern void heap_truncate(List *relids);
+extern void heap_truncate(List *relids, bool is_global_temp);
 
 extern void heap_truncate_one_rel(Relation rel);
 
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 008f723e104..875b1003899 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -157,6 +157,7 @@ extern void reindex_index(Oid indexId, bool skip_constraint_checks,
 #define REINDEX_REL_CHECK_CONSTRAINTS		0x04
 #define REINDEX_REL_FORCE_INDEXES_UNLOGGED	0x08
 #define REINDEX_REL_FORCE_INDEXES_PERMANENT 0x10
+#define REINDEX_REL_PROCESS_GLOBAL_TEMP		0x20
 
 extern bool reindex_relation(Oid relid, int flags, ReindexParams *params);
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 93338d267c1..5237dd00921 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -172,6 +172,7 @@ DECLARE_INDEX(pg_class_tblspc_relfilenode_index, 3455, ClassTblspcRelfilenodeInd
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4d992dc2241..34465b2865d 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5729,6 +5729,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '9874',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '9875',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '9876',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '9877',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 0ab32b44e91..92e9f8ba485 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,7 +22,7 @@
 /* GUC variables */
 extern int	wal_skip_threshold;
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationPreTruncate(Relation rel);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 00000000000..cc023da8acd
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,45 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern int		vacuum_gtt_defer_check_age;
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Oid relid,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void force_enable_gtt_index(Relation index);
+extern void gtt_fix_index_backend_state(Relation index);
+extern void init_gtt_storage(CmdType operation, Relation relation);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index 40544dd4c70..7b66d808fc5 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 336549cc5f0..3e8167134b7 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -86,7 +86,7 @@ extern void find_composite_type_dependencies(Oid typeOid,
 
 extern void check_of_type(HeapTuple typetuple);
 
-extern void register_on_commit_action(Oid relid, OnCommitAction action);
+extern void register_on_commit_action(Oid relid, OnCommitAction action, bool is_gloal_temp);
 extern void remove_on_commit_action(Oid relid);
 
 extern void PreCommit_on_commit_actions(void);
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index cd57a704adc..717632637a9 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -575,7 +575,7 @@ exec_rt_fetch(Index rti, EState *estate)
 
 extern Relation ExecGetRangeTableRelation(EState *estate, Index rti);
 extern void ExecInitResultRelation(EState *estate, ResultRelInfo *resultRelInfo,
-								   Index rti);
+								   Index rti, CmdType operation);
 
 extern int	executor_errposition(EState *estate, int location);
 
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 8336c2c5a29..bddcfe7256d 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index c86ccdaf608..6b395551c1d 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -399,6 +399,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index a8f052e4845..4b4ed1a13aa 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -189,6 +189,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_SHARED_TIDBITMAP,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_PER_XACT_PREDICATE_LIST,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 44b477f49d7..1d621e3290e 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -164,6 +164,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId backend_gtt_frozenxid;	/* backend level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index b01fa52139a..8efffa55ac5 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -94,4 +94,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index aa18d304ac0..524c9d7de3f 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -288,6 +288,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 31281279cf9..39925543ac5 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	SMgrRelation rd_smgr;		/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -327,6 +327,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -609,11 +610,13 @@ RelationGetSmgr(Relation rel)
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -621,6 +624,7 @@ RelationGetSmgr(Relation rel)
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -633,6 +637,30 @@ RelationGetSmgr(Relation rel)
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP_ON_CURRENT_SESSION
+ *		Test a rel is either local temp relation of this session
+ *		or global temp relation.
+ */
+#define RELATION_IS_TEMP_ON_CURRENT_SESSION(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is local temp relation or global temporary relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RelpersistenceTsTemp
+ *		Test a relpersistence is local temp relation or global temporary relation.
+ */
+#define RelpersistenceTsTemp(relpersistence) \
+	(relpersistence == RELPERSISTENCE_TEMP || \
+	 relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -678,6 +706,19 @@ RelationGetSmgr(Relation rel)
 	 (relation)->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&	\
 	 !IsCatalogRelation(relation))
 
+/* For global temporary table */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/* Get on commit clause value only for global temporary table */
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+/* Get relpersistence for relation */
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
-- 
2.32.0 (Apple Git-132)

0004-gtt-v63-regress.patchapplication/octet-stream; name=0004-gtt-v63-regress.patch; x-unix-mode=0644Download
diff --git a/src/test/isolation/expected/gtt-sequence.out b/src/test/isolation/expected/gtt-sequence.out
new file mode 100644
index 00000000000..31db2ebd423
--- /dev/null
+++ b/src/test/isolation/expected/gtt-sequence.out
@@ -0,0 +1,48 @@
+unused step name: s1_seq_restart
+Parsed test spec with 2 sessions
+
+starting permutation: s1_seq_next s2_seq_next s1_seq_next
+step s1_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      1
+(1 row)
+
+step s2_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      1
+(1 row)
+
+step s1_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      2
+(1 row)
+
+
+starting permutation: s1_select s2_select s1_insert s2_insert s1_select s2_select
+step s1_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+(0 rows)
+
+step s2_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+(0 rows)
+
+step s1_insert: insert into gtt_with_seq values(1);
+step s2_insert: insert into gtt_with_seq values(10);
+step s1_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+ 1| 3
+(1 row)
+
+step s2_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+10| 1
+(1 row)
+
diff --git a/src/test/isolation/expected/gtt-table.out b/src/test/isolation/expected/gtt-table.out
new file mode 100644
index 00000000000..5825773aa12
--- /dev/null
+++ b/src/test/isolation/expected/gtt-table.out
@@ -0,0 +1,675 @@
+Parsed test spec with 3 sessions
+
+starting permutation: s1_update_d
+step s1_update_d: update gtt_on_commit_delete_row set b = 'update'
+
+starting permutation: s1_select_d s1_insert_d s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_begin s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+1|test1
+(1 row)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_begin s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+1|test1
+(1 row)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_select_d s1_truncate_d s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_begin s1_insert_d s1_select_d s1_truncate_d s1_select_d s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_begin s1_insert_d s1_select_d s1_truncate_d s1_select_d s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_insert_d s1_select_d s1_begin s1_insert_d s1_select_d s1_save_1 s1_truncate_d s1_insert_d s1_select_d s1_save_2 s1_truncate_d s1_insert_d s1_select_d s1_save_3 s1_rollback_to_save_2 s1_select_d s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_save_1: SAVEPOINT save1
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_save_2: SAVEPOINT save2
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+4|test1
+(1 row)
+
+step s1_save_3: SAVEPOINT save3
+step s1_rollback_to_save_2: rollback to savepoint save2
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+5|test1
+(2 rows)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_insert_d s1_select_d s1_begin s1_insert_d s1_select_d s1_save_1 s1_truncate_d s1_insert_d s1_select_d s1_save_2 s1_truncate_d s1_insert_d s1_select_d s1_save_3 s1_rollback_to_save_2 s1_select_d s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_save_1: SAVEPOINT save1
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_save_2: SAVEPOINT save2
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+4|test1
+(1 row)
+
+step s1_save_3: SAVEPOINT save3
+step s1_rollback_to_save_2: rollback to savepoint save2
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+5|test1
+(2 rows)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_update_p
+step s2_update_p: update gtt_on_commit_preserve_row set b = 'update'
+
+starting permutation: s2_select_p s2_insert_p s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_begin s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_begin s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_select_p s2_insert_p s2_select_p s2_truncate_p s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_select_p s2_insert_p s2_begin s2_insert_p s2_select_p s2_truncate_p s2_select_p s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_insert_p s2_begin s2_insert_p s2_select_p s2_truncate_p s2_select_p s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_insert_p s2_select_p s2_begin s2_insert_p s2_select_p s2_save_1 s2_truncate_p s2_insert_p s2_select_p s2_save_2 s2_truncate_p s2_insert_p s2_select_p s2_save_3 s2_rollback_to_save_2 s2_select_p s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_save_1: SAVEPOINT save1
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_save_2: SAVEPOINT save2
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+4|test10
+(1 row)
+
+step s2_save_3: SAVEPOINT save3
+step s2_rollback_to_save_2: rollback to savepoint save2
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+
+starting permutation: s2_insert_p s2_select_p s2_begin s2_insert_p s2_select_p s2_save_1 s2_truncate_p s2_insert_p s2_select_p s2_save_2 s2_truncate_p s2_insert_p s2_select_p s2_save_3 s2_rollback_to_save_2 s2_select_p s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_save_1: SAVEPOINT save1
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_save_2: SAVEPOINT save2
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+4|test10
+(1 row)
+
+step s2_save_3: SAVEPOINT save3
+step s2_rollback_to_save_2: rollback to savepoint save2
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_insert_p s2_insert_p s1_select_p s2_select_p
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_truncate_p s2_truncate_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_begin s1_insert_d s2_insert_d s1_truncate_d s2_insert_d s1_commit
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s2_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s2_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_commit: commit
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_reindex_p s2_reindex_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_reindex_p: reindex table gtt_on_commit_preserve_row
+step s2_reindex_p: reindex table gtt_on_commit_preserve_row
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_reindex_i_p s2_reindex_i_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_reindex_i_p: reindex index gtt_on_commit_preserve_row_pkey
+step s2_reindex_i_p: reindex index gtt_on_commit_preserve_row_pkey
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_insert_c s3_create_c s3_insert_c s1_insert_c s1_analyze_c s2_analyze_c s3_analyze_c s1_select_c s2_select_c s3_select_c
+step s2_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s3_create_c: create unique index idx_temp_table_a on gtt_test_createindex(a)
+step s3_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s1_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s1_analyze_c: analyze gtt_test_createindex
+step s2_analyze_c: analyze gtt_test_createindex
+step s3_analyze_c: analyze gtt_test_createindex
+step s1_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                                               
+---------------------------------------------------------
+Index Scan using idx_temp_table_a on gtt_test_createindex
+  Index Cond: (a = 1)                                    
+(2 rows)
+
+step s2_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                      
+--------------------------------
+Seq Scan on gtt_test_createindex
+  Filter: (a = 1)               
+(2 rows)
+
+step s3_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                                               
+---------------------------------------------------------
+Index Scan using idx_temp_table_a on gtt_test_createindex
+  Index Cond: (a = 1)                                    
+(2 rows)
+
+
+starting permutation: s1_begin s2_begin s1_lock_p s2_lock_p s1_truncate_p s2_truncate_p s1_insert_p s2_insert_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_lock_p: LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE
+step s2_lock_p: LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE
+step s1_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index 99c23b16ffe..f5c8eec533d 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -97,3 +97,5 @@ test: plpgsql-toast
 test: truncate-conflict
 test: serializable-parallel
 test: serializable-parallel-2
+test: gtt-sequence
+test: gtt-table
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 12179f25146..7cf0b20e636 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -80,9 +80,30 @@ disconnect_atexit(void)
 {
 	int			i;
 
-	for (i = 0; i < nconns; i++)
+	for (i = 1; i < nconns; i++)
 		if (conns[i].conn)
 			PQfinish(conns[i].conn);
+
+	if (parseresult.destroy)
+	{
+		PGresult   *res;
+
+		res = PQexec(conns[0].conn, parseresult.destroy);
+		if (PQresultStatus(res) == PGRES_TUPLES_OK)
+		{
+			printResultSet(res);
+		}
+		else if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		{
+			fprintf(stderr, "destroy failed: %s",
+					PQerrorMessage(conns[0].conn));
+			/* don't exit on teardown failure */
+		}
+		PQclear(res);
+	}
+
+	if (conns[0].conn)
+		PQfinish(conns[0].conn);
 }
 
 int
@@ -238,6 +259,24 @@ main(int argc, char **argv)
 	PQclear(res);
 	termPQExpBuffer(&wait_query);
 
+	if (parseresult.initialize)
+	{
+		PGresult   *res;
+
+		res = PQexec(conns[0].conn, parseresult.initialize);
+		if (PQresultStatus(res) == PGRES_TUPLES_OK)
+		{
+			printResultSet(res);
+		}
+		else if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		{
+			fprintf(stderr, "initialize failed: %s",
+					PQerrorMessage(conns[0].conn));
+			/* don't exit on teardown failure */
+		}
+		PQclear(res);
+	}
+
 	/*
 	 * Run the permutations specified in the spec, or all if none were
 	 * explicitly specified.
diff --git a/src/test/isolation/isolationtester.h b/src/test/isolation/isolationtester.h
index 5f300219c20..b5a29893da9 100644
--- a/src/test/isolation/isolationtester.h
+++ b/src/test/isolation/isolationtester.h
@@ -81,6 +81,8 @@ typedef struct
 	int			nsessions;
 	Permutation **permutations;
 	int			npermutations;
+	char	   *initialize;
+	char	   *destroy;
 } TestSpec;
 
 extern TestSpec parseresult;
diff --git a/src/test/isolation/specparse.y b/src/test/isolation/specparse.y
index c25aa1a73fa..2784f758ed9 100644
--- a/src/test/isolation/specparse.y
+++ b/src/test/isolation/specparse.y
@@ -39,7 +39,7 @@ TestSpec		parseresult;			/* result of parsing is left here */
 }
 
 %type <ptr_list> setup_list
-%type <str>  opt_setup opt_teardown
+%type <str>  opt_setup opt_teardown opt_initialize opt_destroy
 %type <str> setup
 %type <ptr_list> step_list session_list permutation_list opt_permutation_list
 %type <ptr_list> permutation_step_list blocker_list
@@ -51,23 +51,27 @@ TestSpec		parseresult;			/* result of parsing is left here */
 
 %token <str> sqlblock identifier
 %token <integer> INTEGER
-%token NOTICES PERMUTATION SESSION SETUP STEP TEARDOWN TEST
+%token NOTICES PERMUTATION SESSION SETUP STEP TEARDOWN TEST INITIALIZE DESTROY
 
 %%
 
 TestSpec:
+			opt_initialize
 			setup_list
 			opt_teardown
+			opt_destroy
 			session_list
 			opt_permutation_list
 			{
-				parseresult.setupsqls = (char **) $1.elements;
-				parseresult.nsetupsqls = $1.nelements;
-				parseresult.teardownsql = $2;
-				parseresult.sessions = (Session **) $3.elements;
-				parseresult.nsessions = $3.nelements;
-				parseresult.permutations = (Permutation **) $4.elements;
-				parseresult.npermutations = $4.nelements;
+				parseresult.setupsqls = (char **) $2.elements;
+				parseresult.nsetupsqls = $2.nelements;
+				parseresult.teardownsql = $3;
+				parseresult.sessions = (Session **) $5.elements;
+				parseresult.nsessions = $5.nelements;
+				parseresult.permutations = (Permutation **) $6.elements;
+				parseresult.npermutations = $6.nelements;
+				parseresult.initialize = $1;
+				parseresult.destroy = $4;
 			}
 		;
 
@@ -100,6 +104,16 @@ opt_teardown:
 			| TEARDOWN sqlblock	{ $$ = $2; }
 		;
 
+opt_initialize:
+			/* EMPTY */			{ $$ = NULL; }
+			| INITIALIZE sqlblock	{ $$ = $2; }
+		;
+
+opt_destroy:
+			/* EMPTY */			{ $$ = NULL; }
+			| DESTROY sqlblock	{ $$ = $2; }
+		;
+
 session_list:
 			session_list session
 			{
diff --git a/src/test/isolation/specs/gtt-sequence.spec b/src/test/isolation/specs/gtt-sequence.spec
new file mode 100644
index 00000000000..88eece45e29
--- /dev/null
+++ b/src/test/isolation/specs/gtt-sequence.spec
@@ -0,0 +1,39 @@
+# Tests for global temporary relations
+
+initialize
+{
+  CREATE GLOBAL TEMPORARY TABLE if not exists gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+}
+
+destroy
+{
+  /* wait other backend exit */
+  select pg_sleep(1);
+
+  DROP TABLE gtt_with_seq;
+}
+
+# Session 1
+session "s1"
+step "s1_seq_next" { select nextval('gtt_with_seq_c2_seq'); }
+step "s1_seq_restart" { alter sequence gtt_with_seq_c2_seq RESTART; }
+step "s1_insert" { insert into gtt_with_seq values(1); }
+step "s1_select" { select * from gtt_with_seq order by c1,c2; }
+teardown
+{
+  TRUNCATE gtt_with_seq;
+}
+
+# Session 2
+session "s2"
+step "s2_seq_next" { select nextval('gtt_with_seq_c2_seq'); }
+step "s2_insert" { insert into gtt_with_seq values(10); }
+step "s2_select" { select * from gtt_with_seq order by c1,c2; }
+teardown
+{
+  TRUNCATE gtt_with_seq RESTART IDENTITY;
+}
+
+permutation "s1_seq_next" "s2_seq_next" "s1_seq_next"
+permutation "s1_select" "s2_select" "s1_insert" "s2_insert" "s1_select" "s2_select"
+
diff --git a/src/test/isolation/specs/gtt-table.spec b/src/test/isolation/specs/gtt-table.spec
new file mode 100644
index 00000000000..e0396b21ef0
--- /dev/null
+++ b/src/test/isolation/specs/gtt-table.spec
@@ -0,0 +1,135 @@
+# Tests for global temporary relations
+
+initialize
+{
+  create global temp table gtt_on_commit_delete_row(a bigserial primary key, b text) on commit delete rows;
+  create global temp table gtt_on_commit_preserve_row(a bigserial primary key, b text) on commit preserve rows;
+  create global temp table gtt_test_createindex(a int, b char(1000)) on commit preserve rows;
+}
+
+destroy
+{
+  /* wait other backend exit */
+  select pg_sleep(1);
+
+  DROP TABLE gtt_on_commit_delete_row;
+  DROP TABLE gtt_on_commit_preserve_row;
+  DROP TABLE gtt_test_createindex;
+}
+
+# Session 1
+session "s1"
+step "s1_begin" {begin}
+step "s1_commit" {commit}
+step "s1_rollback" {rollback}
+step "s1_insert_d" {insert into gtt_on_commit_delete_row (b) values('test1')}
+step "s1_select_d" {select a,b from gtt_on_commit_delete_row order by a,b}
+step "s1_insert_p" {insert into gtt_on_commit_preserve_row (b) values('test20')}
+step "s1_select_p" {select a,b from gtt_on_commit_preserve_row order by a,b}
+step "s1_truncate_d" {truncate gtt_on_commit_delete_row}
+step "s1_truncate_p" {truncate gtt_on_commit_preserve_row}
+step "s1_lock_p" {LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE}
+step "s1_update_d" {update gtt_on_commit_delete_row set b = 'update'}
+step "s1_save_1" {SAVEPOINT save1}
+step "s1_save_2" {SAVEPOINT save2}
+step "s1_save_3" {SAVEPOINT save3}
+step "s1_rollback_to_save_2" {rollback to savepoint save2}
+step "s1_reindex_p" {reindex table gtt_on_commit_preserve_row}
+step "s1_reindex_i_p" {reindex index gtt_on_commit_preserve_row_pkey}
+step "s1_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s1_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s1_analyze_c" {analyze gtt_test_createindex}
+teardown
+{
+  TRUNCATE gtt_on_commit_delete_row RESTART IDENTITY;
+  TRUNCATE gtt_on_commit_preserve_row RESTART IDENTITY;
+}
+
+# Session 2
+session "s2"
+step "s2_begin" {begin}
+step "s2_commit" {commit}
+step "s2_rollback" {rollback}
+step "s2_insert_p" {insert into gtt_on_commit_preserve_row (b) values('test10')}
+step "s2_select_p" {select a,b from gtt_on_commit_preserve_row order by a,b}
+step "s2_insert_d" {insert into gtt_on_commit_delete_row (b) values('test1')}
+step "s2_truncate_p" {truncate gtt_on_commit_preserve_row}
+step "s2_update_p" {update gtt_on_commit_preserve_row set b = 'update'}
+step "s2_lock_p" {LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE}
+step "s2_save_1" {SAVEPOINT save1}
+step "s2_save_2" {SAVEPOINT save2}
+step "s2_save_3" {SAVEPOINT save3}
+step "s2_rollback_to_save_2" {rollback to savepoint save2}
+step "s2_reindex_p" {reindex table gtt_on_commit_preserve_row}
+step "s2_reindex_i_p" {reindex index gtt_on_commit_preserve_row_pkey}
+step "s2_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s2_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s2_analyze_c" {analyze gtt_test_createindex}
+teardown
+{
+  TRUNCATE gtt_on_commit_delete_row RESTART IDENTITY;
+  TRUNCATE gtt_on_commit_preserve_row RESTART IDENTITY;
+}
+
+session "s3"
+step "s3_create_c" {create unique index idx_temp_table_a on gtt_test_createindex(a)}
+step "s3_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s3_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s3_analyze_c" {analyze gtt_test_createindex}
+
+
+#
+# test on commit delete temp table
+#
+
+# test update empty temp table
+permutation "s1_update_d"
+# test insert into temp table
+permutation "s1_select_d" "s1_insert_d" "s1_select_d"
+# test temp table in transaction(commit)
+permutation "s1_select_d" "s1_begin"    "s1_insert_d" "s1_select_d"   "s1_commit"   "s1_select_d" 
+# test temp table in transaction(rollback)
+permutation "s1_select_d" "s1_begin"    "s1_insert_d" "s1_select_d"   "s1_rollback" "s1_select_d" 
+# test truncate
+permutation "s1_select_d" "s1_insert_d" "s1_select_d" "s1_truncate_d" "s1_select_d"
+# test truncate in transaction block
+permutation "s1_select_d" "s1_insert_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_truncate_d" "s1_select_d"   "s1_insert_d" "s1_select_d" "s1_commit"   "s1_select_d" 
+permutation "s1_select_d" "s1_insert_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_truncate_d" "s1_select_d"   "s1_insert_d" "s1_select_d" "s1_rollback" "s1_select_d"
+# test temp table with subtransaction or savepoint
+permutation "s1_insert_d" "s1_select_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_save_1"     "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_2"   "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_3" "s1_rollback_to_save_2" "s1_select_d" "s1_insert_d" "s1_select_d" "s1_commit" "s1_select_d"
+permutation "s1_insert_d" "s1_select_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_save_1"     "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_2"   "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_3" "s1_rollback_to_save_2" "s1_select_d" "s1_insert_d" "s1_select_d" "s1_rollback" "s1_select_d"
+
+#
+# test on commit preserve table
+#
+
+# same as test on commit delete temp table
+permutation "s2_update_p"
+permutation "s2_select_p" "s2_insert_p" "s2_select_p"
+permutation "s2_select_p" "s2_begin"    "s2_insert_p" "s2_select_p"   "s2_commit"   "s2_select_p"
+permutation "s2_select_p" "s2_begin"    "s2_insert_p" "s2_select_p"   "s2_rollback" "s2_select_p"
+permutation "s2_select_p" "s2_insert_p" "s2_select_p" "s2_truncate_p" "s2_select_p"
+permutation "s2_select_p" "s2_insert_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_truncate_p" "s2_select_p"   "s2_insert_p" "s2_select_p" "s2_commit"   "s2_select_p" 
+permutation "s2_select_p" "s2_insert_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_truncate_p" "s2_select_p"   "s2_insert_p" "s2_select_p" "s2_rollback" "s2_select_p" 
+permutation "s2_insert_p" "s2_select_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_save_1"     "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_2"   "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_3" "s2_rollback_to_save_2" "s2_select_p" "s2_insert_p" "s2_select_p" "s2_commit" "s2_select_p"
+permutation "s2_insert_p" "s2_select_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_save_1"     "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_2"   "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_3" "s2_rollback_to_save_2" "s2_select_p" "s2_insert_p" "s2_select_p" "s2_rollback" "s2_select_p"
+
+#
+# test concurrent operation on temp table
+#
+
+#  test concurrent read
+permutation "s1_insert_p" "s2_insert_p" "s1_select_p" "s2_select_p" 
+#  test concurrent truncate
+permutation "s1_begin" "s2_begin"    "s1_insert_p" "s2_insert_p"   "s1_truncate_p" "s2_truncate_p"  "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+permutation "s1_begin" "s1_insert_d" "s2_insert_d" "s1_truncate_d" "s2_insert_d"   "s1_commit" 
+#  test concurrent reindex table
+permutation "s1_begin" "s2_begin" "s1_insert_p" "s2_insert_p" "s1_reindex_p"   "s2_reindex_p"   "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+#  test concurrent reindex index
+permutation "s1_begin" "s2_begin" "s1_insert_p" "s2_insert_p" "s1_reindex_i_p" "s2_reindex_i_p" "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+
+# test create index
+permutation "s2_insert_c" "s3_create_c" "s3_insert_c" "s1_insert_c" "s1_analyze_c" "s2_analyze_c" "s3_analyze_c" "s1_select_c" "s2_select_c" "s3_select_c"
+
+# test lock gtt
+permutation "s1_begin" "s2_begin" "s1_lock_p" "s2_lock_p" "s1_truncate_p" "s2_truncate_p" "s1_insert_p" "s2_insert_p" "s1_commit" "s2_commit" "s1_select_p" "s2_select_p"
diff --git a/src/test/isolation/specscanner.l b/src/test/isolation/specscanner.l
index d9fa6a5b54a..697db975479 100644
--- a/src/test/isolation/specscanner.l
+++ b/src/test/isolation/specscanner.l
@@ -67,6 +67,8 @@ session			{ return SESSION; }
 setup			{ return SETUP; }
 step			{ return STEP; }
 teardown		{ return TEARDOWN; }
+initialize		 { return INITIALIZE; }
+destroy			 { return DESTROY; }
 
  /* Whitespace and comments */
 [\n]			{ yyline++; }
diff --git a/src/test/regress/expected/global_temporary_table.out b/src/test/regress/expected/global_temporary_table.out
new file mode 100644
index 00000000000..5adc58d77e1
--- /dev/null
+++ b/src/test/regress/expected/global_temporary_table.out
@@ -0,0 +1,574 @@
+--
+-- GLobal emparary table test case 
+--
+CREATE SCHEMA IF NOT EXISTS global_temporary_table;
+set search_path=global_temporary_table,sys;
+--
+--  test create global temp table basic syntax
+--
+create global temp table gtt_on_commit_default(a int primary key, b text);
+create global temp table gtt_on_commit_delete(a int primary key, b text) on commit delete rows;
+create global temp table gtt_on_commit_delete2(n int) with (on_commit_delete_rows='true');
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit PRESERVE rows;
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt_test_createindex(c0 tsvector,c1 varchar(100), c2 int);
+create global temp table gtt_test_alter(b text) with(on_commit_delete_rows=true);
+--
+-- test DML on global temp table
+--
+-- update empty temp table
+update gtt_on_commit_delete set b ='test';
+begin;
+insert into gtt_on_commit_delete values (1);
+update gtt_on_commit_delete set b ='test';
+-- should 1 row
+select * from gtt_on_commit_delete;
+ a |  b   
+---+------
+ 1 | test
+(1 row)
+
+commit;
+-- data delete after transaction commit
+-- should 0 row
+select * from gtt_on_commit_delete;
+ a | b 
+---+---
+(0 rows)
+
+-- update empty temp table
+update gtt_on_commit_preserve set b ='test';
+insert into gtt_on_commit_preserve values (2);
+begin;
+insert into gtt_on_commit_preserve values (3);
+update gtt_on_commit_preserve set b ='test';
+-- should 2 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 2 | test
+ 3 | test
+(2 rows)
+
+delete from gtt_on_commit_preserve where a=2;
+commit;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 3 | test
+(1 row)
+
+begin;
+insert into gtt_on_commit_preserve values (4);
+-- temp table support truncate;
+truncate gtt_on_commit_preserve;
+select * from gtt_on_commit_preserve order by a;
+ a | b 
+---+---
+(0 rows)
+
+rollback;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 3 | test
+(1 row)
+
+--
+-- test unsupported global temp partition table
+--
+-- should fail
+CREATE global temp TABLE global_temp_partition_01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+on commit delete rows;
+ERROR:  Only support global temporary regular table.
+CREATE TABLE regular_partition_01_2019 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+);
+CREATE TABLE regular_partition01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time);
+-- should fail
+CREATE global temp TABLE temp_partition01_2018
+PARTITION OF regular_partition01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+ERROR:  Not support global temporary partition table or inherit table.
+CREATE global temp TABLE global_temp_partition_01_2021 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+)on commit delete rows;
+-- should fail
+ALTER TABLE regular_partition01 ATTACH PARTITION global_temp_partition_01_2021 FOR VALUES FROM ('2021-01-01 00:00:00') TO ('2022-01-01 00:00:00');
+ERROR:  cannot attach a global temporary relation as partition of permanent relation "regular_partition01"
+--
+-- test unsupported inherit table
+--
+create table 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 inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent);
+ERROR:  Not support global temporary partition table or inherit table.
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent_global_temp) on commit delete rows;
+ERROR:  Not support global temporary partition table or inherit table.
+--
+-- test DDL on global temp table
+--
+create index idx_gtt_test_alter_b on gtt_test_alter (b);
+insert into gtt_test_alter values('test');
+alter table gtt_test_alter alter b type varchar;
+create index gtt_idx_1 on gtt_test_createindex using gin (c0);
+create index gtt_idx_2 on gtt_test_createindex using gist (c0);
+create index gtt_idx_3 on gtt_test_createindex using hash (c2);
+alter table gtt_test_rename rename to gtt_test_new;
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+create index CONCURRENTLY idx_b on gtt_on_commit_default (b);
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+insert into gtt_on_commit_default values(1,'test');
+create global temp table gtt_test_alter1 (a int primary key,b text);
+alter table gtt_test_alter1 alter a type varchar;
+-- should fail
+alter table gtt_on_commit_default alter a type varchar;
+ERROR:  Only support alter global temporary table in an empty context.
+HINT:  Please create a new connection and execute ALTER TABLE on the new connection.
+-- should fail
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+ERROR:  not support cluster global temporary table yet
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- should fail
+alter table gtt_on_commit_default SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temporary table
+-- should fail
+alter table gtt_on_commit_default set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table
+-- should fail
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- should fail
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- should fail
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit drop;
+ERROR:  global temporary table not support on commit drop clause
+-- should fail
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  could not create global temporary table with on commit and with clause at same time
+-- should fail
+CREATE MATERIALIZED VIEW mv_gtt_on_commit_default as select * from gtt_on_commit_default;
+ERROR:  materialized views must not use global temporary tables or views
+-- test create table as select
+CREATE GLOBAL TEMPORARY TABLE test_create_table_as AS SELECT 1 AS a;
+-- test copy stmt
+create global temp table gtt_copytest (
+        c1 int,
+        "col with , comma" text,
+        "col with "" quote"  int);
+copy gtt_copytest from stdin csv header;
+select count(*) from gtt_copytest;
+ count 
+-------
+     2
+(1 row)
+
+--
+-- test foreign key dependencies for global temp table
+--
+CREATE global temp TABLE temp_products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+CREATE global temp TABLE temp_orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+)on commit delete rows;
+-- should fail
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+CREATE global temp TABLE temp_orders_2 (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products(product_no),
+    quantity integer
+);
+--should fail
+insert into temp_orders values(1,1,1);
+ERROR:  insert or update on table "temp_orders" violates foreign key constraint "temp_orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "temp_products".
+insert into temp_products values(1,'test',1.0);
+begin;
+insert into temp_orders values(1,1,1);
+commit;
+-- should 1 row
+select count(*) from temp_products;
+ count 
+-------
+     1
+(1 row)
+
+-- should 0 row
+select count(*) from temp_orders;
+ count 
+-------
+     0
+(1 row)
+
+--
+-- test sequence on global temp table
+--
+create global temp table global_temp_with_serial (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+-- support insert data to temp table in read only transaction
+insert into global_temp_with_serial (b) values(1);
+select * from global_temp_with_serial;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_delete(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_preserve(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table temp_table_with_sequence_oncommit_delete add c2 int default nextval('seq_1');
+alter table temp_table_with_sequence_oncommit_preserve add c2 int default nextval('seq_1');
+begin;
+insert into temp_table_with_sequence_oncommit_delete (c1)values(1);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(2);
+insert into temp_table_with_sequence_oncommit_delete (c1)values(3);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(4);
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  3 |  3
+(2 rows)
+
+commit;
+-- should 0 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_preserve order by c1;
+ c1 | c2 
+----+----
+  2 |  2
+  4 |  4
+(2 rows)
+
+--
+-- test statistics on temp table
+--
+create global temp table temp_table_test_statistics(a int);
+insert into temp_table_test_statistics values(generate_series(1,100000));
+create index idx_test_1 on temp_table_test_statistics (a);
+create index idx_test_2 on temp_table_test_statistics((a*10));
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+                   QUERY PLAN                   
+------------------------------------------------
+ Bitmap Heap Scan on temp_table_test_statistics
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_test_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+                   QUERY PLAN                   
+------------------------------------------------
+ Bitmap Heap Scan on temp_table_test_statistics
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_test_2
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+-- test statistic for whole row
+explain (costs off) select count(*) from temp_table_test_statistics group by temp_table_test_statistics;
+                  QUERY PLAN                  
+----------------------------------------------
+ HashAggregate
+   Group Key: temp_table_test_statistics.*
+   ->  Seq Scan on temp_table_test_statistics
+(3 rows)
+
+-- test statistic for system column
+explain (costs off) select count(*) from temp_table_test_statistics group by tableoid;
+                  QUERY PLAN                  
+----------------------------------------------
+ HashAggregate
+   Group Key: tableoid
+   ->  Seq Scan on temp_table_test_statistics
+(3 rows)
+
+analyze temp_table_test_statistics;
+-- indexscan by idx_test_1
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Index Only Scan using idx_test_1 on temp_table_test_statistics
+   Index Cond: (a = 200000)
+(2 rows)
+
+-- indexscan by idx_test_2
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+                        QUERY PLAN                         
+-----------------------------------------------------------
+ Index Scan using idx_test_2 on temp_table_test_statistics
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+explain (costs off) select count(*) from temp_table_test_statistics group by temp_table_test_statistics;
+                  QUERY PLAN                  
+----------------------------------------------
+ HashAggregate
+   Group Key: temp_table_test_statistics.*
+   ->  Seq Scan on temp_table_test_statistics
+(3 rows)
+
+explain (costs off) select count(*) from temp_table_test_statistics group by tableoid;
+                  QUERY PLAN                  
+----------------------------------------------
+ HashAggregate
+   Group Key: tableoid
+   ->  Seq Scan on temp_table_test_statistics
+(3 rows)
+
+--
+-- test temp table with toast table
+--
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |                0 |        483328 |           98304 |                 581632
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            32768 |                  |         32768 |               0 |                  32768
+(3 rows)
+
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |             8192 |        499712 |          106496 |                 606208
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            40960 |                  |         40960 |               0 |                  40960
+(3 rows)
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,10),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',10);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |             8192 |                0 |         16384 |           32768 |                  49152
+ idx_gtt_t_kenyon_1 |            16384 |                  |         16384 |               0 |                  16384
+ idx_gtt_t_kenyon_2 |            16384 |                  |         16384 |               0 |                  16384
+(3 rows)
+
+rollback;
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |             8192 |        499712 |          106496 |                 606208
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            40960 |                  |         40960 |               0 |                  40960
+(3 rows)
+
+-- test analyze/vacuum on global temp table
+ANALYZE gtt_t_kenyon;
+VACUUM gtt_t_kenyon;
+--
+-- test global temp table system view
+--
+create global temp table temp_table_test_systemview(a int primary key, b text) on commit PRESERVE rows;
+-- should empty, storage not initialized
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+ tablename 
+-----------
+(0 rows)
+
+-- should empty, storage not initialized
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into temp_table_test_systemview values(generate_series(1,10000),'test');
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+         tablename          
+----------------------------
+ temp_table_test_systemview
+(1 row)
+
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+       schemaname       |            tablename            | relpages | reltuples | relallvisible 
+------------------------+---------------------------------+----------+-----------+---------------
+ global_temporary_table | temp_table_test_systemview      |        0 |         0 |             0
+ global_temporary_table | temp_table_test_systemview_pkey |        1 |         0 |             0
+(2 rows)
+
+-- should empty
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+analyze temp_table_test_systemview;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+       schemaname       |            tablename            | relpages | reltuples | relallvisible 
+------------------------+---------------------------------+----------+-----------+---------------
+ global_temporary_table | temp_table_test_systemview      |       55 |     10000 |             0
+ global_temporary_table | temp_table_test_systemview_pkey |       30 |     10000 |             0
+(2 rows)
+
+-- get data after analyze;
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+       schemaname       |         tablename          | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------------------+----------------------------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ global_temporary_table | temp_table_test_systemview | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ global_temporary_table | temp_table_test_systemview | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+-- get all object info in current schema
+select relname ,relkind, relpersistence, reloptions from pg_class c, pg_namespace n where c.relnamespace = n.oid and n.nspname = 'global_temporary_table' order by relkind,relpersistence,relname;
+                     relname                     | relkind | relpersistence |          reloptions           
+-------------------------------------------------+---------+----------------+-------------------------------
+ global_temp_partition_01_2021_id_seq            | S       | g              | {on_commit_delete_rows=false}
+ global_temp_with_serial_a_seq                   | S       | g              | {on_commit_delete_rows=false}
+ regular_partition01_id_seq                      | S       | p              | 
+ regular_partition_01_2019_id_seq                | S       | p              | 
+ seq_1                                           | S       | p              | 
+ gtt_idx_1                                       | i       | g              | 
+ gtt_idx_2                                       | i       | g              | 
+ gtt_idx_3                                       | i       | g              | 
+ gtt_on_commit_default_pkey                      | i       | g              | 
+ gtt_on_commit_delete_pkey                       | i       | g              | 
+ gtt_on_commit_preserve_pkey                     | i       | g              | 
+ gtt_test_alter1_pkey                            | i       | g              | 
+ gtt_test_rename_pkey                            | i       | g              | 
+ idx_b                                           | i       | g              | 
+ idx_gtt_t_kenyon_1                              | i       | g              | 
+ idx_gtt_t_kenyon_2                              | i       | g              | 
+ idx_gtt_test_alter_b                            | i       | g              | 
+ idx_test_1                                      | i       | g              | 
+ idx_test_2                                      | i       | g              | 
+ products_pkey                                   | i       | g              | 
+ temp_orders_2_pkey                              | i       | g              | 
+ temp_orders_pkey                                | i       | g              | 
+ temp_products_pkey                              | i       | g              | 
+ temp_table_test_systemview_pkey                 | i       | g              | 
+ temp_table_with_sequence_oncommit_delete_pkey   | i       | g              | 
+ temp_table_with_sequence_oncommit_preserve_pkey | i       | g              | 
+ regular_partition01                             | p       | p              | 
+ global_temp_partition_01_2021                   | r       | g              | {on_commit_delete_rows=true}
+ global_temp_with_serial                         | r       | g              | {on_commit_delete_rows=false}
+ gtt_copytest                                    | r       | g              | {on_commit_delete_rows=false}
+ gtt_on_commit_default                           | r       | g              | {on_commit_delete_rows=false}
+ gtt_on_commit_delete                            | r       | g              | {on_commit_delete_rows=true}
+ gtt_on_commit_delete2                           | r       | g              | {on_commit_delete_rows=true}
+ gtt_on_commit_preserve                          | r       | g              | {on_commit_delete_rows=false}
+ gtt_t_kenyon                                    | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_alter                                  | r       | g              | {on_commit_delete_rows=true}
+ gtt_test_alter1                                 | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_createindex                            | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_new                                    | r       | g              | {on_commit_delete_rows=false}
+ inherits_parent_global_temp                     | r       | g              | {on_commit_delete_rows=true}
+ products                                        | r       | g              | {on_commit_delete_rows=false}
+ temp_orders                                     | r       | g              | {on_commit_delete_rows=true}
+ temp_orders_2                                   | r       | g              | {on_commit_delete_rows=false}
+ temp_products                                   | r       | g              | {on_commit_delete_rows=false}
+ temp_table_test_statistics                      | r       | g              | {on_commit_delete_rows=false}
+ temp_table_test_systemview                      | r       | g              | {on_commit_delete_rows=false}
+ temp_table_with_sequence_oncommit_delete        | r       | g              | {on_commit_delete_rows=true}
+ temp_table_with_sequence_oncommit_preserve      | r       | g              | {on_commit_delete_rows=false}
+ test_create_table_as                            | r       | g              | {on_commit_delete_rows=false}
+ foo                                             | r       | p              | 
+ inherits_parent                                 | r       | p              | 
+ regular_partition_01_2019                       | r       | p              | 
+(52 rows)
+
+reset search_path;
+drop schema global_temporary_table cascade;
+NOTICE:  drop cascades to 27 other objects
+DETAIL:  drop cascades to table global_temporary_table.gtt_on_commit_default
+drop cascades to table global_temporary_table.gtt_on_commit_delete
+drop cascades to table global_temporary_table.gtt_on_commit_delete2
+drop cascades to table global_temporary_table.gtt_on_commit_preserve
+drop cascades to table global_temporary_table.gtt_test_new
+drop cascades to table global_temporary_table.gtt_test_createindex
+drop cascades to table global_temporary_table.gtt_test_alter
+drop cascades to table global_temporary_table.regular_partition_01_2019
+drop cascades to table global_temporary_table.regular_partition01
+drop cascades to table global_temporary_table.global_temp_partition_01_2021
+drop cascades to table global_temporary_table.inherits_parent
+drop cascades to table global_temporary_table.inherits_parent_global_temp
+drop cascades to table global_temporary_table.gtt_test_alter1
+drop cascades to table global_temporary_table.foo
+drop cascades to table global_temporary_table.test_create_table_as
+drop cascades to table global_temporary_table.gtt_copytest
+drop cascades to table global_temporary_table.temp_products
+drop cascades to table global_temporary_table.products
+drop cascades to table global_temporary_table.temp_orders
+drop cascades to table global_temporary_table.temp_orders_2
+drop cascades to table global_temporary_table.global_temp_with_serial
+drop cascades to sequence global_temporary_table.seq_1
+drop cascades to table global_temporary_table.temp_table_with_sequence_oncommit_delete
+drop cascades to table global_temporary_table.temp_table_with_sequence_oncommit_preserve
+drop cascades to table global_temporary_table.temp_table_test_statistics
+drop cascades to table global_temporary_table.gtt_t_kenyon
+drop cascades to table global_temporary_table.temp_table_test_systemview
+-- should empty
+select * from pg_list_gtt_relfrozenxids();
+ pid | relfrozenxid 
+-----+--------------
+(0 rows)
+
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index b58b062b10d..8469c345bf4 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1359,6 +1359,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 5b0c73d7e37..40f4fc5ca10 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -134,3 +134,6 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: global_temporary_table
diff --git a/src/test/regress/sql/global_temporary_table.sql b/src/test/regress/sql/global_temporary_table.sql
new file mode 100644
index 00000000000..5185c3b15af
--- /dev/null
+++ b/src/test/regress/sql/global_temporary_table.sql
@@ -0,0 +1,313 @@
+--
+-- GLobal emparary table test case 
+--
+
+CREATE SCHEMA IF NOT EXISTS global_temporary_table;
+set search_path=global_temporary_table,sys;
+
+--
+--  test create global temp table basic syntax
+--
+create global temp table gtt_on_commit_default(a int primary key, b text);
+create global temp table gtt_on_commit_delete(a int primary key, b text) on commit delete rows;
+create global temp table gtt_on_commit_delete2(n int) with (on_commit_delete_rows='true');
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit PRESERVE rows;
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt_test_createindex(c0 tsvector,c1 varchar(100), c2 int);
+create global temp table gtt_test_alter(b text) with(on_commit_delete_rows=true);
+--
+-- test DML on global temp table
+--
+
+-- update empty temp table
+update gtt_on_commit_delete set b ='test';
+begin;
+insert into gtt_on_commit_delete values (1);
+update gtt_on_commit_delete set b ='test';
+-- should 1 row
+select * from gtt_on_commit_delete;
+commit;
+-- data delete after transaction commit
+-- should 0 row
+select * from gtt_on_commit_delete;
+
+-- update empty temp table
+update gtt_on_commit_preserve set b ='test';
+insert into gtt_on_commit_preserve values (2);
+begin;
+insert into gtt_on_commit_preserve values (3);
+update gtt_on_commit_preserve set b ='test';
+-- should 2 row
+select * from gtt_on_commit_preserve order by a;
+delete from gtt_on_commit_preserve where a=2;
+commit;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+
+begin;
+insert into gtt_on_commit_preserve values (4);
+-- temp table support truncate;
+truncate gtt_on_commit_preserve;
+select * from gtt_on_commit_preserve order by a;
+rollback;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+
+--
+-- test unsupported global temp partition table
+--
+
+-- should fail
+CREATE global temp TABLE global_temp_partition_01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+on commit delete rows;
+
+CREATE TABLE regular_partition_01_2019 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+);
+
+CREATE TABLE regular_partition01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time);
+
+-- should fail
+CREATE global temp TABLE temp_partition01_2018
+PARTITION OF regular_partition01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE global_temp_partition_01_2021 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+)on commit delete rows;
+
+-- should fail
+ALTER TABLE regular_partition01 ATTACH PARTITION global_temp_partition_01_2021 FOR VALUES FROM ('2021-01-01 00:00:00') TO ('2022-01-01 00:00:00');
+
+--
+-- test unsupported inherit table
+--
+create table 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 inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent);
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent_global_temp) on commit delete rows;
+
+--
+-- test DDL on global temp table
+--
+create index idx_gtt_test_alter_b on gtt_test_alter (b);
+insert into gtt_test_alter values('test');
+alter table gtt_test_alter alter b type varchar;
+create index gtt_idx_1 on gtt_test_createindex using gin (c0);
+create index gtt_idx_2 on gtt_test_createindex using gist (c0);
+create index gtt_idx_3 on gtt_test_createindex using hash (c2);
+alter table gtt_test_rename rename to gtt_test_new;
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+create index CONCURRENTLY idx_b on gtt_on_commit_default (b);
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+insert into gtt_on_commit_default values(1,'test');
+create global temp table gtt_test_alter1 (a int primary key,b text);
+alter table gtt_test_alter1 alter a type varchar;
+-- should fail
+alter table gtt_on_commit_default alter a type varchar;
+-- should fail
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) on commit delete rows;
+-- should fail
+alter table gtt_on_commit_default SET TABLESPACE pg_default;
+-- should fail
+alter table gtt_on_commit_default set ( on_commit_delete_rows='true');
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) with(on_commit_delete_rows=true);
+-- should fail
+create or replace global temp view gtt_v as select 5;
+create table foo();
+-- should fail
+alter table foo set (on_commit_delete_rows='true');
+-- should fail
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit drop;
+-- should fail
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+-- should fail
+CREATE MATERIALIZED VIEW mv_gtt_on_commit_default as select * from gtt_on_commit_default;
+-- test create table as select
+CREATE GLOBAL TEMPORARY TABLE test_create_table_as AS SELECT 1 AS a;
+-- test copy stmt
+create global temp table gtt_copytest (
+        c1 int,
+        "col with , comma" text,
+        "col with "" quote"  int);
+
+copy gtt_copytest from stdin csv header;
+this is just a line full of junk that would error out if parsed
+1,a,1
+2,b,2
+\.
+select count(*) from gtt_copytest;
+
+--
+-- test foreign key dependencies for global temp table
+--
+CREATE global temp TABLE temp_products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+CREATE global temp TABLE temp_orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+)on commit delete rows;
+
+-- should fail
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+);
+
+CREATE global temp TABLE temp_orders_2 (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products(product_no),
+    quantity integer
+);
+
+--should fail
+insert into temp_orders values(1,1,1);
+
+insert into temp_products values(1,'test',1.0);
+begin;
+insert into temp_orders values(1,1,1);
+commit;
+-- should 1 row
+select count(*) from temp_products;
+-- should 0 row
+select count(*) from temp_orders;
+
+--
+-- test sequence on global temp table
+--
+create global temp table global_temp_with_serial (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+-- support insert data to temp table in read only transaction
+insert into global_temp_with_serial (b) values(1);
+select * from global_temp_with_serial;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_delete(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_preserve(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table temp_table_with_sequence_oncommit_delete add c2 int default nextval('seq_1');
+alter table temp_table_with_sequence_oncommit_preserve add c2 int default nextval('seq_1');
+begin;
+insert into temp_table_with_sequence_oncommit_delete (c1)values(1);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(2);
+insert into temp_table_with_sequence_oncommit_delete (c1)values(3);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(4);
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+commit;
+-- should 0 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_preserve order by c1;
+
+--
+-- test statistics on temp table
+--
+create global temp table temp_table_test_statistics(a int);
+insert into temp_table_test_statistics values(generate_series(1,100000));
+create index idx_test_1 on temp_table_test_statistics (a);
+create index idx_test_2 on temp_table_test_statistics((a*10));
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+-- test statistic for whole row
+explain (costs off) select count(*) from temp_table_test_statistics group by temp_table_test_statistics;
+-- test statistic for system column
+explain (costs off) select count(*) from temp_table_test_statistics group by tableoid;
+analyze temp_table_test_statistics;
+-- indexscan by idx_test_1
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+-- indexscan by idx_test_2
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+explain (costs off) select count(*) from temp_table_test_statistics group by temp_table_test_statistics;
+explain (costs off) select count(*) from temp_table_test_statistics group by tableoid;
+
+--
+-- test temp table with toast table
+--
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,10),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',10);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+rollback;
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+
+-- test analyze/vacuum on global temp table
+ANALYZE gtt_t_kenyon;
+VACUUM gtt_t_kenyon;
+
+--
+-- test global temp table system view
+--
+create global temp table temp_table_test_systemview(a int primary key, b text) on commit PRESERVE rows;
+-- should empty, storage not initialized
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+-- should empty, storage not initialized
+select count(*) from pg_list_gtt_relfrozenxids();
+insert into temp_table_test_systemview values(generate_series(1,10000),'test');
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+select count(*) from pg_list_gtt_relfrozenxids();
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+-- should empty
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+analyze temp_table_test_systemview;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+-- get data after analyze;
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+
+-- get all object info in current schema
+select relname ,relkind, relpersistence, reloptions from pg_class c, pg_namespace n where c.relnamespace = n.oid and n.nspname = 'global_temporary_table' order by relkind,relpersistence,relname;
+
+reset search_path;
+drop schema global_temporary_table cascade;
+-- should empty
+select * from pg_list_gtt_relfrozenxids();
+
-- 
2.32.0 (Apple Git-132)

#345wenjing
wjzeng2012@gmail.com
In reply to: wenjing zeng (#344)
4 attachment(s)
Re: [Proposal] Global temporary tables

Send an email to trigger the regress test.

wenjing zeng <wjzeng2012@gmail.com> 于2021年12月20日周一 20:42写道:

Show quoted text

Post GTT v63 to fixed conflicts with the latest code.

Hi Andrew

Have you found any new bugs recently?

Wenjing

2021年11月20日 01:31,wenjing <wjzeng2012@gmail.com> 写道:

Andrew Bille <andrewbille@gmail.com> 于2021年11月15日周一 下午6:34写道:

Thanks for the patches. The feature has become much more stable.
However, there is another simple case that generates an error:
Master with v61 patches

CREATE GLOBAL TEMPORARY TABLE t AS SELECT 1 AS a;
ERROR: could not open file "base/13560/t3_16384": No such file or
directory

Thank you for pointing out that this part is not reasonable enough.
This issue has been fixed in v62.
Looking forward to your reply.

Wenjing

Andrew

On Thu, Nov 11, 2021 at 3:15 PM wenjing <wjzeng2012@gmail.com> wrote:

Fixed a bug in function pg_gtt_attached_pid.
Looking forward to your reply.

Wenjing

<0001-gtt-v62-reademe.patch><0004-gtt-v62-regress.patch>
<0002-gtt-v62-doc.patch><0003-gtt-v62-implementation.patch>

Attachments:

0002-gtt-v63-doc.patchapplication/octet-stream; name=0002-gtt-v63-doc.patchDownload
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 473a0a4aeb..e510bde8ac 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -169,32 +169,67 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     <listitem>
      <para>
       If specified, the table is created as a temporary table.
-      Temporary tables are automatically dropped at the end of a
-      session, or optionally at the end of the current transaction
-      (see <literal>ON COMMIT</literal> below).  The default
-      search_path includes the temporary schema first and so identically
-      named existing permanent tables are not chosen for new plans
+      Optionally, <literal>GLOBAL</literal> or <literal>LOCAL</literal>
+      can be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
+      They represent two types of temporary tables supported by <productname>PostgreSQL</productname>:
+      global temporary table and local temporary table. Without specified
+      GLOBAL or LOCAL, a local temporary table is created by default.
+     </para>
+
+    <para>
+     Both types of temporary tables’ data are truncated at the
+     end of a session or optionally at the end of the current transaction.
+     (see <literal>ON COMMIT</literal> below). For global temporary table,
+     its schema is reserved and reused by future sessions or transactions.
+     For local temporary table, both its data and its schema are dropped.
+    </para>
+
+    <variablelist>
+     <varlistentry>
+      <term><literal>Global Temporary Table</literal></term>
+      <listitem>
+       <para>
+        Global temporary table are defined just once and automatically exist
+        (starting with empty contents) in every session that needs them.
+        The schema definition of temporary tables is persistent and shared among sessions.
+        However, the data in temporary tables are kept private to sessions themselves,
+        even though they use same name and same schema.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>Local Temporary Table</literal></term>
+     <listitem>
+     <para>
+      Local temporary table are automatically dropped at the end of a
+      session (include schema and data). Future sessions need to create
+      their own temporary tables when they are used.
+     </para>
+     <para>
+      The default search_path includes the temporary schema first and so
+      identically named existing permanent tables are not chosen for new plans
       while the temporary table exists, unless they are referenced
       with schema-qualified names. Any indexes created on a temporary
       table are automatically temporary as well.
      </para>
+     </listitem>
+     </varlistentry>
+    </variablelist>
 
-     <para>
-      The <link linkend="autovacuum">autovacuum daemon</link> cannot
-      access and therefore cannot vacuum or analyze temporary tables.
-      For this reason, appropriate vacuum and analyze operations should be
-      performed via session SQL commands.  For example, if a temporary
-      table is going to be used in complex queries, it is wise to run
-      <command>ANALYZE</command> on the temporary table after it is populated.
-     </para>
+    <para>
+     The <link linkend="autovacuum">autovacuum daemon</link> cannot
+     access and therefore cannot vacuum or analyze temporary tables.
+     For this reason, appropriate vacuum and analyze operations should be
+     performed via session SQL commands.  For example, if a temporary
+     table is going to be used in complex queries, it is wise to run
+     <command>ANALYZE</command> on the temporary table after it is populated.
+    </para>
+    <para>
+     The Temporary table resembles the SQL standard, but has some differences.
+     see <xref linkend="sql-createtable-compatibility"/> below.
+    </para>
 
-     <para>
-      Optionally, <literal>GLOBAL</literal> or <literal>LOCAL</literal>
-      can be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
-      This presently makes no difference in <productname>PostgreSQL</productname>
-      and is deprecated; see
-      <xref linkend="sql-createtable-compatibility"/> below.
-     </para>
     </listitem>
    </varlistentry>
 
@@ -2133,13 +2168,17 @@ CREATE TABLE cities_partdef
    <title>Temporary Tables</title>
 
    <para>
-    Although the syntax of <literal>CREATE TEMPORARY TABLE</literal>
-    resembles that of the SQL standard, the effect is not the same.  In the
-    standard,
-    temporary tables are defined just once and automatically exist (starting
-    with empty contents) in every session that needs them.
-    <productname>PostgreSQL</productname> instead
-    requires each session to issue its own <literal>CREATE TEMPORARY
+    Although the syntax of <literal>CREATE GLOBAL/LOCAL TEMPORARY TABLE</literal>
+    resembles that of the SQL standard, the effect is not the same.
+    The global temporary table follows the SQL standards while local temporary
+    table does not.
+   </para>
+
+   <para>
+    First, in the standard, both global and local temporary tables are defined just
+    once and automatically exist (starting with empty contents) in every session
+    that needs them. For local temporary tables, <productname>PostgreSQL</productname>
+    instead requires each session to issue its own <literal>CREATE LOCAL TEMPORARY
     TABLE</literal> command for each temporary table to be used.  This allows
     different sessions to use the same temporary table name for different
     purposes, whereas the standard's approach constrains all instances of a
@@ -2147,29 +2186,14 @@ CREATE TABLE cities_partdef
    </para>
 
    <para>
-    The standard's definition of the behavior of temporary tables is
-    widely ignored.  <productname>PostgreSQL</productname>'s behavior
-    on this point is similar to that of several other SQL databases.
-   </para>
-
-   <para>
-    The SQL standard also distinguishes between global and local temporary
+    Second, the SQL standard distinguishes between global and local temporary
     tables, where a local temporary table has a separate set of contents for
     each SQL module within each session, though its definition is still shared
-    across sessions.  Since <productname>PostgreSQL</productname> does not
+    across sessions. Since <productname>PostgreSQL</productname> does not
     support SQL modules, this distinction is not relevant in
     <productname>PostgreSQL</productname>.
    </para>
 
-   <para>
-    For compatibility's sake, <productname>PostgreSQL</productname> will
-    accept the <literal>GLOBAL</literal> and <literal>LOCAL</literal> keywords
-    in a temporary table declaration, but they currently have no effect.
-    Use of these keywords is discouraged, since future versions of
-    <productname>PostgreSQL</productname> might adopt a more
-    standard-compliant interpretation of their meaning.
-   </para>
-
    <para>
     The <literal>ON COMMIT</literal> clause for temporary tables
     also resembles the SQL standard, but has some differences.
@@ -2177,7 +2201,8 @@ CREATE TABLE cities_partdef
     default behavior is <literal>ON COMMIT DELETE ROWS</literal>.  However, the
     default behavior in <productname>PostgreSQL</productname> is
     <literal>ON COMMIT PRESERVE ROWS</literal>.  The <literal>ON COMMIT
-    DROP</literal> option does not exist in SQL.
+    DROP</literal> option does not exist in SQL and is not supported by
+    global temporary table.
    </para>
   </refsect2>
 
-- 
2.30.1 (Apple Git-130)

0001-gtt-v63-reademe.patchapplication/octet-stream; name=0001-gtt-v63-reademe.patchDownload
diff --git a/README.gtt.txt b/README.gtt.txt
new file mode 100644
index 00000000000..d181df9acd7
--- /dev/null
+++ b/README.gtt.txt
@@ -0,0 +1,172 @@
+Global Temporary Table(GTT)
+=========================================
+
+Feature description
+-----------------------------------------
+
+Previously, temporary tables are defined once and automatically
+exist (starting with empty contents) in every session before using them.
+
+The temporary table implementation in PostgreSQL, known as Local temp tables(LTT),
+did not fully comply with the SQL standard. This version added the support of
+Global Temporary Table .
+
+The metadata of Global Temporary Table is persistent and shared among sessions.
+The data stored in the Global temporary table is independent of sessions. This
+means, when a session creates a Global Temporary Table and writes some data.
+Other sessions cannot see those data, but they have an empty Global Temporary
+Table with same schema.
+
+Like local temporary table, Global Temporary Table supports ON COMMIT PRESERVE ROWS
+or ON COMMIT DELETE ROWS clause, so that data in the temporary table can be
+cleaned up or preserved automatically when a session exits or a transaction COMMITs.
+
+Unlike Local Temporary Table, Global Temporary Table does not support
+ON COMMIT DROP clauses.
+
+In following paragraphs, we use GTT for Global Temporary Table and LTT for
+local temporary table.
+
+Main design ideas
+-----------------------------------------
+In general, GTT and LTT use the same storage and buffer design and
+implementation. The storage files for both types of temporary tables are named
+as t_backendid_relfilenode, and the local buffer is used to cache the data.
+
+The schema of GTTs is shared among sessions while their data are not. We build
+a new mechanisms to manage those non-shared data and their statistics.
+Here is the summary of changes:
+
+1) CATALOG
+GTTs store session-specific data. The storage information of GTTs'data, their
+transaction information, and their statistics are not stored in the catalog.
+
+2) STORAGE INFO & STATISTICS INFO & TRANSACTION INFO
+In order to maintain durability and availability of GTTs'session-specific data,
+their storage information, statistics, and transaction information is managed
+in a local hash table tt_storage_local_hash.
+
+3) DDL
+Currently, GTT supports almost all table'DDL except CLUSTER/VACUUM FULL.
+Part of the DDL behavior is limited by shared definitions and multiple copies of
+local data, and we added some structures to handle this.
+
+A shared hash table active_gtt_shared_hash is added to track the state of the
+GTT in a different session. This information is recorded in the hash table
+during the DDL execution of the GTT.
+
+The data stored in a GTT can only be modified or accessed by owning session.
+The statements that only modify data in a GTT do not need a high level of
+table locking. The operations making those changes include truncate GTT,
+reindex GTT, and lock GTT.
+
+4) MVCC commit log(clog) cleanup
+Each GTT in a session has its own piece of data, and they have their own
+transaction information. We set up data structures to track and maintain
+this information. The cleaning of CLOGs also needs to consider the transaction
+information of GTT.
+
+Detailed design
+-----------------------------------------
+
+1. CATALOG
+1.1 relpersistence
+define RELPERSISTENCEGLOBALTEMP 'g'
+Mark Global Temporary Table in pg_class relpersistence to 'g'. The relpersistence
+of indexes created on the GTT, sequences on GTT and toast tables on GTT are
+also set to 'g'
+
+1.2 on commit clause
+LTT's status associated with on commit DELETE ROWS and on commit PRESERVE ROWS
+is not stored in catalog. Instead, GTTs need a bool value on_commit_delete_rows
+in reloptions which is shared among sessions.
+
+1.3 gram.y
+GTT is already supported in syntax tree. We remove the warning message
+"GLOBAL is deprecated in temporary table creation" and mark
+relpersistence = RELPERSISTENCEGLOBALTEMP.
+
+2. STORAGE INFO & STATISTICS INFO & TRANSACTION INFO
+2.1. gtt_storage_local_hash
+Each backend creates a local hashtable gtt_storage_local_hash to track a GTT's
+storage file information, statistics, and transaction information.
+
+2.2 GTT storage file info track
+1) When one session inserts data into a GTT for the first time, record the
+storage info to gtt_storage_local_hash.
+2) Use beforeshmemexit to ensure that all files of session GTT are deleted when
+the session exits.
+
+2.3 statistics info
+1) relpages reltuples relallvisible relfilenode
+2) The statistics of each column from pg_statistic
+All the above information is stored in gtt_storage_local_hash.
+When doing vacuum or analyze, GTT's statistic is updated, which is used by
+the SQL planner.
+The statistics summarizes only data in the current session.
+
+2.3 transaction info track
+frozenxid minmulti from pg_class is stored to gtt_storage_local_hash.
+
+3 DDL
+3.1. active_gtt_shared_hash
+This is the hash table created in shared memory to trace the GTT files initialized
+in each session. Each hash entry contains a bitmap that records the backendid of
+the initialized GTT file. With this hash table, we know which backend/session
+is using this GTT. Such information is used during GTT's DDL operations.
+
+3.2 DROP GTT
+One GTT is allowed to be deleted when there is only one session using the table
+and the session is the current session. After holding the lock on GTT,
+active_gtt_shared_hash is checked to ensure that.
+
+3.3 ALTER GTT/DROP INDEX ON GTT
+Same as drop GTT.
+
+3.4 CREATE INDEX ON GTT
+1) create index on GTT statements build index based on local data in a session.
+2) After the index is created, record the index metadata to the catalog.
+3) Other sessions can enable or disable the local GTT index.
+
+3.5 TRUNCATE/REINDEX GTT
+The SQL truncate/reindex command open the GTT using AccessShareLock lock,
+not AccessExclusiveLock, because this command only cleans up local data and
+local buffers in current session. This allows these operations to be executed
+concurrently between sessions, unlike normal tables.
+
+3.6 LOCK GTT
+A lock GTT statement does not hold any relation lock.
+
+3.7 CLUSTER GTT/VACUUM FULL GTT
+The current version does not support.
+
+4 MVCC commit log(clog) cleanup
+
+The GTT storage file contains transaction information. Queries for GTT data rely
+on transaction information such as clog. The transaction information required by
+each session may be completely different. We need to ensure that the transaction
+information of the GTT data is not cleaned up during its lifetime and that
+transaction resources are recycled at the instance level.
+
+4.1 The session level GTT oldest frozenxid
+1) To manage all GTT transaction information, add session level oldest frozenxid
+in each session. When one GTT is created or removed, record the session level
+oldest frozenxid and store it in MyProc.
+2) When vacuum advances the database's frozenxid, session level oldest frozenxid
+should be considered. This is acquired by searching all of MyProc. This way,
+we can avoid the clog required by GTTs to be cleaned.
+
+4.2 vacuum GTT
+Users can perform vacuum over a GTT to clean up local data in the GTT.
+
+4.3 autovacuum GTT
+Autovacuum skips all GTTs, because the data in GTTs is only visible in current session.
+
+5 OTHERS
+5.1 Parallel query
+Planner does not produce parallel query plans for SQL related to GTT. Because
+GTT private data cannot be accessed across processes.
+
+5.2 WAL and Logical replication
+Like LTT, the DML on GTT does not record WAL and is not parsed or replay by
+the logical replication.
\ No newline at end of file
-- 
2.30.1 (Apple Git-130)

0004-gtt-v63-regress.patchapplication/octet-stream; name=0004-gtt-v63-regress.patchDownload
diff --git a/src/test/isolation/expected/gtt-sequence.out b/src/test/isolation/expected/gtt-sequence.out
new file mode 100644
index 00000000000..31db2ebd423
--- /dev/null
+++ b/src/test/isolation/expected/gtt-sequence.out
@@ -0,0 +1,48 @@
+unused step name: s1_seq_restart
+Parsed test spec with 2 sessions
+
+starting permutation: s1_seq_next s2_seq_next s1_seq_next
+step s1_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      1
+(1 row)
+
+step s2_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      1
+(1 row)
+
+step s1_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      2
+(1 row)
+
+
+starting permutation: s1_select s2_select s1_insert s2_insert s1_select s2_select
+step s1_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+(0 rows)
+
+step s2_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+(0 rows)
+
+step s1_insert: insert into gtt_with_seq values(1);
+step s2_insert: insert into gtt_with_seq values(10);
+step s1_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+ 1| 3
+(1 row)
+
+step s2_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+10| 1
+(1 row)
+
diff --git a/src/test/isolation/expected/gtt-table.out b/src/test/isolation/expected/gtt-table.out
new file mode 100644
index 00000000000..5825773aa12
--- /dev/null
+++ b/src/test/isolation/expected/gtt-table.out
@@ -0,0 +1,675 @@
+Parsed test spec with 3 sessions
+
+starting permutation: s1_update_d
+step s1_update_d: update gtt_on_commit_delete_row set b = 'update'
+
+starting permutation: s1_select_d s1_insert_d s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_begin s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+1|test1
+(1 row)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_begin s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+1|test1
+(1 row)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_select_d s1_truncate_d s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_begin s1_insert_d s1_select_d s1_truncate_d s1_select_d s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_begin s1_insert_d s1_select_d s1_truncate_d s1_select_d s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_insert_d s1_select_d s1_begin s1_insert_d s1_select_d s1_save_1 s1_truncate_d s1_insert_d s1_select_d s1_save_2 s1_truncate_d s1_insert_d s1_select_d s1_save_3 s1_rollback_to_save_2 s1_select_d s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_save_1: SAVEPOINT save1
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_save_2: SAVEPOINT save2
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+4|test1
+(1 row)
+
+step s1_save_3: SAVEPOINT save3
+step s1_rollback_to_save_2: rollback to savepoint save2
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+5|test1
+(2 rows)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_insert_d s1_select_d s1_begin s1_insert_d s1_select_d s1_save_1 s1_truncate_d s1_insert_d s1_select_d s1_save_2 s1_truncate_d s1_insert_d s1_select_d s1_save_3 s1_rollback_to_save_2 s1_select_d s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_save_1: SAVEPOINT save1
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_save_2: SAVEPOINT save2
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+4|test1
+(1 row)
+
+step s1_save_3: SAVEPOINT save3
+step s1_rollback_to_save_2: rollback to savepoint save2
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+5|test1
+(2 rows)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_update_p
+step s2_update_p: update gtt_on_commit_preserve_row set b = 'update'
+
+starting permutation: s2_select_p s2_insert_p s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_begin s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_begin s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_select_p s2_insert_p s2_select_p s2_truncate_p s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_select_p s2_insert_p s2_begin s2_insert_p s2_select_p s2_truncate_p s2_select_p s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_insert_p s2_begin s2_insert_p s2_select_p s2_truncate_p s2_select_p s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_insert_p s2_select_p s2_begin s2_insert_p s2_select_p s2_save_1 s2_truncate_p s2_insert_p s2_select_p s2_save_2 s2_truncate_p s2_insert_p s2_select_p s2_save_3 s2_rollback_to_save_2 s2_select_p s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_save_1: SAVEPOINT save1
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_save_2: SAVEPOINT save2
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+4|test10
+(1 row)
+
+step s2_save_3: SAVEPOINT save3
+step s2_rollback_to_save_2: rollback to savepoint save2
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+
+starting permutation: s2_insert_p s2_select_p s2_begin s2_insert_p s2_select_p s2_save_1 s2_truncate_p s2_insert_p s2_select_p s2_save_2 s2_truncate_p s2_insert_p s2_select_p s2_save_3 s2_rollback_to_save_2 s2_select_p s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_save_1: SAVEPOINT save1
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_save_2: SAVEPOINT save2
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+4|test10
+(1 row)
+
+step s2_save_3: SAVEPOINT save3
+step s2_rollback_to_save_2: rollback to savepoint save2
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_insert_p s2_insert_p s1_select_p s2_select_p
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_truncate_p s2_truncate_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_begin s1_insert_d s2_insert_d s1_truncate_d s2_insert_d s1_commit
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s2_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s2_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_commit: commit
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_reindex_p s2_reindex_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_reindex_p: reindex table gtt_on_commit_preserve_row
+step s2_reindex_p: reindex table gtt_on_commit_preserve_row
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_reindex_i_p s2_reindex_i_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_reindex_i_p: reindex index gtt_on_commit_preserve_row_pkey
+step s2_reindex_i_p: reindex index gtt_on_commit_preserve_row_pkey
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_insert_c s3_create_c s3_insert_c s1_insert_c s1_analyze_c s2_analyze_c s3_analyze_c s1_select_c s2_select_c s3_select_c
+step s2_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s3_create_c: create unique index idx_temp_table_a on gtt_test_createindex(a)
+step s3_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s1_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s1_analyze_c: analyze gtt_test_createindex
+step s2_analyze_c: analyze gtt_test_createindex
+step s3_analyze_c: analyze gtt_test_createindex
+step s1_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                                               
+---------------------------------------------------------
+Index Scan using idx_temp_table_a on gtt_test_createindex
+  Index Cond: (a = 1)                                    
+(2 rows)
+
+step s2_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                      
+--------------------------------
+Seq Scan on gtt_test_createindex
+  Filter: (a = 1)               
+(2 rows)
+
+step s3_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                                               
+---------------------------------------------------------
+Index Scan using idx_temp_table_a on gtt_test_createindex
+  Index Cond: (a = 1)                                    
+(2 rows)
+
+
+starting permutation: s1_begin s2_begin s1_lock_p s2_lock_p s1_truncate_p s2_truncate_p s1_insert_p s2_insert_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_lock_p: LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE
+step s2_lock_p: LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE
+step s1_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index 99c23b16ffe..f5c8eec533d 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -97,3 +97,5 @@ test: plpgsql-toast
 test: truncate-conflict
 test: serializable-parallel
 test: serializable-parallel-2
+test: gtt-sequence
+test: gtt-table
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 12179f25146..7cf0b20e636 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -80,9 +80,30 @@ disconnect_atexit(void)
 {
 	int			i;
 
-	for (i = 0; i < nconns; i++)
+	for (i = 1; i < nconns; i++)
 		if (conns[i].conn)
 			PQfinish(conns[i].conn);
+
+	if (parseresult.destroy)
+	{
+		PGresult   *res;
+
+		res = PQexec(conns[0].conn, parseresult.destroy);
+		if (PQresultStatus(res) == PGRES_TUPLES_OK)
+		{
+			printResultSet(res);
+		}
+		else if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		{
+			fprintf(stderr, "destroy failed: %s",
+					PQerrorMessage(conns[0].conn));
+			/* don't exit on teardown failure */
+		}
+		PQclear(res);
+	}
+
+	if (conns[0].conn)
+		PQfinish(conns[0].conn);
 }
 
 int
@@ -238,6 +259,24 @@ main(int argc, char **argv)
 	PQclear(res);
 	termPQExpBuffer(&wait_query);
 
+	if (parseresult.initialize)
+	{
+		PGresult   *res;
+
+		res = PQexec(conns[0].conn, parseresult.initialize);
+		if (PQresultStatus(res) == PGRES_TUPLES_OK)
+		{
+			printResultSet(res);
+		}
+		else if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		{
+			fprintf(stderr, "initialize failed: %s",
+					PQerrorMessage(conns[0].conn));
+			/* don't exit on teardown failure */
+		}
+		PQclear(res);
+	}
+
 	/*
 	 * Run the permutations specified in the spec, or all if none were
 	 * explicitly specified.
diff --git a/src/test/isolation/isolationtester.h b/src/test/isolation/isolationtester.h
index 5f300219c20..b5a29893da9 100644
--- a/src/test/isolation/isolationtester.h
+++ b/src/test/isolation/isolationtester.h
@@ -81,6 +81,8 @@ typedef struct
 	int			nsessions;
 	Permutation **permutations;
 	int			npermutations;
+	char	   *initialize;
+	char	   *destroy;
 } TestSpec;
 
 extern TestSpec parseresult;
diff --git a/src/test/isolation/specparse.y b/src/test/isolation/specparse.y
index c25aa1a73fa..2784f758ed9 100644
--- a/src/test/isolation/specparse.y
+++ b/src/test/isolation/specparse.y
@@ -39,7 +39,7 @@ TestSpec		parseresult;			/* result of parsing is left here */
 }
 
 %type <ptr_list> setup_list
-%type <str>  opt_setup opt_teardown
+%type <str>  opt_setup opt_teardown opt_initialize opt_destroy
 %type <str> setup
 %type <ptr_list> step_list session_list permutation_list opt_permutation_list
 %type <ptr_list> permutation_step_list blocker_list
@@ -51,23 +51,27 @@ TestSpec		parseresult;			/* result of parsing is left here */
 
 %token <str> sqlblock identifier
 %token <integer> INTEGER
-%token NOTICES PERMUTATION SESSION SETUP STEP TEARDOWN TEST
+%token NOTICES PERMUTATION SESSION SETUP STEP TEARDOWN TEST INITIALIZE DESTROY
 
 %%
 
 TestSpec:
+			opt_initialize
 			setup_list
 			opt_teardown
+			opt_destroy
 			session_list
 			opt_permutation_list
 			{
-				parseresult.setupsqls = (char **) $1.elements;
-				parseresult.nsetupsqls = $1.nelements;
-				parseresult.teardownsql = $2;
-				parseresult.sessions = (Session **) $3.elements;
-				parseresult.nsessions = $3.nelements;
-				parseresult.permutations = (Permutation **) $4.elements;
-				parseresult.npermutations = $4.nelements;
+				parseresult.setupsqls = (char **) $2.elements;
+				parseresult.nsetupsqls = $2.nelements;
+				parseresult.teardownsql = $3;
+				parseresult.sessions = (Session **) $5.elements;
+				parseresult.nsessions = $5.nelements;
+				parseresult.permutations = (Permutation **) $6.elements;
+				parseresult.npermutations = $6.nelements;
+				parseresult.initialize = $1;
+				parseresult.destroy = $4;
 			}
 		;
 
@@ -100,6 +104,16 @@ opt_teardown:
 			| TEARDOWN sqlblock	{ $$ = $2; }
 		;
 
+opt_initialize:
+			/* EMPTY */			{ $$ = NULL; }
+			| INITIALIZE sqlblock	{ $$ = $2; }
+		;
+
+opt_destroy:
+			/* EMPTY */			{ $$ = NULL; }
+			| DESTROY sqlblock	{ $$ = $2; }
+		;
+
 session_list:
 			session_list session
 			{
diff --git a/src/test/isolation/specs/gtt-sequence.spec b/src/test/isolation/specs/gtt-sequence.spec
new file mode 100644
index 00000000000..88eece45e29
--- /dev/null
+++ b/src/test/isolation/specs/gtt-sequence.spec
@@ -0,0 +1,39 @@
+# Tests for global temporary relations
+
+initialize
+{
+  CREATE GLOBAL TEMPORARY TABLE if not exists gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+}
+
+destroy
+{
+  /* wait other backend exit */
+  select pg_sleep(1);
+
+  DROP TABLE gtt_with_seq;
+}
+
+# Session 1
+session "s1"
+step "s1_seq_next" { select nextval('gtt_with_seq_c2_seq'); }
+step "s1_seq_restart" { alter sequence gtt_with_seq_c2_seq RESTART; }
+step "s1_insert" { insert into gtt_with_seq values(1); }
+step "s1_select" { select * from gtt_with_seq order by c1,c2; }
+teardown
+{
+  TRUNCATE gtt_with_seq;
+}
+
+# Session 2
+session "s2"
+step "s2_seq_next" { select nextval('gtt_with_seq_c2_seq'); }
+step "s2_insert" { insert into gtt_with_seq values(10); }
+step "s2_select" { select * from gtt_with_seq order by c1,c2; }
+teardown
+{
+  TRUNCATE gtt_with_seq RESTART IDENTITY;
+}
+
+permutation "s1_seq_next" "s2_seq_next" "s1_seq_next"
+permutation "s1_select" "s2_select" "s1_insert" "s2_insert" "s1_select" "s2_select"
+
diff --git a/src/test/isolation/specs/gtt-table.spec b/src/test/isolation/specs/gtt-table.spec
new file mode 100644
index 00000000000..e0396b21ef0
--- /dev/null
+++ b/src/test/isolation/specs/gtt-table.spec
@@ -0,0 +1,135 @@
+# Tests for global temporary relations
+
+initialize
+{
+  create global temp table gtt_on_commit_delete_row(a bigserial primary key, b text) on commit delete rows;
+  create global temp table gtt_on_commit_preserve_row(a bigserial primary key, b text) on commit preserve rows;
+  create global temp table gtt_test_createindex(a int, b char(1000)) on commit preserve rows;
+}
+
+destroy
+{
+  /* wait other backend exit */
+  select pg_sleep(1);
+
+  DROP TABLE gtt_on_commit_delete_row;
+  DROP TABLE gtt_on_commit_preserve_row;
+  DROP TABLE gtt_test_createindex;
+}
+
+# Session 1
+session "s1"
+step "s1_begin" {begin}
+step "s1_commit" {commit}
+step "s1_rollback" {rollback}
+step "s1_insert_d" {insert into gtt_on_commit_delete_row (b) values('test1')}
+step "s1_select_d" {select a,b from gtt_on_commit_delete_row order by a,b}
+step "s1_insert_p" {insert into gtt_on_commit_preserve_row (b) values('test20')}
+step "s1_select_p" {select a,b from gtt_on_commit_preserve_row order by a,b}
+step "s1_truncate_d" {truncate gtt_on_commit_delete_row}
+step "s1_truncate_p" {truncate gtt_on_commit_preserve_row}
+step "s1_lock_p" {LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE}
+step "s1_update_d" {update gtt_on_commit_delete_row set b = 'update'}
+step "s1_save_1" {SAVEPOINT save1}
+step "s1_save_2" {SAVEPOINT save2}
+step "s1_save_3" {SAVEPOINT save3}
+step "s1_rollback_to_save_2" {rollback to savepoint save2}
+step "s1_reindex_p" {reindex table gtt_on_commit_preserve_row}
+step "s1_reindex_i_p" {reindex index gtt_on_commit_preserve_row_pkey}
+step "s1_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s1_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s1_analyze_c" {analyze gtt_test_createindex}
+teardown
+{
+  TRUNCATE gtt_on_commit_delete_row RESTART IDENTITY;
+  TRUNCATE gtt_on_commit_preserve_row RESTART IDENTITY;
+}
+
+# Session 2
+session "s2"
+step "s2_begin" {begin}
+step "s2_commit" {commit}
+step "s2_rollback" {rollback}
+step "s2_insert_p" {insert into gtt_on_commit_preserve_row (b) values('test10')}
+step "s2_select_p" {select a,b from gtt_on_commit_preserve_row order by a,b}
+step "s2_insert_d" {insert into gtt_on_commit_delete_row (b) values('test1')}
+step "s2_truncate_p" {truncate gtt_on_commit_preserve_row}
+step "s2_update_p" {update gtt_on_commit_preserve_row set b = 'update'}
+step "s2_lock_p" {LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE}
+step "s2_save_1" {SAVEPOINT save1}
+step "s2_save_2" {SAVEPOINT save2}
+step "s2_save_3" {SAVEPOINT save3}
+step "s2_rollback_to_save_2" {rollback to savepoint save2}
+step "s2_reindex_p" {reindex table gtt_on_commit_preserve_row}
+step "s2_reindex_i_p" {reindex index gtt_on_commit_preserve_row_pkey}
+step "s2_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s2_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s2_analyze_c" {analyze gtt_test_createindex}
+teardown
+{
+  TRUNCATE gtt_on_commit_delete_row RESTART IDENTITY;
+  TRUNCATE gtt_on_commit_preserve_row RESTART IDENTITY;
+}
+
+session "s3"
+step "s3_create_c" {create unique index idx_temp_table_a on gtt_test_createindex(a)}
+step "s3_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s3_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s3_analyze_c" {analyze gtt_test_createindex}
+
+
+#
+# test on commit delete temp table
+#
+
+# test update empty temp table
+permutation "s1_update_d"
+# test insert into temp table
+permutation "s1_select_d" "s1_insert_d" "s1_select_d"
+# test temp table in transaction(commit)
+permutation "s1_select_d" "s1_begin"    "s1_insert_d" "s1_select_d"   "s1_commit"   "s1_select_d" 
+# test temp table in transaction(rollback)
+permutation "s1_select_d" "s1_begin"    "s1_insert_d" "s1_select_d"   "s1_rollback" "s1_select_d" 
+# test truncate
+permutation "s1_select_d" "s1_insert_d" "s1_select_d" "s1_truncate_d" "s1_select_d"
+# test truncate in transaction block
+permutation "s1_select_d" "s1_insert_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_truncate_d" "s1_select_d"   "s1_insert_d" "s1_select_d" "s1_commit"   "s1_select_d" 
+permutation "s1_select_d" "s1_insert_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_truncate_d" "s1_select_d"   "s1_insert_d" "s1_select_d" "s1_rollback" "s1_select_d"
+# test temp table with subtransaction or savepoint
+permutation "s1_insert_d" "s1_select_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_save_1"     "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_2"   "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_3" "s1_rollback_to_save_2" "s1_select_d" "s1_insert_d" "s1_select_d" "s1_commit" "s1_select_d"
+permutation "s1_insert_d" "s1_select_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_save_1"     "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_2"   "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_3" "s1_rollback_to_save_2" "s1_select_d" "s1_insert_d" "s1_select_d" "s1_rollback" "s1_select_d"
+
+#
+# test on commit preserve table
+#
+
+# same as test on commit delete temp table
+permutation "s2_update_p"
+permutation "s2_select_p" "s2_insert_p" "s2_select_p"
+permutation "s2_select_p" "s2_begin"    "s2_insert_p" "s2_select_p"   "s2_commit"   "s2_select_p"
+permutation "s2_select_p" "s2_begin"    "s2_insert_p" "s2_select_p"   "s2_rollback" "s2_select_p"
+permutation "s2_select_p" "s2_insert_p" "s2_select_p" "s2_truncate_p" "s2_select_p"
+permutation "s2_select_p" "s2_insert_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_truncate_p" "s2_select_p"   "s2_insert_p" "s2_select_p" "s2_commit"   "s2_select_p" 
+permutation "s2_select_p" "s2_insert_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_truncate_p" "s2_select_p"   "s2_insert_p" "s2_select_p" "s2_rollback" "s2_select_p" 
+permutation "s2_insert_p" "s2_select_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_save_1"     "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_2"   "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_3" "s2_rollback_to_save_2" "s2_select_p" "s2_insert_p" "s2_select_p" "s2_commit" "s2_select_p"
+permutation "s2_insert_p" "s2_select_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_save_1"     "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_2"   "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_3" "s2_rollback_to_save_2" "s2_select_p" "s2_insert_p" "s2_select_p" "s2_rollback" "s2_select_p"
+
+#
+# test concurrent operation on temp table
+#
+
+#  test concurrent read
+permutation "s1_insert_p" "s2_insert_p" "s1_select_p" "s2_select_p" 
+#  test concurrent truncate
+permutation "s1_begin" "s2_begin"    "s1_insert_p" "s2_insert_p"   "s1_truncate_p" "s2_truncate_p"  "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+permutation "s1_begin" "s1_insert_d" "s2_insert_d" "s1_truncate_d" "s2_insert_d"   "s1_commit" 
+#  test concurrent reindex table
+permutation "s1_begin" "s2_begin" "s1_insert_p" "s2_insert_p" "s1_reindex_p"   "s2_reindex_p"   "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+#  test concurrent reindex index
+permutation "s1_begin" "s2_begin" "s1_insert_p" "s2_insert_p" "s1_reindex_i_p" "s2_reindex_i_p" "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+
+# test create index
+permutation "s2_insert_c" "s3_create_c" "s3_insert_c" "s1_insert_c" "s1_analyze_c" "s2_analyze_c" "s3_analyze_c" "s1_select_c" "s2_select_c" "s3_select_c"
+
+# test lock gtt
+permutation "s1_begin" "s2_begin" "s1_lock_p" "s2_lock_p" "s1_truncate_p" "s2_truncate_p" "s1_insert_p" "s2_insert_p" "s1_commit" "s2_commit" "s1_select_p" "s2_select_p"
diff --git a/src/test/isolation/specscanner.l b/src/test/isolation/specscanner.l
index d9fa6a5b54a..697db975479 100644
--- a/src/test/isolation/specscanner.l
+++ b/src/test/isolation/specscanner.l
@@ -67,6 +67,8 @@ session			{ return SESSION; }
 setup			{ return SETUP; }
 step			{ return STEP; }
 teardown		{ return TEARDOWN; }
+initialize		 { return INITIALIZE; }
+destroy			 { return DESTROY; }
 
  /* Whitespace and comments */
 [\n]			{ yyline++; }
diff --git a/src/test/regress/expected/global_temporary_table.out b/src/test/regress/expected/global_temporary_table.out
new file mode 100644
index 00000000000..5adc58d77e1
--- /dev/null
+++ b/src/test/regress/expected/global_temporary_table.out
@@ -0,0 +1,574 @@
+--
+-- GLobal emparary table test case 
+--
+CREATE SCHEMA IF NOT EXISTS global_temporary_table;
+set search_path=global_temporary_table,sys;
+--
+--  test create global temp table basic syntax
+--
+create global temp table gtt_on_commit_default(a int primary key, b text);
+create global temp table gtt_on_commit_delete(a int primary key, b text) on commit delete rows;
+create global temp table gtt_on_commit_delete2(n int) with (on_commit_delete_rows='true');
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit PRESERVE rows;
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt_test_createindex(c0 tsvector,c1 varchar(100), c2 int);
+create global temp table gtt_test_alter(b text) with(on_commit_delete_rows=true);
+--
+-- test DML on global temp table
+--
+-- update empty temp table
+update gtt_on_commit_delete set b ='test';
+begin;
+insert into gtt_on_commit_delete values (1);
+update gtt_on_commit_delete set b ='test';
+-- should 1 row
+select * from gtt_on_commit_delete;
+ a |  b   
+---+------
+ 1 | test
+(1 row)
+
+commit;
+-- data delete after transaction commit
+-- should 0 row
+select * from gtt_on_commit_delete;
+ a | b 
+---+---
+(0 rows)
+
+-- update empty temp table
+update gtt_on_commit_preserve set b ='test';
+insert into gtt_on_commit_preserve values (2);
+begin;
+insert into gtt_on_commit_preserve values (3);
+update gtt_on_commit_preserve set b ='test';
+-- should 2 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 2 | test
+ 3 | test
+(2 rows)
+
+delete from gtt_on_commit_preserve where a=2;
+commit;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 3 | test
+(1 row)
+
+begin;
+insert into gtt_on_commit_preserve values (4);
+-- temp table support truncate;
+truncate gtt_on_commit_preserve;
+select * from gtt_on_commit_preserve order by a;
+ a | b 
+---+---
+(0 rows)
+
+rollback;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 3 | test
+(1 row)
+
+--
+-- test unsupported global temp partition table
+--
+-- should fail
+CREATE global temp TABLE global_temp_partition_01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+on commit delete rows;
+ERROR:  Only support global temporary regular table.
+CREATE TABLE regular_partition_01_2019 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+);
+CREATE TABLE regular_partition01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time);
+-- should fail
+CREATE global temp TABLE temp_partition01_2018
+PARTITION OF regular_partition01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+ERROR:  Not support global temporary partition table or inherit table.
+CREATE global temp TABLE global_temp_partition_01_2021 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+)on commit delete rows;
+-- should fail
+ALTER TABLE regular_partition01 ATTACH PARTITION global_temp_partition_01_2021 FOR VALUES FROM ('2021-01-01 00:00:00') TO ('2022-01-01 00:00:00');
+ERROR:  cannot attach a global temporary relation as partition of permanent relation "regular_partition01"
+--
+-- test unsupported inherit table
+--
+create table 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 inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent);
+ERROR:  Not support global temporary partition table or inherit table.
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent_global_temp) on commit delete rows;
+ERROR:  Not support global temporary partition table or inherit table.
+--
+-- test DDL on global temp table
+--
+create index idx_gtt_test_alter_b on gtt_test_alter (b);
+insert into gtt_test_alter values('test');
+alter table gtt_test_alter alter b type varchar;
+create index gtt_idx_1 on gtt_test_createindex using gin (c0);
+create index gtt_idx_2 on gtt_test_createindex using gist (c0);
+create index gtt_idx_3 on gtt_test_createindex using hash (c2);
+alter table gtt_test_rename rename to gtt_test_new;
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+create index CONCURRENTLY idx_b on gtt_on_commit_default (b);
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+insert into gtt_on_commit_default values(1,'test');
+create global temp table gtt_test_alter1 (a int primary key,b text);
+alter table gtt_test_alter1 alter a type varchar;
+-- should fail
+alter table gtt_on_commit_default alter a type varchar;
+ERROR:  Only support alter global temporary table in an empty context.
+HINT:  Please create a new connection and execute ALTER TABLE on the new connection.
+-- should fail
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+ERROR:  not support cluster global temporary table yet
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- should fail
+alter table gtt_on_commit_default SET TABLESPACE pg_default;
+ERROR:  not support alter table set tablespace on global temporary table
+-- should fail
+alter table gtt_on_commit_default set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table
+-- should fail
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- should fail
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- should fail
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit drop;
+ERROR:  global temporary table not support on commit drop clause
+-- should fail
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  could not create global temporary table with on commit and with clause at same time
+-- should fail
+CREATE MATERIALIZED VIEW mv_gtt_on_commit_default as select * from gtt_on_commit_default;
+ERROR:  materialized views must not use global temporary tables or views
+-- test create table as select
+CREATE GLOBAL TEMPORARY TABLE test_create_table_as AS SELECT 1 AS a;
+-- test copy stmt
+create global temp table gtt_copytest (
+        c1 int,
+        "col with , comma" text,
+        "col with "" quote"  int);
+copy gtt_copytest from stdin csv header;
+select count(*) from gtt_copytest;
+ count 
+-------
+     2
+(1 row)
+
+--
+-- test foreign key dependencies for global temp table
+--
+CREATE global temp TABLE temp_products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+CREATE global temp TABLE temp_orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+)on commit delete rows;
+-- should fail
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+CREATE global temp TABLE temp_orders_2 (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products(product_no),
+    quantity integer
+);
+--should fail
+insert into temp_orders values(1,1,1);
+ERROR:  insert or update on table "temp_orders" violates foreign key constraint "temp_orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "temp_products".
+insert into temp_products values(1,'test',1.0);
+begin;
+insert into temp_orders values(1,1,1);
+commit;
+-- should 1 row
+select count(*) from temp_products;
+ count 
+-------
+     1
+(1 row)
+
+-- should 0 row
+select count(*) from temp_orders;
+ count 
+-------
+     0
+(1 row)
+
+--
+-- test sequence on global temp table
+--
+create global temp table global_temp_with_serial (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+-- support insert data to temp table in read only transaction
+insert into global_temp_with_serial (b) values(1);
+select * from global_temp_with_serial;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_delete(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_preserve(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table temp_table_with_sequence_oncommit_delete add c2 int default nextval('seq_1');
+alter table temp_table_with_sequence_oncommit_preserve add c2 int default nextval('seq_1');
+begin;
+insert into temp_table_with_sequence_oncommit_delete (c1)values(1);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(2);
+insert into temp_table_with_sequence_oncommit_delete (c1)values(3);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(4);
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  3 |  3
+(2 rows)
+
+commit;
+-- should 0 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_preserve order by c1;
+ c1 | c2 
+----+----
+  2 |  2
+  4 |  4
+(2 rows)
+
+--
+-- test statistics on temp table
+--
+create global temp table temp_table_test_statistics(a int);
+insert into temp_table_test_statistics values(generate_series(1,100000));
+create index idx_test_1 on temp_table_test_statistics (a);
+create index idx_test_2 on temp_table_test_statistics((a*10));
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+                   QUERY PLAN                   
+------------------------------------------------
+ Bitmap Heap Scan on temp_table_test_statistics
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_test_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+                   QUERY PLAN                   
+------------------------------------------------
+ Bitmap Heap Scan on temp_table_test_statistics
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_test_2
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+-- test statistic for whole row
+explain (costs off) select count(*) from temp_table_test_statistics group by temp_table_test_statistics;
+                  QUERY PLAN                  
+----------------------------------------------
+ HashAggregate
+   Group Key: temp_table_test_statistics.*
+   ->  Seq Scan on temp_table_test_statistics
+(3 rows)
+
+-- test statistic for system column
+explain (costs off) select count(*) from temp_table_test_statistics group by tableoid;
+                  QUERY PLAN                  
+----------------------------------------------
+ HashAggregate
+   Group Key: tableoid
+   ->  Seq Scan on temp_table_test_statistics
+(3 rows)
+
+analyze temp_table_test_statistics;
+-- indexscan by idx_test_1
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Index Only Scan using idx_test_1 on temp_table_test_statistics
+   Index Cond: (a = 200000)
+(2 rows)
+
+-- indexscan by idx_test_2
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+                        QUERY PLAN                         
+-----------------------------------------------------------
+ Index Scan using idx_test_2 on temp_table_test_statistics
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+explain (costs off) select count(*) from temp_table_test_statistics group by temp_table_test_statistics;
+                  QUERY PLAN                  
+----------------------------------------------
+ HashAggregate
+   Group Key: temp_table_test_statistics.*
+   ->  Seq Scan on temp_table_test_statistics
+(3 rows)
+
+explain (costs off) select count(*) from temp_table_test_statistics group by tableoid;
+                  QUERY PLAN                  
+----------------------------------------------
+ HashAggregate
+   Group Key: tableoid
+   ->  Seq Scan on temp_table_test_statistics
+(3 rows)
+
+--
+-- test temp table with toast table
+--
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |                0 |        483328 |           98304 |                 581632
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            32768 |                  |         32768 |               0 |                  32768
+(3 rows)
+
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |             8192 |        499712 |          106496 |                 606208
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            40960 |                  |         40960 |               0 |                  40960
+(3 rows)
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,10),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',10);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |             8192 |                0 |         16384 |           32768 |                  49152
+ idx_gtt_t_kenyon_1 |            16384 |                  |         16384 |               0 |                  16384
+ idx_gtt_t_kenyon_2 |            16384 |                  |         16384 |               0 |                  16384
+(3 rows)
+
+rollback;
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |             8192 |        499712 |          106496 |                 606208
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            40960 |                  |         40960 |               0 |                  40960
+(3 rows)
+
+-- test analyze/vacuum on global temp table
+ANALYZE gtt_t_kenyon;
+VACUUM gtt_t_kenyon;
+--
+-- test global temp table system view
+--
+create global temp table temp_table_test_systemview(a int primary key, b text) on commit PRESERVE rows;
+-- should empty, storage not initialized
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+ tablename 
+-----------
+(0 rows)
+
+-- should empty, storage not initialized
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into temp_table_test_systemview values(generate_series(1,10000),'test');
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+         tablename          
+----------------------------
+ temp_table_test_systemview
+(1 row)
+
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+       schemaname       |            tablename            | relpages | reltuples | relallvisible 
+------------------------+---------------------------------+----------+-----------+---------------
+ global_temporary_table | temp_table_test_systemview      |        0 |         0 |             0
+ global_temporary_table | temp_table_test_systemview_pkey |        1 |         0 |             0
+(2 rows)
+
+-- should empty
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+analyze temp_table_test_systemview;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+       schemaname       |            tablename            | relpages | reltuples | relallvisible 
+------------------------+---------------------------------+----------+-----------+---------------
+ global_temporary_table | temp_table_test_systemview      |       55 |     10000 |             0
+ global_temporary_table | temp_table_test_systemview_pkey |       30 |     10000 |             0
+(2 rows)
+
+-- get data after analyze;
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+       schemaname       |         tablename          | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------------------+----------------------------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ global_temporary_table | temp_table_test_systemview | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ global_temporary_table | temp_table_test_systemview | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+-- get all object info in current schema
+select relname ,relkind, relpersistence, reloptions from pg_class c, pg_namespace n where c.relnamespace = n.oid and n.nspname = 'global_temporary_table' order by relkind,relpersistence,relname;
+                     relname                     | relkind | relpersistence |          reloptions           
+-------------------------------------------------+---------+----------------+-------------------------------
+ global_temp_partition_01_2021_id_seq            | S       | g              | {on_commit_delete_rows=false}
+ global_temp_with_serial_a_seq                   | S       | g              | {on_commit_delete_rows=false}
+ regular_partition01_id_seq                      | S       | p              | 
+ regular_partition_01_2019_id_seq                | S       | p              | 
+ seq_1                                           | S       | p              | 
+ gtt_idx_1                                       | i       | g              | 
+ gtt_idx_2                                       | i       | g              | 
+ gtt_idx_3                                       | i       | g              | 
+ gtt_on_commit_default_pkey                      | i       | g              | 
+ gtt_on_commit_delete_pkey                       | i       | g              | 
+ gtt_on_commit_preserve_pkey                     | i       | g              | 
+ gtt_test_alter1_pkey                            | i       | g              | 
+ gtt_test_rename_pkey                            | i       | g              | 
+ idx_b                                           | i       | g              | 
+ idx_gtt_t_kenyon_1                              | i       | g              | 
+ idx_gtt_t_kenyon_2                              | i       | g              | 
+ idx_gtt_test_alter_b                            | i       | g              | 
+ idx_test_1                                      | i       | g              | 
+ idx_test_2                                      | i       | g              | 
+ products_pkey                                   | i       | g              | 
+ temp_orders_2_pkey                              | i       | g              | 
+ temp_orders_pkey                                | i       | g              | 
+ temp_products_pkey                              | i       | g              | 
+ temp_table_test_systemview_pkey                 | i       | g              | 
+ temp_table_with_sequence_oncommit_delete_pkey   | i       | g              | 
+ temp_table_with_sequence_oncommit_preserve_pkey | i       | g              | 
+ regular_partition01                             | p       | p              | 
+ global_temp_partition_01_2021                   | r       | g              | {on_commit_delete_rows=true}
+ global_temp_with_serial                         | r       | g              | {on_commit_delete_rows=false}
+ gtt_copytest                                    | r       | g              | {on_commit_delete_rows=false}
+ gtt_on_commit_default                           | r       | g              | {on_commit_delete_rows=false}
+ gtt_on_commit_delete                            | r       | g              | {on_commit_delete_rows=true}
+ gtt_on_commit_delete2                           | r       | g              | {on_commit_delete_rows=true}
+ gtt_on_commit_preserve                          | r       | g              | {on_commit_delete_rows=false}
+ gtt_t_kenyon                                    | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_alter                                  | r       | g              | {on_commit_delete_rows=true}
+ gtt_test_alter1                                 | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_createindex                            | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_new                                    | r       | g              | {on_commit_delete_rows=false}
+ inherits_parent_global_temp                     | r       | g              | {on_commit_delete_rows=true}
+ products                                        | r       | g              | {on_commit_delete_rows=false}
+ temp_orders                                     | r       | g              | {on_commit_delete_rows=true}
+ temp_orders_2                                   | r       | g              | {on_commit_delete_rows=false}
+ temp_products                                   | r       | g              | {on_commit_delete_rows=false}
+ temp_table_test_statistics                      | r       | g              | {on_commit_delete_rows=false}
+ temp_table_test_systemview                      | r       | g              | {on_commit_delete_rows=false}
+ temp_table_with_sequence_oncommit_delete        | r       | g              | {on_commit_delete_rows=true}
+ temp_table_with_sequence_oncommit_preserve      | r       | g              | {on_commit_delete_rows=false}
+ test_create_table_as                            | r       | g              | {on_commit_delete_rows=false}
+ foo                                             | r       | p              | 
+ inherits_parent                                 | r       | p              | 
+ regular_partition_01_2019                       | r       | p              | 
+(52 rows)
+
+reset search_path;
+drop schema global_temporary_table cascade;
+NOTICE:  drop cascades to 27 other objects
+DETAIL:  drop cascades to table global_temporary_table.gtt_on_commit_default
+drop cascades to table global_temporary_table.gtt_on_commit_delete
+drop cascades to table global_temporary_table.gtt_on_commit_delete2
+drop cascades to table global_temporary_table.gtt_on_commit_preserve
+drop cascades to table global_temporary_table.gtt_test_new
+drop cascades to table global_temporary_table.gtt_test_createindex
+drop cascades to table global_temporary_table.gtt_test_alter
+drop cascades to table global_temporary_table.regular_partition_01_2019
+drop cascades to table global_temporary_table.regular_partition01
+drop cascades to table global_temporary_table.global_temp_partition_01_2021
+drop cascades to table global_temporary_table.inherits_parent
+drop cascades to table global_temporary_table.inherits_parent_global_temp
+drop cascades to table global_temporary_table.gtt_test_alter1
+drop cascades to table global_temporary_table.foo
+drop cascades to table global_temporary_table.test_create_table_as
+drop cascades to table global_temporary_table.gtt_copytest
+drop cascades to table global_temporary_table.temp_products
+drop cascades to table global_temporary_table.products
+drop cascades to table global_temporary_table.temp_orders
+drop cascades to table global_temporary_table.temp_orders_2
+drop cascades to table global_temporary_table.global_temp_with_serial
+drop cascades to sequence global_temporary_table.seq_1
+drop cascades to table global_temporary_table.temp_table_with_sequence_oncommit_delete
+drop cascades to table global_temporary_table.temp_table_with_sequence_oncommit_preserve
+drop cascades to table global_temporary_table.temp_table_test_statistics
+drop cascades to table global_temporary_table.gtt_t_kenyon
+drop cascades to table global_temporary_table.temp_table_test_systemview
+-- should empty
+select * from pg_list_gtt_relfrozenxids();
+ pid | relfrozenxid 
+-----+--------------
+(0 rows)
+
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index b58b062b10d..8469c345bf4 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1359,6 +1359,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 5b0c73d7e37..40f4fc5ca10 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -134,3 +134,6 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: global_temporary_table
diff --git a/src/test/regress/sql/global_temporary_table.sql b/src/test/regress/sql/global_temporary_table.sql
new file mode 100644
index 00000000000..5185c3b15af
--- /dev/null
+++ b/src/test/regress/sql/global_temporary_table.sql
@@ -0,0 +1,313 @@
+--
+-- GLobal emparary table test case 
+--
+
+CREATE SCHEMA IF NOT EXISTS global_temporary_table;
+set search_path=global_temporary_table,sys;
+
+--
+--  test create global temp table basic syntax
+--
+create global temp table gtt_on_commit_default(a int primary key, b text);
+create global temp table gtt_on_commit_delete(a int primary key, b text) on commit delete rows;
+create global temp table gtt_on_commit_delete2(n int) with (on_commit_delete_rows='true');
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit PRESERVE rows;
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt_test_createindex(c0 tsvector,c1 varchar(100), c2 int);
+create global temp table gtt_test_alter(b text) with(on_commit_delete_rows=true);
+--
+-- test DML on global temp table
+--
+
+-- update empty temp table
+update gtt_on_commit_delete set b ='test';
+begin;
+insert into gtt_on_commit_delete values (1);
+update gtt_on_commit_delete set b ='test';
+-- should 1 row
+select * from gtt_on_commit_delete;
+commit;
+-- data delete after transaction commit
+-- should 0 row
+select * from gtt_on_commit_delete;
+
+-- update empty temp table
+update gtt_on_commit_preserve set b ='test';
+insert into gtt_on_commit_preserve values (2);
+begin;
+insert into gtt_on_commit_preserve values (3);
+update gtt_on_commit_preserve set b ='test';
+-- should 2 row
+select * from gtt_on_commit_preserve order by a;
+delete from gtt_on_commit_preserve where a=2;
+commit;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+
+begin;
+insert into gtt_on_commit_preserve values (4);
+-- temp table support truncate;
+truncate gtt_on_commit_preserve;
+select * from gtt_on_commit_preserve order by a;
+rollback;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+
+--
+-- test unsupported global temp partition table
+--
+
+-- should fail
+CREATE global temp TABLE global_temp_partition_01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+on commit delete rows;
+
+CREATE TABLE regular_partition_01_2019 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+);
+
+CREATE TABLE regular_partition01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time);
+
+-- should fail
+CREATE global temp TABLE temp_partition01_2018
+PARTITION OF regular_partition01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE global_temp_partition_01_2021 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+)on commit delete rows;
+
+-- should fail
+ALTER TABLE regular_partition01 ATTACH PARTITION global_temp_partition_01_2021 FOR VALUES FROM ('2021-01-01 00:00:00') TO ('2022-01-01 00:00:00');
+
+--
+-- test unsupported inherit table
+--
+create table 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 inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent);
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent_global_temp) on commit delete rows;
+
+--
+-- test DDL on global temp table
+--
+create index idx_gtt_test_alter_b on gtt_test_alter (b);
+insert into gtt_test_alter values('test');
+alter table gtt_test_alter alter b type varchar;
+create index gtt_idx_1 on gtt_test_createindex using gin (c0);
+create index gtt_idx_2 on gtt_test_createindex using gist (c0);
+create index gtt_idx_3 on gtt_test_createindex using hash (c2);
+alter table gtt_test_rename rename to gtt_test_new;
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+create index CONCURRENTLY idx_b on gtt_on_commit_default (b);
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+insert into gtt_on_commit_default values(1,'test');
+create global temp table gtt_test_alter1 (a int primary key,b text);
+alter table gtt_test_alter1 alter a type varchar;
+-- should fail
+alter table gtt_on_commit_default alter a type varchar;
+-- should fail
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) on commit delete rows;
+-- should fail
+alter table gtt_on_commit_default SET TABLESPACE pg_default;
+-- should fail
+alter table gtt_on_commit_default set ( on_commit_delete_rows='true');
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) with(on_commit_delete_rows=true);
+-- should fail
+create or replace global temp view gtt_v as select 5;
+create table foo();
+-- should fail
+alter table foo set (on_commit_delete_rows='true');
+-- should fail
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit drop;
+-- should fail
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+-- should fail
+CREATE MATERIALIZED VIEW mv_gtt_on_commit_default as select * from gtt_on_commit_default;
+-- test create table as select
+CREATE GLOBAL TEMPORARY TABLE test_create_table_as AS SELECT 1 AS a;
+-- test copy stmt
+create global temp table gtt_copytest (
+        c1 int,
+        "col with , comma" text,
+        "col with "" quote"  int);
+
+copy gtt_copytest from stdin csv header;
+this is just a line full of junk that would error out if parsed
+1,a,1
+2,b,2
+\.
+select count(*) from gtt_copytest;
+
+--
+-- test foreign key dependencies for global temp table
+--
+CREATE global temp TABLE temp_products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+CREATE global temp TABLE temp_orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+)on commit delete rows;
+
+-- should fail
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+);
+
+CREATE global temp TABLE temp_orders_2 (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products(product_no),
+    quantity integer
+);
+
+--should fail
+insert into temp_orders values(1,1,1);
+
+insert into temp_products values(1,'test',1.0);
+begin;
+insert into temp_orders values(1,1,1);
+commit;
+-- should 1 row
+select count(*) from temp_products;
+-- should 0 row
+select count(*) from temp_orders;
+
+--
+-- test sequence on global temp table
+--
+create global temp table global_temp_with_serial (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+-- support insert data to temp table in read only transaction
+insert into global_temp_with_serial (b) values(1);
+select * from global_temp_with_serial;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_delete(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_preserve(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table temp_table_with_sequence_oncommit_delete add c2 int default nextval('seq_1');
+alter table temp_table_with_sequence_oncommit_preserve add c2 int default nextval('seq_1');
+begin;
+insert into temp_table_with_sequence_oncommit_delete (c1)values(1);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(2);
+insert into temp_table_with_sequence_oncommit_delete (c1)values(3);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(4);
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+commit;
+-- should 0 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_preserve order by c1;
+
+--
+-- test statistics on temp table
+--
+create global temp table temp_table_test_statistics(a int);
+insert into temp_table_test_statistics values(generate_series(1,100000));
+create index idx_test_1 on temp_table_test_statistics (a);
+create index idx_test_2 on temp_table_test_statistics((a*10));
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+-- test statistic for whole row
+explain (costs off) select count(*) from temp_table_test_statistics group by temp_table_test_statistics;
+-- test statistic for system column
+explain (costs off) select count(*) from temp_table_test_statistics group by tableoid;
+analyze temp_table_test_statistics;
+-- indexscan by idx_test_1
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+-- indexscan by idx_test_2
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+explain (costs off) select count(*) from temp_table_test_statistics group by temp_table_test_statistics;
+explain (costs off) select count(*) from temp_table_test_statistics group by tableoid;
+
+--
+-- test temp table with toast table
+--
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,10),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',10);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+rollback;
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+
+-- test analyze/vacuum on global temp table
+ANALYZE gtt_t_kenyon;
+VACUUM gtt_t_kenyon;
+
+--
+-- test global temp table system view
+--
+create global temp table temp_table_test_systemview(a int primary key, b text) on commit PRESERVE rows;
+-- should empty, storage not initialized
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+-- should empty, storage not initialized
+select count(*) from pg_list_gtt_relfrozenxids();
+insert into temp_table_test_systemview values(generate_series(1,10000),'test');
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+select count(*) from pg_list_gtt_relfrozenxids();
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+-- should empty
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+analyze temp_table_test_systemview;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+-- get data after analyze;
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+
+-- get all object info in current schema
+select relname ,relkind, relpersistence, reloptions from pg_class c, pg_namespace n where c.relnamespace = n.oid and n.nspname = 'global_temporary_table' order by relkind,relpersistence,relname;
+
+reset search_path;
+drop schema global_temporary_table cascade;
+-- should empty
+select * from pg_list_gtt_relfrozenxids();
+
-- 
2.32.0 (Apple Git-132)

0003-gtt-v63-implementation.patchapplication/octet-stream; name=0003-gtt-v63-implementation.patchDownload
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index a23d0182fc0..74a28d7de8d 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -18,6 +18,7 @@
 #include "access/toast_internals.h"
 #include "access/visibilitymap.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "funcapi.h"
 #include "miscadmin.h"
 #include "storage/bufmgr.h"
@@ -226,6 +227,8 @@ verify_heapam(PG_FUNCTION_ARGS)
 	BlockNumber last_block;
 	BlockNumber nblocks;
 	const char *skip;
+	TransactionId	relfrozenxid = InvalidTransactionId;
+	MultiXactId		relminmxid = InvalidMultiXactId;
 
 	/* Check to see if caller supports us returning a tuplestore */
 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -340,6 +343,13 @@ verify_heapam(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(ctx.rel) &&
+		!gtt_storage_attached(RelationGetRelid(ctx.rel)))
+	{
+		relation_close(ctx.rel, AccessShareLock);
+		PG_RETURN_NULL();
+	}
+
 	/* Early exit if the relation is empty */
 	nblocks = RelationGetNumberOfBlocks(ctx.rel);
 	if (!nblocks)
@@ -407,9 +417,25 @@ verify_heapam(PG_FUNCTION_ARGS)
 
 	update_cached_xid_range(&ctx);
 	update_cached_mxid_range(&ctx);
-	ctx.relfrozenxid = ctx.rel->rd_rel->relfrozenxid;
+
+	if (RELATION_IS_GLOBAL_TEMP(ctx.rel))
+	{
+		get_gtt_relstats(RelationGetRelid(ctx.rel),
+						NULL,
+						NULL,
+						NULL,
+						&relfrozenxid,
+						&relminmxid);
+	}
+	else
+	{
+		relfrozenxid = ctx.rel->rd_rel->relfrozenxid;
+		relminmxid = ctx.rel->rd_rel->relminmxid;
+	}
+
+	ctx.relfrozenxid = relfrozenxid;
 	ctx.relfrozenfxid = FullTransactionIdFromXidAndCtx(ctx.relfrozenxid, &ctx);
-	ctx.relminmxid = ctx.rel->rd_rel->relminmxid;
+	ctx.relminmxid = relminmxid;
 
 	if (TransactionIdIsNormal(ctx.relfrozenxid))
 		ctx.oldest_xid = ctx.relfrozenxid;
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index b5602f53233..21b2d2a9527 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -159,6 +159,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * In order to avoid consistency problems, the global temporary table
+	 * uses ShareUpdateExclusiveLock.
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temporary table on commit options",
+			RELOPT_KIND_HEAP,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},
 	/* list terminator */
 	{{NULL}}
 };
@@ -1834,6 +1847,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 94dbabc1988..f31bb2f318c 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1024,7 +1024,7 @@ gistproperty(Oid index_oid, int attno,
 XLogRecPtr
 gistGetFakeLSN(Relation rel)
 {
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	if (RELATION_IS_TEMP(rel))
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 81c7da7ec69..febc98e1b9c 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -152,7 +152,7 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	if (!RELATION_IS_TEMP(index))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 0b4a46b31ba..0d32b5d2d93 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -52,6 +52,7 @@
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/atomics.h"
@@ -5825,6 +5826,19 @@ heap_abort_speculative(Relation relation, ItemPointer tid)
 	BlockNumber block;
 	Buffer		buffer;
 	TransactionId prune_xid;
+	TransactionId relfrozenxid = InvalidTransactionId;
+
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		get_gtt_relstats(RelationGetRelid(relation),
+						NULL,
+						NULL,
+						NULL,
+						&relfrozenxid,
+						NULL);
+	}
+	else
+		relfrozenxid = relation->rd_rel->relfrozenxid;
 
 	Assert(ItemPointerIsValid(tid));
 
@@ -5877,8 +5891,8 @@ heap_abort_speculative(Relation relation, ItemPointer tid)
 	 * TransactionXmin, so there's no race here).
 	 */
 	Assert(TransactionIdIsValid(TransactionXmin));
-	if (TransactionIdPrecedes(TransactionXmin, relation->rd_rel->relfrozenxid))
-		prune_xid = relation->rd_rel->relfrozenxid;
+	if (TransactionIdPrecedes(TransactionXmin, relfrozenxid))
+		prune_xid = relfrozenxid;
 	else
 		prune_xid = TransactionXmin;
 	PageSetPrunable(page, prune_xid);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 9befe012a9e..26fce0c4b83 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -593,7 +593,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
@@ -645,7 +645,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(RelationGetSmgr(rel), dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index db6becfed54..f43ebc13e6e 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -47,6 +47,7 @@
 #include "access/xlog.h"
 #include "catalog/index.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -496,6 +497,25 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 	TransactionId OldestXmin;
 	TransactionId FreezeLimit;
 	MultiXactId MultiXactCutoff;
+	TransactionId	relfrozenxid = InvalidTransactionId;
+	MultiXactId		relminmxid = InvalidMultiXactId;
+	double			reltuples = 0;
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		get_gtt_relstats(RelationGetRelid(rel),
+						NULL,
+						&reltuples,
+						NULL,
+						&relfrozenxid,
+						&relminmxid);
+	}
+	else
+	{
+		relfrozenxid = rel->rd_rel->relfrozenxid;
+		relminmxid = rel->rd_rel->relminmxid;
+		reltuples = rel->rd_rel->reltuples;
+	}
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
@@ -531,9 +551,9 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 	 * table's minimum MultiXactId is older than or equal to the requested
 	 * mxid full-table scan limit; or if DISABLE_PAGE_SKIPPING was specified.
 	 */
-	aggressive = TransactionIdPrecedesOrEquals(rel->rd_rel->relfrozenxid,
+	aggressive = TransactionIdPrecedesOrEquals(relfrozenxid,
 											   xidFullScanLimit);
-	aggressive |= MultiXactIdPrecedesOrEquals(rel->rd_rel->relminmxid,
+	aggressive |= MultiXactIdPrecedesOrEquals(relminmxid,
 											  mxactFullScanLimit);
 	if (params->options & VACOPT_DISABLE_PAGE_SKIPPING)
 		aggressive = true;
@@ -580,9 +600,9 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 	}
 
 	vacrel->bstrategy = bstrategy;
-	vacrel->relfrozenxid = rel->rd_rel->relfrozenxid;
-	vacrel->relminmxid = rel->rd_rel->relminmxid;
-	vacrel->old_live_tuples = rel->rd_rel->reltuples;
+	vacrel->relfrozenxid = relfrozenxid;
+	vacrel->relminmxid = relminmxid;
+	vacrel->old_live_tuples = reltuples;
 
 	/* Set cutoffs for entire VACUUM */
 	vacrel->OldestXmin = OldestXmin;
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 0aea476b8ce..1e8a5d6882c 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -677,6 +678,14 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		/*
+		 * If a global temporary table storage file is not initialized in the
+		 * current backend, its index does not have a root page, just returns 0.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 4e6efda97f3..ae12fd91a1b 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -44,6 +44,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index aa7d4d5456b..595cb03eb4a 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -504,6 +504,7 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 6780ec53b7c..8aaa54e9a34 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -62,6 +62,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,
@@ -343,13 +345,26 @@ heap_create(const char *relname,
 	if (!RELKIND_HAS_TABLESPACE(relkind))
 		reltablespace = InvalidOid;
 
+	/* For global temporary table, even if the storage is not initialized,
+	 * the relfilenode needs to be generated and put into the catalog.
+	 */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		create_storage = false;
+		if (OidIsValid(relfilenode))
+			elog(ERROR, "global temporary table can not reuse an existing relfilenode");
+
+		relfilenode = relid;
+	}
 	/*
 	 * Decide whether to create storage. If caller passed a valid relfilenode,
 	 * storage is already created, so don't do it here.  Also don't create it
 	 * for relkinds without physical storage.
 	 */
-	if (!RELKIND_HAS_STORAGE(relkind) || OidIsValid(relfilenode))
+	else if (!RELKIND_HAS_STORAGE(relkind) || OidIsValid(relfilenode))
+	{
 		create_storage = false;
+	}
 	else
 	{
 		create_storage = true;
@@ -397,7 +412,7 @@ heap_create(const char *relname,
 											relpersistence,
 											relfrozenxid, relminmxid);
 		else if (RELKIND_HAS_STORAGE(rel->rd_rel->relkind))
-			RelationCreateStorage(rel->rd_node, relpersistence);
+			RelationCreateStorage(rel->rd_node, relpersistence, rel);
 		else
 			Assert(false);
 	}
@@ -407,7 +422,8 @@ heap_create(const char *relname,
 	 * protected by the existence of a physical file; but for relations with
 	 * no files, add a pg_shdepend entry to account for that.
 	 */
-	if (!create_storage && reltablespace != InvalidOid)
+	if (!create_storage && reltablespace != InvalidOid &&
+		relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
 		recordDependencyOnTablespace(RelationRelationId, relid,
 									 reltablespace);
 
@@ -964,6 +980,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -989,8 +1006,21 @@ AddNewRelationTuple(Relation pg_class_desc,
 		new_rel_reltup->reltuples = 1;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/*
+	 * The transaction information of the global temporary table is stored
+	 * in the local hash table, not in catalog.
+	 */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1371,6 +1401,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1455,8 +1486,9 @@ heap_create_with_catalog(const char *relname,
 	/*
 	 * If there's a special on-commit action, remember it
 	 */
-	if (oncommit != ONCOMMIT_NOOP)
-		register_on_commit_action(relid, oncommit);
+	if (oncommit != ONCOMMIT_NOOP &&
+		relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+		register_on_commit_action(relid, oncommit, false);
 
 	/*
 	 * ok, the relation has been cataloged, so close our relations and return
@@ -1953,6 +1985,19 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/*
+	 * Only when other sessions are not using this Global temporary table,
+	 * is it allowed to DROP it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot drop global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3239,7 +3284,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3251,7 +3296,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3287,7 +3332,7 @@ RelationTruncateIndexes(Relation heapRelation)
  * ON COMMIT truncation of temporary tables, where it doesn't matter.
  */
 void
-heap_truncate(List *relids)
+heap_truncate(List *relids, bool is_global_temp)
 {
 	List	   *relations = NIL;
 	ListCell   *cell;
@@ -3297,8 +3342,23 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode;
+
+		/*
+		 * Truncate global temporary table only clears local data,
+		 * so only low-level locks need to be held.
+		 */
+		if (is_global_temp)
+		{
+			if (!gtt_storage_attached(rid))
+				continue;
 
-		rel = table_open(rid, AccessExclusiveLock);
+			lockmode = RowExclusiveLock;
+		}
+		else
+			lockmode = AccessExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3331,6 +3391,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3339,23 +3400,39 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	/*
+	 * Truncate GTT only clears local data, so only low-level locks
+	 * need to be held.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		lockmode = AccessShareLock;
+	else
+		lockmode = AccessExclusiveLock;
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	/*
+	 * After the data is cleaned up on the GTT, the transaction information
+	 * for the data(stored in local hash table) is also need reset.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(RelationGetRelid(rel), 0, 0, 0, RecentXmin, GetOldestMultiXactId());
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 1757cd3446f..50119909f79 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -54,6 +54,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"
@@ -734,6 +735,25 @@ index_create(Relation heapRelation,
 	TransactionId relfrozenxid;
 	MultiXactId relminmxid;
 
+	/* For global temporary table only */
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temporary table with concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot reindex global temporary tables concurrently")));
+
+		/*
+		 * For the case that some backend is applied relcache message to create
+		 * an index on a global temporary table, if this table in the current
+		 * backend are not initialized, the creation of index storage on the
+		 * table are also skipped.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/* constraint flags can only be set when a constraint is requested */
 	Assert((constr_flags == 0) ||
 		   ((flags & INDEX_CREATE_ADD_CONSTRAINT) != 0));
@@ -2119,7 +2139,7 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 * lock (see comments in RemoveRelations), and a non-concurrent DROP is
 	 * more efficient.
 	 */
-	Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+	Assert(!RelpersistenceTsTemp(get_rel_persistence(indexId)) ||
 		   (!concurrent && !concurrent_lock_mode));
 
 	/*
@@ -2151,6 +2171,21 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/*
+	 * Allow to drop index on global temporary table when only current
+	 * backend use it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation) &&
+		is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+	{
+		ereport(ERROR,
+			(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+			 errmsg("cannot drop index %s on global temporary table %s",
+					RelationGetRelationName(userIndexRelation), RelationGetRelationName(userHeapRelation)),
+					errdetail("Because the index is created on the global temporary table and other backend attached it."),
+					errhint("Please try detach all sessions using this temporary table and try again.")));
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2759,6 +2794,7 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = RELATION_IS_GLOBAL_TEMP(rel);
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2853,20 +2889,37 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
-		{
-			rd_rel->relpages = (int32) relpages;
-			dirty = true;
-		}
-		if (rd_rel->reltuples != (float4) reltuples)
+		/* For global temporary table */
+		if (is_gtt)
 		{
-			rd_rel->reltuples = (float4) reltuples;
-			dirty = true;
+			/* Update GTT'statistics into local relcache */
+			rel->rd_rel->relpages = (int32) relpages;
+			rel->rd_rel->reltuples = (float4) reltuples;
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+
+			/* Update GTT'statistics into local hashtable */
+			up_gtt_relstats(RelationGetRelid(rel), relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
 		}
-		if (rd_rel->relallvisible != (int32) relallvisible)
+		else
 		{
-			rd_rel->relallvisible = (int32) relallvisible;
-			dirty = true;
+			if (rd_rel->relpages != (int32) relpages)
+			{
+				rd_rel->relpages = (int32) relpages;
+				dirty = true;
+			}
+
+			if (rd_rel->reltuples != (float4) reltuples)
+			{
+				rd_rel->reltuples = (float4) reltuples;
+				dirty = true;
+			}
+
+			if (rd_rel->relallvisible != (int32) relallvisible)
+			{
+				rd_rel->relallvisible = (int32) relallvisible;
+				dirty = true;
+			}
 		}
 	}
 
@@ -2979,6 +3032,26 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, progress_index, progress_vals);
 	}
 
+	/* For build index on global temporary table */
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		/*
+		 * If the storage for the index in this session is not initialized,
+		 * it needs to be created.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			/* Before create init storage, fix the local Relcache first */
+			force_enable_gtt_index(indexRelation);
+
+			Assert(gtt_storage_attached(RelationGetRelid(heapRelation)));
+
+			/* Init storage for index */
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3520,6 +3593,8 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	PGRUsage	ru0;
 	bool		progress = ((params->options & REINDEXOPT_REPORT_PROGRESS) != 0);
 	bool		set_tablespace = false;
+	LOCKMODE	lockmode_on_heap = ShareLock;
+	LOCKMODE	lockmode_on_index = AccessExclusiveLock;
 
 	pg_rusage_init(&ru0);
 
@@ -3533,10 +3608,29 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	if (!OidIsValid(heapId))
 		return;
 
+	/*
+	 * For reindex on global temporary table, If the storage for the index
+	 * in current backend is not initialized, nothing is done.
+	 */
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		if (!gtt_storage_attached(indexId))
+		{
+			/* Suppress use of the target index while rebuilding it */
+			SetReindexProcessing(heapId, indexId);
+			/* Re-allow use of target index */
+			ResetReindexProcessing();
+			return;
+		}
+
+		lockmode_on_heap = AccessShareLock;
+		lockmode_on_index = AccessShareLock;
+	}
+
 	if ((params->options & REINDEXOPT_MISSING_OK) != 0)
-		heapRelation = try_table_open(heapId, ShareLock);
+		heapRelation = try_table_open(heapId, lockmode_on_heap);
 	else
-		heapRelation = table_open(heapId, ShareLock);
+		heapRelation = table_open(heapId, lockmode_on_heap);
 
 	/* if relation is gone, leave */
 	if (!heapRelation)
@@ -3562,7 +3656,7 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	 * Open the target index relation and get an exclusive lock on it, to
 	 * ensure that no one else is touching this particular index.
 	 */
-	iRel = index_open(indexId, AccessExclusiveLock);
+	iRel = index_open(indexId, lockmode_on_index);
 
 	if (progress)
 		pgstat_progress_update_param(PROGRESS_CREATEIDX_ACCESS_METHOD_OID,
@@ -3813,6 +3907,12 @@ reindex_relation(Oid relid, int flags, ReindexParams *params)
 	bool		result;
 	ListCell   *indexId;
 	int			i;
+	LOCKMODE	lockmode;
+
+	if (flags & REINDEX_REL_PROCESS_GLOBAL_TEMP)
+		lockmode = AccessShareLock;
+	else
+		lockmode = ShareLock;
 
 	/*
 	 * Open and lock the relation.  ShareLock is sufficient since we only need
@@ -3820,9 +3920,9 @@ reindex_relation(Oid relid, int flags, ReindexParams *params)
 	 * should match ReindexTable().
 	 */
 	if ((params->options & REINDEXOPT_MISSING_OK) != 0)
-		rel = try_table_open(relid, ShareLock);
+		rel = try_table_open(relid, lockmode);
 	else
-		rel = table_open(relid, ShareLock);
+		rel = table_open(relid, lockmode);
 
 	/* if relation is gone, leave */
 	if (!rel)
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 4de8400fd0f..fe3fcc712cb 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -656,6 +656,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/* Do not allow create global temporary table in temporary schemas */
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp table in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index c5ad28d71fe..707068a6fd8 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,6 +27,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -61,6 +62,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			temprelOid;			/* InvalidOid if not a global temporary rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -115,7 +117,7 @@ AddPendingSync(const RelFileNode *rnode)
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -126,7 +128,12 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/*
+		 * Global temporary table and local temporary table use same
+		 * design on storage module.
+		 */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -154,6 +161,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
@@ -165,6 +173,21 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		AddPendingSync(&rnode);
 	}
 
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		Assert(rel && RELATION_IS_GLOBAL_TEMP(rel));
+
+		/*
+		 * Remember the reloid of global temporary table, which is used for
+		 * transaction commit or rollback.
+		 * see smgrDoPendingDeletes.
+		 */
+		pending->temprelOid = RelationGetRelid(rel);
+
+		/* Remember global temporary table storage info to localhash */
+		remember_gtt_storage_info(rnode, rel);
+	}
+
 	return srel;
 }
 
@@ -201,11 +224,20 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/*
+	 * Remember the reloid of global temporary table, which is used for
+	 * transaction commit or rollback.
+	 * see smgrDoPendingDeletes.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->temprelOid = RelationGetRelid(rel);
+
 	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
@@ -618,6 +650,7 @@ smgrDoPendingDeletes(bool isCommit)
 	int			nrels = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -647,14 +680,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->temprelOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -664,12 +701,21 @@ smgrDoPendingDeletes(bool isCommit)
 
 	if (nrels > 0)
 	{
+		int	i;
+
 		smgrdounlinkall(srels, nrels, false);
 
-		for (int i = 0; i < nrels; i++)
+		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* Delete global temporary table info in localhash */
+			if (gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 00000000000..4fc4678c4c7
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1508 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  The body implementation of Global Temparary table.
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *	  See src/backend/catalog/GTT_README for Global temparary table's
+ *	  requirements and design.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_info_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+
+int		vacuum_gtt_defer_check_age = 0;
+
+/*
+ * The Global temporary table's shared hash table data structure
+ */
+typedef struct gtt_ctl_data
+{
+	LWLock		lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+/* record this global temporary table in which backends are being used */
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+/*
+ * The Global temporary table's local hash table data structure
+ */
+/* Record the storage information and statistical information of the global temporary table */
+typedef struct
+{
+	Oid			relfilenode;
+	Oid			spcnode;
+
+	/* pg_class relstat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+
+	/* pg_statistic column stat */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_free_statistics(gtt_relfilenode *rnode);
+static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok);
+static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok);
+static Bitmapset *copy_active_gtt_bitmap(Oid relid);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+/*
+ * Calculate shared hash table entry size for GTT.
+ */
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	/* hash entry header size */
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	/*
+	 * hash entry data size
+	 * this is a bitmap in shared memory, each backend have a bit.
+	 */
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+/*
+ * Calculate shared hash table max size for GTT.
+ */
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	/* shared hash header size */
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	/* hash entry size */
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	/* max size */
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+/*
+ * Initialization shared hash table for GTT.
+ */
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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();
+	}
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(gtt_fnode);
+	info.entrysize = action_gtt_shared_hash_entry_size();
+	active_gtt_shared_hash =
+		ShmemInitHash("active gtt shared hash",
+						gtt_shared_ctl->max_entry,
+						gtt_shared_ctl->max_entry,
+						&info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+/*
+ * Record GTT relid to shared hash table, which means that current backend is using this GTT.
+ */
+static void
+gtt_storage_checkin(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			found;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *)&(fnode), 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_global_temporary_table.")));
+	}
+
+	if (!found)
+	{
+		int			wordnum;
+
+		/* init bitmap */
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	/* record itself in bitmap */
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+/*
+ * Remove the GTT relid record from the shared hash table which means that current backend is
+ * not use this GTT.
+ */
+static void
+gtt_storage_checkout(Oid relid, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		if (isCommit)
+			elog(WARNING, "relid %u not exist in gtt shared hash when drop local storage", relid);
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+	/* remove itself from bitmap */
+	bms_del_member(entry->map, MyBackendId);
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return;
+}
+
+/*
+ * Gets usage information for a GTT from shared hash table.
+ * The information is in the form of bitmap.
+ * Quickly copy the entire bitmap from shared memory and return it.
+ * that to avoid holding locks for a long time.
+ */
+static Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	Bitmapset		*map_copy = NULL;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry)
+	{
+		Assert(entry->map);
+		/* copy the entire bitmap */
+		if (!bms_is_empty(entry->map))
+			map_copy = bms_copy(entry->map);
+	}
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return map_copy;
+}
+
+/*
+ * Check if there are other backends using this GTT besides the current backend.
+ */
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			in_use = false;
+	int			num_use = 0;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		return false;
+	}
+
+	Assert(entry->map);
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+	/* how many backend are using this GTT */
+	num_use = bms_num_members(entry->map);
+	if (num_use == 0)
+		in_use = false;
+	else if (num_use == 1)
+	{
+		/* check if this is itself */
+		if(bms_is_member(MyBackendId, entry->map))
+			in_use = false;
+		else
+			in_use = true;
+	}
+	else
+		in_use = true;
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return in_use;
+}
+
+/*
+ * Record GTT information to local hash.
+ * They include GTT storage info, transaction info and statistical info.
+ */
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+	gtt_local_hash_entry		*entry;
+	MemoryContext			oldcontext;
+	gtt_relfilenode			*new_node = NULL;
+	Oid				relid = RelationGetRelid(rel);
+	int				natts = 0;
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	/* First time through: initialize the hash table */
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		HASHCTL		ctl;
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_info_context =
+			AllocSetContextCreate(CacheMemoryContext,
+								"gtt info context",
+								ALLOCSET_DEFAULT_SIZES);
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		ctl.hcxt = gtt_info_context;
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+	}
+
+	Assert(CacheMemoryContext);
+	Assert(gtt_info_context);
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	entry = gtt_search_by_relid(relid, true);
+	if (!entry)
+	{
+		bool		found = false;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash,
+						(void *) &relid, HASH_ENTER, &found);
+
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			/* record the on commit clause */
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS, true);
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			gtt_storage_checkin(relid);
+		}
+	}
+
+	/* record storage info relstat columnstats and transaction info to relfilenode list */
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	new_node->natts = 0;
+	new_node->attnum = NULL;
+	new_node->att_stat_tups = NULL;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* init column stats structure */
+	natts = RelationGetNumberOfAttributes(rel);
+	new_node->attnum = palloc0(sizeof(int) * natts);
+	new_node->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+	new_node->natts = natts;
+
+	/* only heap rel or toast rel have transaction info */
+	if (entry->relkind == RELKIND_RELATION ||
+		entry->relkind == RELKIND_TOASTVALUE)
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+
+	/* Registration callbacks are used to trigger cleanup during process exit */
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+/*
+ * Remove GTT information from local hash when transaction commit/rollback.
+ */
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	gtt_relfilenode		*d_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+		return;
+	}
+
+	d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+		else
+		{
+			/* rollback transaction */
+			if (entry->relfilenode_list == NIL)
+			{
+				if (entry->relkind == RELKIND_RELATION ||
+					entry->relkind == RELKIND_SEQUENCE)
+					gtt_storage_checkout(relid, isCommit);
+
+				hash_search(gtt_storage_local_hash,
+						(void *) &(relid), HASH_REMOVE, NULL);
+			}
+
+			return;
+		}
+	}
+
+	/* Clean up transaction info from Local order list and MyProc */
+	if (entry->relkind == RELKIND_RELATION ||
+		entry->relkind == RELKIND_TOASTVALUE)
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+
+		/* this is valid relfrozenxid */
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	/* delete relfilenode from rel entry */
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	gtt_free_statistics(d_rnode);
+
+	if (entry->relfilenode_list == NIL)
+	{
+		/* tell shared hash that current backend will no longer use this GTT */
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(relid, isCommit);
+
+		hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_REMOVE, NULL);
+	}
+
+	return;
+}
+
+/*
+ * Check if current backend is using this GTT.
+ */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool			found = false;
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (!OidIsValid(relid))
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry)
+		found = true;
+
+	return found;
+}
+
+/*
+ * When backend exit, bulk cleaning all GTT storage and local buffer of this backend.
+ */
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS			status;
+	gtt_local_hash_entry	*entry;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	/* Need to ensure we have a usable transaction. */
+	AbortOutOfAnyTransaction();
+
+	/* Search all relfilenode for GTT in current backend */
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel[1];
+			RelFileNode		rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel[0] = smgropen(rnode, MyBackendId);
+			smgrdounlinkall(srel, 1, false);
+			smgrclose(srel[0]);
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(entry->relid, false);
+
+		hash_search(gtt_storage_local_hash, (void *) &(entry->relid), HASH_REMOVE, NULL);
+	}
+
+	/* set to global area */
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;
+
+	return;
+}
+
+/*
+ * Update GTT relstats(relpage/reltuple/relallvisible)
+ * to local hash.
+ */
+void
+up_gtt_relstats(Oid relid,
+					BlockNumber num_pages,
+					double num_tuples,
+					BlockNumber num_all_visible_pages,
+					TransactionId relfrozenxid,
+					TransactionId relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (!OidIsValid(relid))
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (num_pages > 0 &&
+		gtt_rnode->relpages != (int32)num_pages)
+		gtt_rnode->relpages = (int32)num_pages;
+
+	if (num_tuples > 0 &&
+		gtt_rnode->reltuples != (float4)num_tuples)
+		gtt_rnode->reltuples = (float4)num_tuples;
+
+	/* only heap contain transaction information and relallvisible */
+	if (entry->relkind == RELKIND_RELATION ||
+		entry->relkind == RELKIND_TOASTVALUE)
+	{
+		if (num_all_visible_pages > 0 &&
+			gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+		{
+			gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+		}
+
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNextTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			/* set to local order list */
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			/* set to global area */
+			set_gtt_session_relfrozenxid();
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search GTT relstats(relpage/reltuple/relallvisible)
+ * from local has.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update GTT info(definition is same as pg_statistic)
+ * to local hash.
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+	MemoryContext		oldcontext;
+	bool		found = false;
+	int			i = 0;
+
+	/* not support whole row or system column */
+	if (attnum <= 0)
+		return;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return;
+
+	Assert(entry->relid == reloid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	/* switch context to gtt_info_context for store tuple at heap_form_tuple */
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == 0)
+		{
+			Assert(gtt_rnode->att_stat_tups[i] == NULL);
+			gtt_rnode->attnum[i] = attnum;
+			gtt_rnode->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+			found = true;
+			break;
+		}
+		else if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			heap_freetuple(gtt_rnode->att_stat_tups[i]);
+			gtt_rnode->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+			found = true;
+			break;
+		}
+	}
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!found)
+		elog(WARNING, "analyze can not update relid %u column %d statistics after add or drop column, try truncate table first", reloid, attnum);
+
+	return;
+}
+
+/*
+ * Search GTT statistic info(definition is same as pg_statistic)
+ * from local hash.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	int			i = 0;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	/* not support whole row or system column */
+	if (attnum <= 0)
+		return NULL;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return NULL;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return NULL;
+
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			return gtt_rnode->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Insert a RelfrozenXID into the list and keep the list in order.
+ */
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int		i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Remove a RelfrozenXID from order list gtt_session_relfrozenxid_list.
+ */
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+/*
+ * Update of backend Level oldest relfrozenxid to MyProc.
+ * This makes each backend's oldest RelFrozenxID globally visible.
+ */
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list != NIL)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	if (MyProc->backend_gtt_frozenxid != gtt_frozenxid)
+		MyProc->backend_gtt_frozenxid = gtt_frozenxid;
+}
+
+/*
+ * Get GTT column level data statistics.
+ */
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate		*tupstore;
+	HeapTuple		tuple;
+	Relation		rel = NULL;
+	Oid			reloid = PG_GETARG_OID(0);
+	int			attnum = PG_GETARG_INT32(1);
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	if (!RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	/* get data from local hash */
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum		values[Natts_pg_statistic];
+		bool		isnull[Natts_pg_statistic];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, AccessShareLock);
+	relation_close(pg_tatistic, AccessShareLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get GTT table level data statistics.
+ */
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate	*tupstore;
+	TupleDesc	tupdesc;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid			reloid = PG_GETARG_OID(0);
+	Oid			relnode = 0;
+	BlockNumber		relpages = 0;
+	BlockNumber		relallvisible = 0;
+	uint32			relfrozenxid = 0;
+	uint32			relminmxid = 0;
+	double			reltuples = 0;
+	Relation		rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	if (!RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[6];
+		bool	isnull[6];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get a list of backend pids that are currently using this GTT.
+ */
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	PGPROC			*proc = NULL;
+	Bitmapset		*map = NULL;
+	Tuplestorestate		*tupstore;
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	HeapTuple		tuple;
+	Oid			reloid = PG_GETARG_OID(0);
+	Relation		rel = NULL;
+	int				backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	if (!RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	/* get data from share hash */
+	map = copy_active_gtt_bitmap(reloid);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			/* backendid map to process pid */
+			proc = BackendIdGetProc(backendid);
+			if (proc && proc->pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+				pid_t	pid = proc->pid;
+
+				memset(isnull, 0, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, AccessShareLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get backend level oldest relfrozenxid of each backend using GTT in current database.
+ */
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate		*tupstore;
+	int			*pids = NULL;
+	uint32			*xids = NULL;
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	HeapTuple		tuple;
+	int			num_xid = MaxBackends + 1;
+	int			i = 0;
+	int			j = 0;
+	uint32			oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	if (pids == NULL || xids == NULL)
+		elog(ERROR, "out of memory");
+
+	/* Get backend level oldest relfrozenxid in all backend that in MyDatabaseId use GTT */
+	oldest = list_all_backend_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		/* save oldest relfrozenxid */
+		pids[i] = 0;
+		xids[i] = oldest;
+		i++;
+
+		/* save relfrozenxid for each session */
+		for (j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, 0, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+/*
+ * In order to build the GTT index, force enable GTT'index.
+ */
+void
+force_enable_gtt_index(Relation index)
+{
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(RelationGetRelid(index)));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+/*
+ * Fix the local state of the GTT's index.
+ */
+void
+gtt_fix_index_backend_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid heapOid = index->rd_index->indrelid;
+
+	/* Must be GTT */
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	/*
+	 * If this GTT is not initialized in the current backend,
+	 * its index status is temporarily set to invalid(local relcache).
+	 */
+	if (gtt_storage_attached(heapOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+/*
+ * During the SQL initialization of the executor (InitPlan)
+ * Initialize storage of GTT GTT'indexes and build empty index.
+ */
+void
+init_gtt_storage(CmdType operation, Relation relation)
+{
+	Oid			toastrelid;
+	List		*indexoidlist = NIL;
+	ListCell	*l;
+
+	if (!(operation == CMD_INSERT))
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	/* Each GTT is initialized once in each backend */
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	/* init heap storage */
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	indexoidlist = RelationGetIndexList(relation);
+	foreach(l, indexoidlist)
+	{
+		Oid			indexOid = lfirst_oid(l);
+		Relation	index = index_open(indexOid, RowExclusiveLock);
+		IndexInfo	*info = BuildDummyIndexInfo(index);
+
+		index_build(relation, index, info, true, false);
+		/* after build index, index re-enabled */
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+		index_close(index, NoLock);
+	}
+	list_free(indexoidlist);
+
+	/* rebuild index for global temp toast table */
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+
+		/* init index storage */
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid			indexId = lfirst_oid(indlist);
+			Relation	index = index_open(indexId, RowExclusiveLock);
+			IndexInfo	*info = BuildDummyIndexInfo(index);
+
+			/* build empty index */
+			index_build(toastrel, index, info, true, false);
+			Assert(index->rd_index->indisvalid);
+			Assert(index->rd_index->indislive);
+			Assert(index->rd_index->indisready);
+			index_close(index, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+/*
+ * Release the data structure memory used to store GTT storage info.
+ */
+static void
+gtt_free_statistics(gtt_relfilenode *rnode)
+{
+	int i;
+
+	Assert(rnode);
+
+	for (i = 0; i < rnode->natts; i++)
+	{
+		if (rnode->att_stat_tups[i])
+		{
+			heap_freetuple(rnode->att_stat_tups[i]);
+			rnode->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (rnode->attnum)
+		pfree(rnode->attnum);
+
+	if (rnode->att_stat_tups)
+		pfree(rnode->att_stat_tups);
+
+	pfree(rnode);
+
+	return;
+}
+
+/*
+ * Get the current relfilenode of this GTT.
+ */
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
+/*
+ * Get a relfilenode used by this GTT during the transaction life cycle.
+ */
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, bool missing_ok)
+{
+	gtt_relfilenode		*rnode = NULL;
+	ListCell		*lc;
+
+	Assert(entry);
+
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == relfilenode)
+		{
+			rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (!missing_ok && rnode == NULL)
+		elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+	return rnode;
+}
+
+/*
+ * Get one GTT info from local hash.
+ */
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry == NULL && !missing_ok)
+		elog(ERROR, "relid %u not found in local hash", relid);
+
+	return entry;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 61b515cdb85..dcf2bdbbde3 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index cd77907fc74..446a713a9b1 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -105,7 +106,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);
 
@@ -186,6 +187,17 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -602,14 +614,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]));
 		}
 
 		/*
@@ -1623,7 +1636,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;
@@ -1725,31 +1738,48 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/*
+		 * For global temporary table,
+		 * Update column statistic to localhash, not catalog.
+		 */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 9d22f648a84..a44eefa1f6b 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/toasting.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
@@ -390,6 +391,22 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+	{
+		if (gtt_storage_attached(RelationGetRelid(OldHeap)))
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("not support cluster global temporary table yet")));
+
+		relation_close(OldHeap, AccessExclusiveLock);
+		pgstat_progress_end_command();
+		return;
+	}
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
@@ -585,6 +602,8 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
 	TransactionId frozenXid;
 	MultiXactId cutoffMulti;
 
+	Assert(!RELATION_IS_GLOBAL_TEMP(OldHeap));
+
 	/* Mark the correct index as clustered */
 	if (OidIsValid(indexOid))
 		mark_index_clustered(OldHeap, indexOid, true);
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 53f48531419..c03191cce94 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -289,7 +289,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP_ON_CURRENT_SESSION(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, whereClause,
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index f366a818a14..fb0e9349a6e 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -30,6 +30,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/copyfrom_internal.h"
 #include "commands/progress.h"
@@ -652,7 +653,7 @@ CopyFrom(CopyFromState cstate)
 	 */
 	ExecInitRangeTable(estate, cstate->range_table);
 	resultRelInfo = target_resultRelInfo = makeNode(ResultRelInfo);
-	ExecInitResultRelation(estate, resultRelInfo, 1);
+	ExecInitResultRelation(estate, resultRelInfo, 1, CMD_INSERT);
 
 	/* Verify the named relation is a valid target for INSERT */
 	CheckValidResultRel(resultRelInfo, CMD_INSERT);
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 09828517153..b151fbdd80f 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -38,6 +38,7 @@
 #include "commands/prepare.h"
 #include "commands/tablecmds.h"
 #include "commands/view.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
@@ -520,6 +521,8 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 	 */
 	intoRelationDesc = table_open(intoRelationAddr.objectId, AccessExclusiveLock);
 
+	init_gtt_storage(CMD_INSERT, intoRelationDesc);
+
 	/*
 	 * Make sure the constructed table does not have RLS enabled.
 	 *
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 8d3104821ee..ae570b07c1e 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -111,6 +111,7 @@ struct ReindexIndexCallbackState
 {
 	ReindexParams params;		/* options from statement */
 	Oid			locked_table_oid;	/* tracks previously locked table */
+	LOCKMODE	lockmode;
 };
 
 /*
@@ -570,7 +571,7 @@ DefineIndex(Oid relationId,
 	 * is more efficient.  Do this before any use of the concurrent option is
 	 * done.
 	 */
-	if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+	if (stmt->concurrent && !RelpersistenceTsTemp(get_rel_persistence(relationId)))
 		concurrent = true;
 	else
 		concurrent = false;
@@ -2581,9 +2582,9 @@ ReindexIndex(RangeVar *indexRelation, ReindexParams *params, bool isTopLevel)
 	 */
 	state.params = *params;
 	state.locked_table_oid = InvalidOid;
+	state.lockmode = AccessShareLock;
 	indOid = RangeVarGetRelidExtended(indexRelation,
-									  (params->options & REINDEXOPT_CONCURRENTLY) != 0 ?
-									  ShareUpdateExclusiveLock : AccessExclusiveLock,
+									  AccessShareLock,
 									  0,
 									  RangeVarCallbackForReindexIndex,
 									  &state);
@@ -2594,11 +2595,25 @@ ReindexIndex(RangeVar *indexRelation, ReindexParams *params, bool isTopLevel)
 	 */
 	persistence = get_rel_persistence(indOid);
 	relkind = get_rel_relkind(indOid);
+	if (persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		LOCKMODE	lockmode;
+
+		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0)
+			lockmode = ShareUpdateExclusiveLock;
+		else
+			lockmode = AccessExclusiveLock;
+
+		/* lock heap first */
+		Assert(OidIsValid(state.locked_table_oid));
+		LockRelationOid(state.locked_table_oid, lockmode);
+		LockRelationOid(indOid, lockmode);
+	}
 
 	if (relkind == RELKIND_PARTITIONED_INDEX)
 		ReindexPartitions(indOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 persistence != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(persistence))
 		ReindexRelationConcurrently(indOid, params);
 	else
 	{
@@ -2620,15 +2635,7 @@ RangeVarCallbackForReindexIndex(const RangeVar *relation,
 {
 	char		relkind;
 	struct ReindexIndexCallbackState *state = arg;
-	LOCKMODE	table_lockmode;
-
-	/*
-	 * Lock level here should match table lock in reindex_index() for
-	 * non-concurrent case and table locks used by index_concurrently_*() for
-	 * concurrent case.
-	 */
-	table_lockmode = (state->params.options & REINDEXOPT_CONCURRENTLY) != 0 ?
-		ShareUpdateExclusiveLock : ShareLock;
+	LOCKMODE	table_lockmode = state->lockmode;
 
 	/*
 	 * If we previously locked some other index's heap, and the name we're
@@ -2689,6 +2696,8 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 {
 	Oid			heapOid;
 	bool		result;
+	char		relpersistence;
+	int 		reindex_flags = 0;
 
 	/*
 	 * The lock level used here should match reindex_relation().
@@ -2699,15 +2708,27 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 	 * locks on our temporary table.
 	 */
 	heapOid = RangeVarGetRelidExtended(relation,
-									   (params->options & REINDEXOPT_CONCURRENTLY) != 0 ?
-									   ShareUpdateExclusiveLock : ShareLock,
+									   AccessShareLock,
 									   0,
 									   RangeVarCallbackOwnsTable, NULL);
 
+	relpersistence = get_rel_persistence(heapOid);
+	if (relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		LOCKMODE	lockmode;
+
+		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0)
+			lockmode = ShareUpdateExclusiveLock;
+		else
+			lockmode = ShareLock;
+
+		LockRelationOid(heapOid, lockmode);
+	}
+
 	if (get_rel_relkind(heapOid) == RELKIND_PARTITIONED_TABLE)
 		ReindexPartitions(heapOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(relpersistence))
 	{
 		result = ReindexRelationConcurrently(heapOid, params);
 
@@ -2721,9 +2742,14 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 		ReindexParams newparams = *params;
 
 		newparams.options |= REINDEXOPT_REPORT_PROGRESS;
+
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			reindex_flags |= REINDEX_REL_PROCESS_GLOBAL_TEMP;
+
+		reindex_flags |= REINDEX_REL_PROCESS_TOAST;
+		reindex_flags |= REINDEX_REL_CHECK_CONSTRAINTS;
 		result = reindex_relation(heapOid,
-								  REINDEX_REL_PROCESS_TOAST |
-								  REINDEX_REL_CHECK_CONSTRAINTS,
+								  reindex_flags,
 								  &newparams);
 		if (!result)
 			ereport(NOTICE,
@@ -3119,7 +3145,7 @@ ReindexMultipleInternal(List *relids, ReindexParams *params)
 		Assert(!RELKIND_HAS_PARTITIONS(relkind));
 
 		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			relpersistence != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(relpersistence))
 		{
 			ReindexParams newparams = *params;
 
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 62465bacd81..ef37f79ba68 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -51,12 +51,32 @@ LockTableCommand(LockStmt *lockstmt)
 		RangeVar   *rv = (RangeVar *) lfirst(p);
 		bool		recurse = rv->inh;
 		Oid			reloid;
+		LOCKMODE	lockmode = lockstmt->mode;
+		char		relpersistence;
 
-		reloid = RangeVarGetRelidExtended(rv, lockstmt->mode,
-										  lockstmt->nowait ? RVR_NOWAIT : 0,
+		reloid = RangeVarGetRelidExtended(rv, NoLock, 0,
 										  RangeVarCallbackForLockTable,
 										  (void *) &lockstmt->mode);
 
+		relpersistence = get_rel_persistence(reloid);
+		if (relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			if (!lockstmt->nowait)
+				LockRelationOid(reloid, lockmode);
+			else if (!ConditionalLockRelationOid(reloid, lockmode))
+			{
+				/* try to throw error by name; relation could be deleted... */
+				char	   *relname = get_rel_name(reloid);
+
+				if (!relname)
+					return;		/* child concurrently dropped, just skip it */
+				ereport(ERROR,
+						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						 errmsg("could not obtain lock on relation \"%s\"",
+								relname)));
+			}
+		}
+
 		if (get_rel_relkind(reloid) == RELKIND_VIEW)
 			LockViewRecurse(reloid, lockstmt->mode, lockstmt->nowait, NIL);
 		else if (recurse)
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 72bfdc07a49..e5257f610f6 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -220,9 +223,12 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	rel = table_open(seqoid, AccessExclusiveLock);
 	tupDesc = RelationGetDescr(rel);
 
-	/* now initialize the sequence's data */
-	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	if (!RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		/* now initialize the sequence's data */
+		tuple = heap_form_tuple(tupDesc, value, null);
+		fill_seq_with_data(rel, tuple);
+	}
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -275,8 +281,6 @@ ResetSequence(Oid seq_relid)
 	Buffer		buf;
 	HeapTupleData seqdatatuple;
 	HeapTuple	tuple;
-	HeapTuple	pgstuple;
-	Form_pg_sequence pgsform;
 	int64		startv;
 
 	/*
@@ -287,12 +291,7 @@ ResetSequence(Oid seq_relid)
 	init_sequence(seq_relid, &elm, &seq_rel);
 	(void) read_seq_tuple(seq_rel, &buf, &seqdatatuple);
 
-	pgstuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seq_relid));
-	if (!HeapTupleIsValid(pgstuple))
-		elog(ERROR, "cache lookup failed for sequence %u", seq_relid);
-	pgsform = (Form_pg_sequence) GETSTRUCT(pgstuple);
-	startv = pgsform->seqstart;
-	ReleaseSysCache(pgstuple);
+	startv = get_seqence_start_value(seq_relid);
 
 	/*
 	 * Copy the existing sequence tuple.
@@ -451,6 +450,15 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 
 	init_sequence(relid, &elm, &seqrel);
 
+	if (RELATION_IS_GLOBAL_TEMP(seqrel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(seqrel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary sequence %s when other backend attached it.",
+						RelationGetRelationName(seqrel))));
+	}
+
 	rel = table_open(SequenceRelationId, RowExclusiveLock);
 	seqtuple = SearchSysCacheCopy1(SEQRELID,
 								   ObjectIdGetDatum(relid));
@@ -611,7 +619,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +944,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1153,6 +1161,14 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	/* Initializes the storage for sequence which the global temporary table belongs. */
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1927,3 +1943,58 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+/*
+ * Get the startValue of the sequence from syscache.
+ */
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+/*
+ * Initialize sequence which global temporary table belongs.
+ */
+void
+gtt_init_seq(Relation rel)
+{
+	Datum		value[SEQ_COL_LASTCOL];
+	bool		null[SEQ_COL_LASTCOL];
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL - 1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+	null[SEQ_COL_LASTVAL - 1] = false;
+
+	value[SEQ_COL_LOG - 1] = Int64GetDatum((int64)0);
+	null[SEQ_COL_LOG - 1] = false;
+
+	value[SEQ_COL_CALLED - 1] = BoolGetDatum(false);
+	null[SEQ_COL_CALLED - 1] = false;
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index bf42587e383..94cd1f8306d 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -118,6 +119,7 @@ typedef struct OnCommitItem
 	 */
 	SubTransactionId creating_subid;
 	SubTransactionId deleting_subid;
+	bool			 is_global_temp;
 } OnCommitItem;
 
 static List *on_commits = NIL;
@@ -607,7 +609,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
 static char GetAttributeCompression(Oid atttypid, char *compression);
-
+static OnCommitAction gtt_oncommit_option(List *options);
 
 /* ----------------------------------------------------------------
  *		DefineRelation
@@ -652,6 +654,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -663,7 +666,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * Check consistency of arguments
 	 */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !RelpersistenceTsTemp(stmt->relation->relpersistence))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -693,7 +696,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	if (RelpersistenceTsTemp(stmt->relation->relpersistence)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -794,6 +797,50 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* For global temporary table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		if (!(relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE))
+			elog(ERROR, "Only support global temporary regular table.");
+
+		/* Check parent table */
+		if (inheritOids)
+			elog(ERROR, "Not support global temporary partition table or inherit table.");
+
+		/* Check oncommit clause and save to reloptions */
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temporary table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1418,7 +1465,7 @@ RemoveRelations(DropStmt *drop)
 		 * relation persistence cannot be known without its OID.
 		 */
 		if (drop->concurrent &&
-			get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(get_rel_persistence(relOid)))
 		{
 			Assert(list_length(drop->objects) == 1 &&
 				   drop->removeType == OBJECT_INDEX);
@@ -1627,9 +1674,9 @@ ExecuteTruncate(TruncateStmt *stmt)
 		Relation	rel;
 		bool		recurse = rv->inh;
 		Oid			myrelid;
-		LOCKMODE	lockmode = AccessExclusiveLock;
+		LOCKMODE	lockmode;
 
-		myrelid = RangeVarGetRelidExtended(rv, lockmode,
+		myrelid = RangeVarGetRelidExtended(rv, AccessShareLock,
 										   0, RangeVarCallbackForTruncate,
 										   NULL);
 
@@ -1637,9 +1684,21 @@ ExecuteTruncate(TruncateStmt *stmt)
 		if (list_member_oid(relids, myrelid))
 			continue;
 
-		/* open the relation, we already hold a lock on it */
+		/* open the relation, we need hold a low-level lock first */
 		rel = table_open(myrelid, NoLock);
 
+		/*
+		 * Truncate global temp table only cleans up the data in current backend,
+		 * only low-level locks are required.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel))
+			lockmode = AccessShareLock;
+		else
+		{
+			lockmode = AccessExclusiveLock;
+			LockRelationOid(myrelid, lockmode);
+		}
+
 		/*
 		 * RangeVarGetRelidExtended() has done most checks with its callback,
 		 * but other checks with the now-opened Relation remain.
@@ -1889,6 +1948,7 @@ ExecuteTruncateGuts(List *explicit_rels,
 	foreach(cell, rels)
 	{
 		Relation	rel = (Relation) lfirst(cell);
+		LOCKMODE	lockmode;
 
 		/* Skip partitioned tables as there is nothing to do */
 		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
@@ -1939,6 +1999,19 @@ ExecuteTruncateGuts(List *explicit_rels,
 			continue;
 		}
 
+		/*
+		 * Skip the global temporary table that is not initialized for storage
+		 * in current backend.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel))
+		{
+			lockmode = AccessShareLock;
+			if (!gtt_storage_attached(RelationGetRelid(rel)))
+				continue;
+		}
+		else
+			lockmode = AccessExclusiveLock;
+
 		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
@@ -1957,6 +2030,7 @@ ExecuteTruncateGuts(List *explicit_rels,
 			Oid			heap_relid;
 			Oid			toast_relid;
 			ReindexParams reindex_params = {0};
+			int			reindex_flags = 0;
 
 			/*
 			 * This effectively deletes all rows in the table, and may be done
@@ -1984,17 +2058,21 @@ ExecuteTruncateGuts(List *explicit_rels,
 			if (OidIsValid(toast_relid))
 			{
 				Relation	toastrel = relation_open(toast_relid,
-													 AccessExclusiveLock);
+													 lockmode);
 
 				RelationSetNewRelfilenode(toastrel,
 										  toastrel->rd_rel->relpersistence);
 				table_close(toastrel, NoLock);
 			}
 
+			reindex_flags = REINDEX_REL_PROCESS_TOAST;
+			if (RELATION_IS_GLOBAL_TEMP(rel))
+				reindex_flags |= REINDEX_REL_PROCESS_GLOBAL_TEMP;
+
 			/*
 			 * Reconstruct the indexes to match, and we're done.
 			 */
-			reindex_relation(heap_relid, REINDEX_REL_PROCESS_TOAST,
+			reindex_relation(heap_relid, reindex_flags,
 							 &reindex_params);
 		}
 
@@ -4035,6 +4113,16 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temporary table only current backend use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+				 errmsg("cannot alter global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -5366,6 +5454,24 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 
 			rel = table_open(tab->relid, NoLock);
 			find_composite_type_dependencies(rel->rd_rel->reltype, rel, NULL);
+
+			if (RELATION_IS_GLOBAL_TEMP(rel) && tab->rewrite > 0)
+			{
+				if (tab->chgPersistence)
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot change global temporary table persistence setting")));
+
+				if(gtt_storage_attached(tab->relid))
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("Only support alter global temporary table in an empty context."),
+						 errhint("Please create a new connection and execute ALTER TABLE on the new connection.")));
+
+				/* There is no need to override the whole temp table */
+				tab->rewrite = 0;
+			}
+
 			table_close(rel, NoLock);
 		}
 
@@ -5417,6 +5523,8 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot rewrite temporary tables of other sessions")));
 
+			Assert(!RELATION_IS_GLOBAL_TEMP(OldHeap));
+
 			/*
 			 * Select destination tablespace (same as original unless user
 			 * requested a change)
@@ -9064,6 +9172,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -13179,7 +13293,9 @@ TryReuseIndex(Oid oldId, IndexStmt *stmt)
 		Relation	irel = index_open(oldId, NoLock);
 
 		/* If it's a partitioned index, there is no storage to share. */
-		if (irel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
+		/* multiple global temp table are not allow use same relfilenode */
+		if (irel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX &&
+			!RELATION_IS_GLOBAL_TEMP(irel))
 		{
 			stmt->oldNode = irel->rd_node.relNode;
 			stmt->oldCreateSubid = irel->rd_createSubid;
@@ -13841,6 +13957,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -14040,6 +14159,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	 */
 	rel = relation_open(tableOid, lockmode);
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		elog(ERROR, "not support alter table set tablespace on global temporary table");
+
 	/* Check first if relation can be moved to new tablespace */
 	if (!CheckRelationTableSpaceMove(rel, newTableSpace))
 	{
@@ -14341,7 +14463,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(RelationGetSmgr(rel), dstrel, MAIN_FORKNUM,
@@ -15945,6 +16067,7 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
 	switch (rel->rd_rel->relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -16386,7 +16509,7 @@ AlterSeqNamespaces(Relation classRel, Relation rel,
  * Register a newly-created relation's ON COMMIT action.
  */
 void
-register_on_commit_action(Oid relid, OnCommitAction action)
+register_on_commit_action(Oid relid, OnCommitAction action, bool is_gloal_temp)
 {
 	OnCommitItem *oc;
 	MemoryContext oldcxt;
@@ -16405,6 +16528,7 @@ register_on_commit_action(Oid relid, OnCommitAction action)
 	oc->oncommit = action;
 	oc->creating_subid = GetCurrentSubTransactionId();
 	oc->deleting_subid = InvalidSubTransactionId;
+	oc->is_global_temp = is_gloal_temp;
 
 	/*
 	 * We use lcons() here so that ON COMMIT actions are processed in reverse
@@ -16450,6 +16574,7 @@ PreCommit_on_commit_actions(void)
 	ListCell   *l;
 	List	   *oids_to_truncate = NIL;
 	List	   *oids_to_drop = NIL;
+	List	   *oids_to_truncate_gtt = NIL;
 
 	foreach(l, on_commits)
 	{
@@ -16473,7 +16598,12 @@ PreCommit_on_commit_actions(void)
 				 * tables, as they must still be empty.
 				 */
 				if ((MyXactFlags & XACT_FLAGS_ACCESSEDTEMPNAMESPACE))
-					oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid);
+				{
+					if (oc->is_global_temp)
+						oids_to_truncate_gtt = lappend_oid(oids_to_truncate_gtt, oc->relid);
+					else
+						oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid);
+				}
 				break;
 			case ONCOMMIT_DROP:
 				oids_to_drop = lappend_oid(oids_to_drop, oc->relid);
@@ -16490,7 +16620,10 @@ PreCommit_on_commit_actions(void)
 	 * exists at truncation time.
 	 */
 	if (oids_to_truncate != NIL)
-		heap_truncate(oids_to_truncate);
+		heap_truncate(oids_to_truncate, false);
+
+	if (oids_to_truncate_gtt != NIL)
+		heap_truncate(oids_to_truncate_gtt, true);
 
 	if (oids_to_drop != NIL)
 	{
@@ -17489,6 +17622,13 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot attach temporary relation of another session as partition")));
 
+	/* If the parent is permanent, so must be all of its partitions. */
+	if (attachrel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach a global temporary relation as partition of permanent relation \"%s\"",
+						RelationGetRelationName(rel))));
+
 	/* Check if there are any columns in attachrel that aren't in the parent */
 	tupleDesc = RelationGetDescr(attachrel);
 	natts = tupleDesc->natts;
@@ -18959,3 +19099,40 @@ GetAttributeCompression(Oid atttypid, char *compression)
 
 	return cmethod;
 }
+
+/*
+ * Parse the on commit clause for the temporary table
+ */
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			bool	res = false;
+			char	*sval = defGetString(def);
+
+			/* It has to be a Boolean value */
+			if (!parse_bool(sval, &res))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
+
+			if (res)
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 5c4bc15b441..3a861c47946 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"
@@ -1315,6 +1316,27 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = RELATION_IS_GLOBAL_TEMP(relation);
+
+	 /* For global temporary table */
+	if (is_gtt)
+	{
+		/* Store relation statistics and transaction information to the localhash */
+		up_gtt_relstats(RelationGetRelid(relation),
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+
+		/* Update relation statistics to local relcache */
+		relation->rd_rel->relpages = (int32) num_pages;
+		relation->rd_rel->reltuples = (float4) num_tuples;
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+		if (TransactionIdIsNormal(frozenxid))
+			relation->rd_rel->relfrozenxid = frozenxid;
+
+		if (MultiXactIdIsValid(minmulti))
+			relation->rd_rel->relminmxid = minmulti;
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1328,17 +1350,23 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (!is_gtt &&
+		pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (!is_gtt &&
+		pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (!is_gtt &&
+		pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1351,7 +1379,15 @@ vac_update_relstats(Relation relation,
 		/*
 		 * If we didn't find any indexes, reset relhasindex.
 		 */
-		if (pgcform->relhasindex && !hasindex)
+		if (is_gtt &&
+			RelationGetIndexList(relation) != NIL)
+		{
+			/*
+			 * Global temporary tables may contain indexes that are not valid locally.
+			 * The catalog should not be updated based on local invalid index.
+			 */
+		}
+		else if (pgcform->relhasindex && !hasindex)
 		{
 			pgcform->relhasindex = false;
 			dirty = true;
@@ -1383,7 +1419,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNextTransactionId(),
@@ -1394,7 +1431,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1502,6 +1540,13 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/*
+		 * The relfrozenxid for a global temporary talble is stored in localhash,
+		 * not pg_class, See list_all_session_gtt_frozenxids()
+		 */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1559,6 +1604,43 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/* If enable global temporary table */
+	if (max_active_gtt > 0)
+	{
+		TransactionId	safe_age;
+		/*  */
+		TransactionId	oldest_gtt_frozenxid =
+			list_all_backend_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid))
+		{
+			safe_age = oldest_gtt_frozenxid + vacuum_gtt_defer_check_age;
+			if (safe_age < FirstNormalTransactionId)
+				safe_age += FirstNormalTransactionId;
+
+			/*
+			 * We tolerate that the minimum age of gtt is less than
+			 * the minimum age of conventional tables, otherwise it will
+			 * throw warning message.
+			 */
+			if (TransactionIdIsNormal(safe_age) &&
+				TransactionIdPrecedes(safe_age, newFrozenXid))
+			{
+				ereport(WARNING,
+					(errmsg("global temp table oldest relfrozenxid %u is the oldest in the entire db",
+							oldest_gtt_frozenxid),
+					 errdetail("The oldest relfrozenxid in pg_class is %u", newFrozenXid),
+					 errhint("If they differ greatly, please consider cleaning up the data in global temp table.")));
+			}
+
+			/*
+			 * We need to ensure that the clog required by gtt is not cleand.
+			 */
+			if (TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+				newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1910,6 +1992,19 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	/*
+	 * Skip those global temporary table that are not initialized in
+	 * current backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel) &&
+		!gtt_storage_attached(RelationGetRelid(rel)))
+	{
+		relation_close(rel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 4df05a0b33d..4c181e2e14e 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -527,6 +527,12 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	/* Global temporary table are not sensible. */
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b3ce4bae530..611e3f18a70 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -784,6 +784,10 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		/* This is one kind of temp table */
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 5c723bc54e1..191e0f6fd21 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 4ab1302313f..b6dfb46fd57 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -50,6 +50,7 @@
 #include "access/table.h"
 #include "access/tableam.h"
 #include "access/transam.h"
+#include "catalog/storage_gtt.h"
 #include "executor/executor.h"
 #include "executor/execPartition.h"
 #include "jit/jit.h"
@@ -832,7 +833,7 @@ ExecGetRangeTableRelation(EState *estate, Index rti)
  */
 void
 ExecInitResultRelation(EState *estate, ResultRelInfo *resultRelInfo,
-					   Index rti)
+					   Index rti, CmdType operation)
 {
 	Relation	resultRelationDesc;
 
@@ -843,6 +844,9 @@ ExecInitResultRelation(EState *estate, ResultRelInfo *resultRelInfo,
 					  NULL,
 					  estate->es_instrument);
 
+	/* Check and init global temporary table storage in current backend */
+	init_gtt_storage(operation, resultRelationDesc);
+
 	if (estate->es_result_relations == NULL)
 		estate->es_result_relations = (ResultRelInfo **)
 			palloc0(estate->es_range_table_size * sizeof(ResultRelInfo *));
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d328856ae5b..c540874789b 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -38,6 +38,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -2754,13 +2755,13 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	{
 		mtstate->rootResultRelInfo = makeNode(ResultRelInfo);
 		ExecInitResultRelation(estate, mtstate->rootResultRelInfo,
-							   node->rootRelation);
+							   node->rootRelation, operation);
 	}
 	else
 	{
 		mtstate->rootResultRelInfo = mtstate->resultRelInfo;
 		ExecInitResultRelation(estate, mtstate->resultRelInfo,
-							   linitial_int(node->resultRelations));
+							   linitial_int(node->resultRelations), operation);
 	}
 
 	/* set up epqstate with dummy subplan data for the moment */
@@ -2788,7 +2789,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 		if (resultRelInfo != mtstate->rootResultRelInfo)
 		{
-			ExecInitResultRelation(estate, resultRelInfo, resultRelation);
+			ExecInitResultRelation(estate, resultRelInfo, resultRelation, operation);
 
 			/*
 			 * For child result relations, store the root result relation
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 296dd75c1b6..d971aea2546 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -48,7 +48,7 @@
 #include "partitioning/partprune.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/lsyscache.h"
-
+#include "utils/rel.h"
 
 /* results of subquery_is_pushdown_safe */
 typedef struct pushdown_safety_info
@@ -619,7 +619,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			if (RelpersistenceTsTemp(get_rel_persistence(rte->relid)))
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index bd01ec0526f..ff4e81ca2cf 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6071,7 +6071,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
-	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+	if (RELATION_IS_TEMP(heap) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 564a38a13e9..1cf2d31b034 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -30,6 +30,7 @@
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temporary table in current backend */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 146ee8dd1ea..2d4e9393f00 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2907,6 +2907,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 3d4dd43e47b..6af3302e908 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3434,17 +3434,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; }
@@ -11845,19 +11839,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 c5c3f26ecf1..2a2b2789077 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -82,6 +82,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);
 
 
 /*
@@ -3665,3 +3666,53 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * Like function isQueryUsingTempRelation_walker
+ * return true if any relation underlying
+ * the query is a global temporary table.
+ */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* Check if the query uses global temporary table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 2d857a301bb..c52bb35a3a8 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -447,6 +447,13 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	/*
+	 * If a sequence is bound to a global temporary table, then the sequence
+	 * must been "global temporary"
+	 */
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
@@ -3326,6 +3333,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		cxt.isforeign = false;
 	}
 	cxt.relation = stmt->relation;
+	/* Sets the table persistence to the context */
+	cxt.relation->relpersistence = RelationGetRelPersistence(rel);
 	cxt.rel = rel;
 	cxt.inhRelations = NIL;
 	cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index f6d05628764..0bd3914d706 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2116,6 +2116,14 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/*
+			 * Aotuvacuum cannot vacuum the private data stored in each backend
+			 * that belongs to global temporary table, so skip them.
+			 */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2182,7 +2190,7 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		if (RelpersistenceTsTemp(classForm->relpersistence))
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index b4532948d3f..1325f5e12ad 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlogutils.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -2934,7 +2935,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
-	if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind))
+	/*
+	 * Returns 0 if this global temporary table is not initialized in current
+	 * backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+	else if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind))
 	{
 		/*
 		 * Not every table AM uses BLCKSZ wide fixed size blocks.
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 9fa3e0631e6..cc3eb928bc6 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -23,6 +23,7 @@
 #include "access/syncscan.h"
 #include "access/twophase.h"
 #include "commands/async.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
@@ -143,6 +144,7 @@ CalculateShmemSize(int *num_semaphores)
 	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
@@ -246,6 +248,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* For global temporary table shared hashtable */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index a9945c80eb4..63d9d2ee80f 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -65,6 +65,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -5161,3 +5162,82 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temporary table.
+ */
+int
+list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct		*arrayP = NULL;
+	TransactionId		result = InvalidTransactionId;
+	int			index = 0;
+	int			i = 0;
+	uint8		flags = 0;
+
+	if (n)
+		*n = 0;
+
+	/* return 0 if feature is disabled */
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+	}
+
+	/* Disable in standby node */
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	arrayP = procArray;
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		uint8           statusFlags = ProcGlobal->statusFlags[index];
+
+		if (statusFlags & flags)
+			continue;
+
+		/* Fetch all backend that is belonging to MyDatabaseId */
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->backend_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->backend_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->backend_gtt_frozenxid, result))
+				result = proc->backend_gtt_frozenxid;
+
+			/* save backend pid and backend level oldest relfrozenxid */
+			if (pids)
+				pids[i] = proc->pid;
+
+			if (xids)
+				xids[i] = proc->backend_gtt_frozenxid;
+
+			i++;
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (n)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 862097352bb..4edd3b31f7a 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -176,7 +176,9 @@ static const char *const BuiltinTrancheNames[] = {
 	/* LWTRANCHE_PARALLEL_APPEND: */
 	"ParallelAppend",
 	/* LWTRANCHE_PER_XACT_PREDICATE_LIST: */
-	"PerXactPredicateList"
+	"PerXactPredicateList",
+	/* LWTRANCHE_GTT_CTL */
+	"GlobalTempTableControl"
 };
 
 StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index d1d3cd0dc88..fdc8f8e70bb 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -391,6 +391,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;	/* init backend level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
@@ -576,6 +577,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;	/* init backend level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index d5a7fb13f3c..8225cf6219f 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -982,6 +982,13 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/*
+			 * For global temporary table ,each backend has its own storage,
+			 * also only sees its own storage. Use Backendid to identify them.
+			 */
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 10895fb2876..66255eb7604 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -108,6 +108,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -5115,12 +5116,26 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								/* For global temporary table, get statistic data from localhash */
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -5368,15 +5383,28 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6820,6 +6848,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;
@@ -6837,6 +6866,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6848,6 +6885,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;
@@ -6863,6 +6902,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7781,6 +7828,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))
@@ -7793,6 +7842,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7805,6 +7863,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.
@@ -7824,6 +7884,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 9176514a962..6df1675b1f8 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -3113,6 +3114,19 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		/* For global temporary table, get statistic data from localhash */
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 105d8d4601c..18656f48442 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -65,6 +65,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1152,6 +1153,36 @@ retry:
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+				TransactionId	relfrozenxid = InvalidTransactionId;
+				MultiXactId 	relminmxid = InvalidMultiXactId;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+
+				/* For global temporary table, get relstat data from localhash */
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								&relfrozenxid,
+								&relminmxid);
+
+				/* And put them to local relcache */
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+				if (TransactionIdIsNormal(relfrozenxid))
+					relation->rd_rel->relfrozenxid = relfrozenxid;
+
+				if (MultiXactIdIsValid(relminmxid))
+					relation->rd_rel->relminmxid = relminmxid;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1212,6 +1243,10 @@ retry:
 	else
 		Assert(relation->rd_rel->relam == InvalidOid);
 
+	/* The state of the global temporary table's index may need to be set */
+	if (relation->rd_rel->relkind == RELKIND_INDEX)
+		gtt_fix_index_backend_state(relation);
+
 	/* extract reloptions if any */
 	RelationParseRelOptions(relation, pg_class_tuple);
 
@@ -1335,7 +1370,22 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+			/*
+			 * For global temporary table, get the latest relfilenode
+			 * from localhash and put it in relcache.
+			 */
+			if (OidIsValid(newrelnode) &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2288,6 +2338,9 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		/* The state of the global temporary table's index may need to be set */
+		gtt_fix_index_backend_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3568,6 +3621,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3674,28 +3731,39 @@ void
 RelationSetNewRelfilenode(Relation relation, char persistence)
 {
 	Oid			newrelfilenode;
-	Relation	pg_class;
-	HeapTuple	tuple;
+	Relation	pg_class = NULL;
+	HeapTuple	tuple = NULL;
 	Form_pg_class classform;
 	MultiXactId minmulti = InvalidMultiXactId;
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
 
+	/*
+	 * For global temporary table, storage information for the table is
+	 * maintained locally, not in catalog.
+	 */
+	bool		update_catalog = !RELATION_IS_GLOBAL_TEMP(relation);
+
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
 
-	/*
-	 * Get a writable copy of the pg_class tuple for the given relation.
-	 */
-	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+	memset(&classform, 0, sizeof(classform));
 
-	tuple = SearchSysCacheCopy1(RELOID,
-								ObjectIdGetDatum(RelationGetRelid(relation)));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "could not find tuple for relation %u",
-			 RelationGetRelid(relation));
-	classform = (Form_pg_class) GETSTRUCT(tuple);
+	if (update_catalog)
+	{
+		/*
+		 * Get a writable copy of the pg_class tuple for the given relation.
+		 */
+		pg_class = table_open(RelationRelationId, RowExclusiveLock);
+
+		tuple = SearchSysCacheCopy1(RELOID,
+									ObjectIdGetDatum(RelationGetRelid(relation)));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u",
+				 RelationGetRelid(relation));
+		classform = (Form_pg_class) GETSTRUCT(tuple);
+	}
 
 	/*
 	 * Schedule unlinking of the old storage at transaction commit.
@@ -3724,7 +3792,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);
 	}
 	else
@@ -3734,6 +3802,18 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 			 RelationGetRelationName(relation));
 	}
 
+	/* For global temporary table */
+	if (!update_catalog)
+	{
+		Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+		Assert(RELATION_IS_GLOBAL_TEMP(relation));
+		Assert(!RelationIsMapped(relation));
+
+		/* Make cache invalid and set new relnode to local cache. */
+		CacheInvalidateRelcache(relation);
+		relation->rd_node.relNode = relnode;
+	}
 	/*
 	 * If we're dealing with a mapped index, pg_class.relfilenode doesn't
 	 * change; instead we have to send the update to the relation mapper.
@@ -3743,7 +3823,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	 * possibly-inaccurate values of relpages etc, but those will be fixed up
 	 * later.
 	 */
-	if (RelationIsMapped(relation))
+	else if (RelationIsMapped(relation))
 	{
 		/* This case is only supported for indexes */
 		Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3789,9 +3869,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 		CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 	}
 
-	heap_freetuple(tuple);
+	if (update_catalog)
+	{
+		heap_freetuple(tuple);
 
-	table_close(pg_class, RowExclusiveLock);
+		table_close(pg_class, RowExclusiveLock);
+	}
 
 	/*
 	 * Make the pg_class row change or relation map change visible.  This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 7b030463013..d650382b2a8 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -44,6 +44,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "commands/prepare.h"
 #include "commands/trigger.h"
@@ -153,6 +154,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temporary table feature.
+ * table schema are still saved in catalog.
+ *
+ * num > 0 means allows the database to manage multiple active tables at the same time.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2131,6 +2144,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("Sets the amount of time to wait before forcing a "
@@ -2702,6 +2724,16 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"vacuum_gtt_defer_check_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("The defer check age of GTT, used to check expired data after vacuum."),
+			NULL
+		},
+		&vacuum_gtt_defer_check_age,
+		10000, 0, 1000000,
+		NULL, NULL, NULL
+	},
+
 	/*
 	 * See also CheckRequiredParameterValues() if this parameter changes
 	 */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ac291fbef21..5a2751ea4de 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2525,6 +2525,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
 		dopt->no_unlogged_table_data)
 		return;
 
+	/* Don't dump data in global temporary table/sequence */
+	if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		return;
+
 	/* Check that the data is not explicitly excluded */
 	if (simple_oid_list_member(&tabledata_exclude_oids,
 							   tbinfo->dobj.catId.oid))
@@ -14883,6 +14887,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
 		char	   *foreign = "";
+		char	   *table_type = NULL;
 
 		/*
 		 * Set reltypename, and collect any relkind-specific data that we
@@ -14958,9 +14963,15 @@ dumpTableSchema(Archive *fout, const 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);
 
@@ -15328,6 +15339,15 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 			}
 		}
 
+		/*
+		 * Transaction information for the global temporary table is not stored
+		 * in the pg_class.
+		 */
+		if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			Assert(tbinfo->frozenxid == 0);
+			Assert(tbinfo->minmxid == 0);
+		}
 		/*
 		 * In binary_upgrade mode, arrange to restore the old relfrozenxid and
 		 * relminmxid of all vacuumable relations.  (While vacuum.c processes
@@ -15335,7 +15355,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		 * of other relations; so this "if" lacks RELKIND_TOASTVALUE, and the
 		 * child toast table is handled below.)
 		 */
-		if (dopt->binary_upgrade &&
+		else if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
 			 tbinfo->relkind == RELKIND_MATVIEW))
 		{
@@ -16355,6 +16375,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	PQExpBuffer query = createPQExpBuffer();
 	PQExpBuffer delqry = createPQExpBuffer();
 	char	   *qseqname;
+	bool	   global_temp_seq = false;
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
@@ -16364,9 +16385,12 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
-						  "FROM pg_catalog.pg_sequence "
-						  "WHERE seqrelid = '%u'::oid",
+						  "seqcache, seqcycle, "
+						  "c.relpersistence "
+						  "FROM pg_catalog.pg_sequence s, "
+						  "pg_catalog.pg_class c "
+						  "WHERE seqrelid = '%u'::oid "
+						  "and s.seqrelid = c.oid",
 						  tbinfo->dobj.catId.oid);
 	}
 	else
@@ -16403,6 +16427,9 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
 
+	if (fout->remoteVersion >= 140000)
+		global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
 	if (strcmp(seqtype, "smallint") == 0)
@@ -16480,9 +16507,13 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	}
 	else
 	{
-		appendPQExpBuffer(query,
-						  "CREATE SEQUENCE %s\n",
-						  fmtQualifiedDumpable(tbinfo));
+		appendPQExpBuffer(query, "CREATE ");
+
+		if (global_temp_seq)
+			appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+		appendPQExpBuffer(query, "SEQUENCE %s\n",
+								  fmtQualifiedDumpable(tbinfo));
 
 		if (strcmp(seqtype, "bigint") != 0)
 			appendPQExpBuffer(query, "    AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index bc5fbd93c6c..2fe6a7c0959 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -88,7 +88,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -174,7 +174,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 1dbc09e6422..fb55f0b638e 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -298,9 +298,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -310,7 +312,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -395,7 +397,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -438,8 +440,17 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
 			 "         ON c.relnamespace = n.oid "
 			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
+			 CppAsString2(RELKIND_MATVIEW) ") AND ");
+
+	if (skip_gtt)
+	{
+		/* exclude global temp tables */
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+			"    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND ");
+	}
+
 	/* exclude possible orphaned temp tables */
+	snprintf(query + strlen(query), sizeof(query) - strlen(query),
 			 "    ((n.nspname !~ '^pg_temp_' AND "
 			 "      n.nspname !~ '^pg_toast_temp_' AND "
 			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index f85cb2e2620..e3b0a1f161b 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -410,7 +410,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -648,7 +648,10 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+			/* exclude global temp tables */
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -659,7 +662,10 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+		/* exclude global temp tables */
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 22169f10021..fb4c1f748ca 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -378,7 +378,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index c28788e84fa..d086d2f9dfa 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3739,7 +3739,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		 * Show whether a relation is permanent, temporary, or unlogged.
 		 */
 		appendPQExpBuffer(&buf,
-						  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+						  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+						  gettext_noop("session"),
 						  gettext_noop("permanent"),
 						  gettext_noop("temporary"),
 						  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b524dc87fc1..73ca85d1fab 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1059,6 +1059,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2624,6 +2626,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2854,6 +2859,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE", "SEQUENCE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 6ce480b49c1..dda3f3c5a60 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -85,7 +85,7 @@ extern Oid	heap_create_with_catalog(const char *relname,
 
 extern void heap_drop_with_catalog(Oid relid);
 
-extern void heap_truncate(List *relids);
+extern void heap_truncate(List *relids, bool is_global_temp);
 
 extern void heap_truncate_one_rel(Relation rel);
 
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 008f723e104..875b1003899 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -157,6 +157,7 @@ extern void reindex_index(Oid indexId, bool skip_constraint_checks,
 #define REINDEX_REL_CHECK_CONSTRAINTS		0x04
 #define REINDEX_REL_FORCE_INDEXES_UNLOGGED	0x08
 #define REINDEX_REL_FORCE_INDEXES_PERMANENT 0x10
+#define REINDEX_REL_PROCESS_GLOBAL_TEMP		0x20
 
 extern bool reindex_relation(Oid relid, int flags, ReindexParams *params);
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 93338d267c1..5237dd00921 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -172,6 +172,7 @@ DECLARE_INDEX(pg_class_tblspc_relfilenode_index, 3455, ClassTblspcRelfilenodeInd
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4d992dc2241..34465b2865d 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5729,6 +5729,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '9874',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '9875',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '9876',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '9877',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 0ab32b44e91..92e9f8ba485 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,7 +22,7 @@
 /* GUC variables */
 extern int	wal_skip_threshold;
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationPreTruncate(Relation rel);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 00000000000..cc023da8acd
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,45 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern int		vacuum_gtt_defer_check_age;
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Oid relid,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void force_enable_gtt_index(Relation index);
+extern void gtt_fix_index_backend_state(Relation index);
+extern void init_gtt_storage(CmdType operation, Relation relation);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index 40544dd4c70..7b66d808fc5 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 336549cc5f0..3e8167134b7 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -86,7 +86,7 @@ extern void find_composite_type_dependencies(Oid typeOid,
 
 extern void check_of_type(HeapTuple typetuple);
 
-extern void register_on_commit_action(Oid relid, OnCommitAction action);
+extern void register_on_commit_action(Oid relid, OnCommitAction action, bool is_gloal_temp);
 extern void remove_on_commit_action(Oid relid);
 
 extern void PreCommit_on_commit_actions(void);
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index cd57a704adc..717632637a9 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -575,7 +575,7 @@ exec_rt_fetch(Index rti, EState *estate)
 
 extern Relation ExecGetRangeTableRelation(EState *estate, Index rti);
 extern void ExecInitResultRelation(EState *estate, ResultRelInfo *resultRelInfo,
-								   Index rti);
+								   Index rti, CmdType operation);
 
 extern int	executor_errposition(EState *estate, int location);
 
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 8336c2c5a29..bddcfe7256d 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index c86ccdaf608..6b395551c1d 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -399,6 +399,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index a8f052e4845..4b4ed1a13aa 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -189,6 +189,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_SHARED_TIDBITMAP,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_PER_XACT_PREDICATE_LIST,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 44b477f49d7..1d621e3290e 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -164,6 +164,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId backend_gtt_frozenxid;	/* backend level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index b01fa52139a..8efffa55ac5 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -94,4 +94,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index aa18d304ac0..524c9d7de3f 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -288,6 +288,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 31281279cf9..39925543ac5 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	SMgrRelation rd_smgr;		/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -327,6 +327,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -609,11 +610,13 @@ RelationGetSmgr(Relation rel)
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -621,6 +624,7 @@ RelationGetSmgr(Relation rel)
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -633,6 +637,30 @@ RelationGetSmgr(Relation rel)
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP_ON_CURRENT_SESSION
+ *		Test a rel is either local temp relation of this session
+ *		or global temp relation.
+ */
+#define RELATION_IS_TEMP_ON_CURRENT_SESSION(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is local temp relation or global temporary relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RelpersistenceTsTemp
+ *		Test a relpersistence is local temp relation or global temporary relation.
+ */
+#define RelpersistenceTsTemp(relpersistence) \
+	(relpersistence == RELPERSISTENCE_TEMP || \
+	 relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -678,6 +706,19 @@ RelationGetSmgr(Relation rel)
 	 (relation)->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&	\
 	 !IsCatalogRelation(relation))
 
+/* For global temporary table */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/* Get on commit clause value only for global temporary table */
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+/* Get relpersistence for relation */
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
-- 
2.32.0 (Apple Git-132)

#346Andrew Bille
andrewbille@gmail.com
In reply to: wenjing zeng (#344)
Re: [Proposal] Global temporary tables

Hi!
Thanks for new patches.
Yet another crash reproduced on master with v63 patches:

CREATE TABLESPACE ts LOCATION '/tmp/ts';
CREATE GLOBAL TEMP TABLE tbl (num1 bigint);
INSERT INTO tbl (num1) values (1);
CREATE INDEX tbl_idx ON tbl (num1);
REINDEX (TABLESPACE ts) TABLE tbl;

Got error:
CREATE TABLESPACE
CREATE TABLE
INSERT 0 1
CREATE INDEX
WARNING: AbortTransaction while in COMMIT state
ERROR: gtt relfilenode 16388 not found in rel 16388
PANIC: cannot abort transaction 726, it was already committed
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
connection to server was lost

in log:
2021-12-21 12:54:08.273 +07 [208725] ERROR: gtt relfilenode 16388 not
found in rel 16388
2021-12-21 12:54:08.273 +07 [208725] STATEMENT: REINDEX (TABLESPACE ts)
TABLE tbl;
2021-12-21 12:54:08.273 +07 [208725] WARNING: AbortTransaction while in
COMMIT state
2021-12-21 12:54:08.273 +07 [208725] PANIC: cannot abort transaction 726,
it was already committed
2021-12-21 12:54:08.775 +07 [208716] LOG: server process (PID 208725) was
terminated by signal 6: Аварийный останов
2021-12-21 12:54:08.775 +07 [208716] DETAIL: Failed process was running:
REINDEX (TABLESPACE ts) TABLE tbl;
2021-12-21 12:54:08.775 +07 [208716] LOG: terminating any other active
server processes
2021-12-21 12:54:08.775 +07 [208716] LOG: all server processes terminated;
reinitializing

with dump:
[New LWP 208725]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Core was generated by `postgres: andrew postgres [local] REINDEX
'.
Program terminated with signal SIGABRT, Aborted.
#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
50 ../sysdeps/unix/sysv/linux/raise.c: Нет такого файла или каталога.
(gdb) bt
#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
#1 0x00007feadfac7859 in __GI_abort () at abort.c:79
#2 0x000055e36b6d9ec7 in errfinish (filename=0x55e36b786e20 "xact.c",
lineno=1729, funcname=0x55e36b788660 <__func__.29619>
"RecordTransactionAbort") at elog.c:680
#3 0x000055e36b0d6e37 in RecordTransactionAbort (isSubXact=false) at
xact.c:1729
#4 0x000055e36b0d7f64 in AbortTransaction () at xact.c:2787
#5 0x000055e36b0d88fa in AbortCurrentTransaction () at xact.c:3315
#6 0x000055e36b524f33 in PostgresMain (dbname=0x55e36d4d97b8 "postgres",
username=0x55e36d4d9798 "andrew") at postgres.c:4252
#7 0x000055e36b44d1e0 in BackendRun (port=0x55e36d4d1020) at
postmaster.c:4594
#8 0x000055e36b44cac5 in BackendStartup (port=0x55e36d4d1020) at
postmaster.c:4322
#9 0x000055e36b448bad in ServerLoop () at postmaster.c:1802
#10 0x000055e36b448346 in PostmasterMain (argc=3, argv=0x55e36d4a84d0) at
postmaster.c:1474
#11 0x000055e36b33b5ca in main (argc=3, argv=0x55e36d4a84d0) at main.c:198

Regards!

On Mon, Dec 20, 2021 at 7:42 PM wenjing zeng <wjzeng2012@gmail.com> wrote:

Show quoted text

Post GTT v63 to fixed conflicts with the latest code.

Hi Andrew

Have you found any new bugs recently?

Wenjing

2021年11月20日 01:31,wenjing <wjzeng2012@gmail.com> 写道:

Andrew Bille <andrewbille@gmail.com> 于2021年11月15日周一 下午6:34写道:

Thanks for the patches. The feature has become much more stable.
However, there is another simple case that generates an error:
Master with v61 patches

CREATE GLOBAL TEMPORARY TABLE t AS SELECT 1 AS a;
ERROR: could not open file "base/13560/t3_16384": No such file or
directory

Thank you for pointing out that this part is not reasonable enough.
This issue has been fixed in v62.
Looking forward to your reply.

Wenjing

Andrew

On Thu, Nov 11, 2021 at 3:15 PM wenjing <wjzeng2012@gmail.com> wrote:

Fixed a bug in function pg_gtt_attached_pid.
Looking forward to your reply.

Wenjing

<0001-gtt-v62-reademe.patch><0004-gtt-v62-regress.patch>
<0002-gtt-v62-doc.patch><0003-gtt-v62-implementation.patch>

#347wenjing
wjzeng2012@gmail.com
In reply to: Andrew Bille (#346)
4 attachment(s)
Re: [Proposal] Global temporary tables

Andrew Bille <andrewbille@gmail.com> 于2021年12月21日周二 14:00写道:

Hi!
Thanks for new patches.
Yet another crash reproduced on master with v63 patches:

CREATE TABLESPACE ts LOCATION '/tmp/ts';
CREATE GLOBAL TEMP TABLE tbl (num1 bigint);
INSERT INTO tbl (num1) values (1);
CREATE INDEX tbl_idx ON tbl (num1);
REINDEX (TABLESPACE ts) TABLE tbl;

This is a feature made in PG14 that supports reindex change tablespaces.
Thank you for pointing that out and I fixed it in v64.
Waiting for your feedback.

regards

Wenjing

Show quoted text

Got error:
CREATE TABLESPACE
CREATE TABLE
INSERT 0 1
CREATE INDEX
WARNING: AbortTransaction while in COMMIT state
ERROR: gtt relfilenode 16388 not found in rel 16388
PANIC: cannot abort transaction 726, it was already committed
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
connection to server was lost

in log:
2021-12-21 12:54:08.273 +07 [208725] ERROR: gtt relfilenode 16388 not
found in rel 16388
2021-12-21 12:54:08.273 +07 [208725] STATEMENT: REINDEX (TABLESPACE ts)
TABLE tbl;
2021-12-21 12:54:08.273 +07 [208725] WARNING: AbortTransaction while in
COMMIT state
2021-12-21 12:54:08.273 +07 [208725] PANIC: cannot abort transaction 726,
it was already committed
2021-12-21 12:54:08.775 +07 [208716] LOG: server process (PID 208725) was
terminated by signal 6: Аварийный останов
2021-12-21 12:54:08.775 +07 [208716] DETAIL: Failed process was running:
REINDEX (TABLESPACE ts) TABLE tbl;
2021-12-21 12:54:08.775 +07 [208716] LOG: terminating any other active
server processes
2021-12-21 12:54:08.775 +07 [208716] LOG: all server processes
terminated; reinitializing

with dump:
[New LWP 208725]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Core was generated by `postgres: andrew postgres [local] REINDEX
'.
Program terminated with signal SIGABRT, Aborted.
#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
50 ../sysdeps/unix/sysv/linux/raise.c: Нет такого файла или каталога.
(gdb) bt
#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
#1 0x00007feadfac7859 in __GI_abort () at abort.c:79
#2 0x000055e36b6d9ec7 in errfinish (filename=0x55e36b786e20 "xact.c",
lineno=1729, funcname=0x55e36b788660 <__func__.29619>
"RecordTransactionAbort") at elog.c:680
#3 0x000055e36b0d6e37 in RecordTransactionAbort (isSubXact=false) at
xact.c:1729
#4 0x000055e36b0d7f64 in AbortTransaction () at xact.c:2787
#5 0x000055e36b0d88fa in AbortCurrentTransaction () at xact.c:3315
#6 0x000055e36b524f33 in PostgresMain (dbname=0x55e36d4d97b8 "postgres",
username=0x55e36d4d9798 "andrew") at postgres.c:4252
#7 0x000055e36b44d1e0 in BackendRun (port=0x55e36d4d1020) at
postmaster.c:4594
#8 0x000055e36b44cac5 in BackendStartup (port=0x55e36d4d1020) at
postmaster.c:4322
#9 0x000055e36b448bad in ServerLoop () at postmaster.c:1802
#10 0x000055e36b448346 in PostmasterMain (argc=3, argv=0x55e36d4a84d0) at
postmaster.c:1474
#11 0x000055e36b33b5ca in main (argc=3, argv=0x55e36d4a84d0) at main.c:198

Regards!

On Mon, Dec 20, 2021 at 7:42 PM wenjing zeng <wjzeng2012@gmail.com> wrote:

Post GTT v63 to fixed conflicts with the latest code.

Hi Andrew

Have you found any new bugs recently?

Wenjing

2021年11月20日 01:31,wenjing <wjzeng2012@gmail.com> 写道:

Andrew Bille <andrewbille@gmail.com> 于2021年11月15日周一 下午6:34写道:

Thanks for the patches. The feature has become much more stable.
However, there is another simple case that generates an error:
Master with v61 patches

CREATE GLOBAL TEMPORARY TABLE t AS SELECT 1 AS a;
ERROR: could not open file "base/13560/t3_16384": No such file or
directory

Thank you for pointing out that this part is not reasonable enough.
This issue has been fixed in v62.
Looking forward to your reply.

Wenjing

Andrew

On Thu, Nov 11, 2021 at 3:15 PM wenjing <wjzeng2012@gmail.com> wrote:

Fixed a bug in function pg_gtt_attached_pid.
Looking forward to your reply.

Wenjing

<0001-gtt-v62-reademe.patch><0004-gtt-v62-regress.patch>
<0002-gtt-v62-doc.patch><0003-gtt-v62-implementation.patch>

Attachments:

0001-gtt-v64-reademe.patchapplication/octet-stream; name=0001-gtt-v64-reademe.patchDownload
diff --git a/README.gtt.txt b/README.gtt.txt
new file mode 100644
index 00000000000..d181df9acd7
--- /dev/null
+++ b/README.gtt.txt
@@ -0,0 +1,172 @@
+Global Temporary Table(GTT)
+=========================================
+
+Feature description
+-----------------------------------------
+
+Previously, temporary tables are defined once and automatically
+exist (starting with empty contents) in every session before using them.
+
+The temporary table implementation in PostgreSQL, known as Local temp tables(LTT),
+did not fully comply with the SQL standard. This version added the support of
+Global Temporary Table .
+
+The metadata of Global Temporary Table is persistent and shared among sessions.
+The data stored in the Global temporary table is independent of sessions. This
+means, when a session creates a Global Temporary Table and writes some data.
+Other sessions cannot see those data, but they have an empty Global Temporary
+Table with same schema.
+
+Like local temporary table, Global Temporary Table supports ON COMMIT PRESERVE ROWS
+or ON COMMIT DELETE ROWS clause, so that data in the temporary table can be
+cleaned up or preserved automatically when a session exits or a transaction COMMITs.
+
+Unlike Local Temporary Table, Global Temporary Table does not support
+ON COMMIT DROP clauses.
+
+In following paragraphs, we use GTT for Global Temporary Table and LTT for
+local temporary table.
+
+Main design ideas
+-----------------------------------------
+In general, GTT and LTT use the same storage and buffer design and
+implementation. The storage files for both types of temporary tables are named
+as t_backendid_relfilenode, and the local buffer is used to cache the data.
+
+The schema of GTTs is shared among sessions while their data are not. We build
+a new mechanisms to manage those non-shared data and their statistics.
+Here is the summary of changes:
+
+1) CATALOG
+GTTs store session-specific data. The storage information of GTTs'data, their
+transaction information, and their statistics are not stored in the catalog.
+
+2) STORAGE INFO & STATISTICS INFO & TRANSACTION INFO
+In order to maintain durability and availability of GTTs'session-specific data,
+their storage information, statistics, and transaction information is managed
+in a local hash table tt_storage_local_hash.
+
+3) DDL
+Currently, GTT supports almost all table'DDL except CLUSTER/VACUUM FULL.
+Part of the DDL behavior is limited by shared definitions and multiple copies of
+local data, and we added some structures to handle this.
+
+A shared hash table active_gtt_shared_hash is added to track the state of the
+GTT in a different session. This information is recorded in the hash table
+during the DDL execution of the GTT.
+
+The data stored in a GTT can only be modified or accessed by owning session.
+The statements that only modify data in a GTT do not need a high level of
+table locking. The operations making those changes include truncate GTT,
+reindex GTT, and lock GTT.
+
+4) MVCC commit log(clog) cleanup
+Each GTT in a session has its own piece of data, and they have their own
+transaction information. We set up data structures to track and maintain
+this information. The cleaning of CLOGs also needs to consider the transaction
+information of GTT.
+
+Detailed design
+-----------------------------------------
+
+1. CATALOG
+1.1 relpersistence
+define RELPERSISTENCEGLOBALTEMP 'g'
+Mark Global Temporary Table in pg_class relpersistence to 'g'. The relpersistence
+of indexes created on the GTT, sequences on GTT and toast tables on GTT are
+also set to 'g'
+
+1.2 on commit clause
+LTT's status associated with on commit DELETE ROWS and on commit PRESERVE ROWS
+is not stored in catalog. Instead, GTTs need a bool value on_commit_delete_rows
+in reloptions which is shared among sessions.
+
+1.3 gram.y
+GTT is already supported in syntax tree. We remove the warning message
+"GLOBAL is deprecated in temporary table creation" and mark
+relpersistence = RELPERSISTENCEGLOBALTEMP.
+
+2. STORAGE INFO & STATISTICS INFO & TRANSACTION INFO
+2.1. gtt_storage_local_hash
+Each backend creates a local hashtable gtt_storage_local_hash to track a GTT's
+storage file information, statistics, and transaction information.
+
+2.2 GTT storage file info track
+1) When one session inserts data into a GTT for the first time, record the
+storage info to gtt_storage_local_hash.
+2) Use beforeshmemexit to ensure that all files of session GTT are deleted when
+the session exits.
+
+2.3 statistics info
+1) relpages reltuples relallvisible relfilenode
+2) The statistics of each column from pg_statistic
+All the above information is stored in gtt_storage_local_hash.
+When doing vacuum or analyze, GTT's statistic is updated, which is used by
+the SQL planner.
+The statistics summarizes only data in the current session.
+
+2.3 transaction info track
+frozenxid minmulti from pg_class is stored to gtt_storage_local_hash.
+
+3 DDL
+3.1. active_gtt_shared_hash
+This is the hash table created in shared memory to trace the GTT files initialized
+in each session. Each hash entry contains a bitmap that records the backendid of
+the initialized GTT file. With this hash table, we know which backend/session
+is using this GTT. Such information is used during GTT's DDL operations.
+
+3.2 DROP GTT
+One GTT is allowed to be deleted when there is only one session using the table
+and the session is the current session. After holding the lock on GTT,
+active_gtt_shared_hash is checked to ensure that.
+
+3.3 ALTER GTT/DROP INDEX ON GTT
+Same as drop GTT.
+
+3.4 CREATE INDEX ON GTT
+1) create index on GTT statements build index based on local data in a session.
+2) After the index is created, record the index metadata to the catalog.
+3) Other sessions can enable or disable the local GTT index.
+
+3.5 TRUNCATE/REINDEX GTT
+The SQL truncate/reindex command open the GTT using AccessShareLock lock,
+not AccessExclusiveLock, because this command only cleans up local data and
+local buffers in current session. This allows these operations to be executed
+concurrently between sessions, unlike normal tables.
+
+3.6 LOCK GTT
+A lock GTT statement does not hold any relation lock.
+
+3.7 CLUSTER GTT/VACUUM FULL GTT
+The current version does not support.
+
+4 MVCC commit log(clog) cleanup
+
+The GTT storage file contains transaction information. Queries for GTT data rely
+on transaction information such as clog. The transaction information required by
+each session may be completely different. We need to ensure that the transaction
+information of the GTT data is not cleaned up during its lifetime and that
+transaction resources are recycled at the instance level.
+
+4.1 The session level GTT oldest frozenxid
+1) To manage all GTT transaction information, add session level oldest frozenxid
+in each session. When one GTT is created or removed, record the session level
+oldest frozenxid and store it in MyProc.
+2) When vacuum advances the database's frozenxid, session level oldest frozenxid
+should be considered. This is acquired by searching all of MyProc. This way,
+we can avoid the clog required by GTTs to be cleaned.
+
+4.2 vacuum GTT
+Users can perform vacuum over a GTT to clean up local data in the GTT.
+
+4.3 autovacuum GTT
+Autovacuum skips all GTTs, because the data in GTTs is only visible in current session.
+
+5 OTHERS
+5.1 Parallel query
+Planner does not produce parallel query plans for SQL related to GTT. Because
+GTT private data cannot be accessed across processes.
+
+5.2 WAL and Logical replication
+Like LTT, the DML on GTT does not record WAL and is not parsed or replay by
+the logical replication.
\ No newline at end of file
-- 
2.30.1 (Apple Git-130)

0002-gtt-v64-doc.patchapplication/octet-stream; name=0002-gtt-v64-doc.patchDownload
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 473a0a4aeb..e510bde8ac 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -169,32 +169,67 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     <listitem>
      <para>
       If specified, the table is created as a temporary table.
-      Temporary tables are automatically dropped at the end of a
-      session, or optionally at the end of the current transaction
-      (see <literal>ON COMMIT</literal> below).  The default
-      search_path includes the temporary schema first and so identically
-      named existing permanent tables are not chosen for new plans
+      Optionally, <literal>GLOBAL</literal> or <literal>LOCAL</literal>
+      can be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
+      They represent two types of temporary tables supported by <productname>PostgreSQL</productname>:
+      global temporary table and local temporary table. Without specified
+      GLOBAL or LOCAL, a local temporary table is created by default.
+     </para>
+
+    <para>
+     Both types of temporary tables’ data are truncated at the
+     end of a session or optionally at the end of the current transaction.
+     (see <literal>ON COMMIT</literal> below). For global temporary table,
+     its schema is reserved and reused by future sessions or transactions.
+     For local temporary table, both its data and its schema are dropped.
+    </para>
+
+    <variablelist>
+     <varlistentry>
+      <term><literal>Global Temporary Table</literal></term>
+      <listitem>
+       <para>
+        Global temporary table are defined just once and automatically exist
+        (starting with empty contents) in every session that needs them.
+        The schema definition of temporary tables is persistent and shared among sessions.
+        However, the data in temporary tables are kept private to sessions themselves,
+        even though they use same name and same schema.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>Local Temporary Table</literal></term>
+     <listitem>
+     <para>
+      Local temporary table are automatically dropped at the end of a
+      session (include schema and data). Future sessions need to create
+      their own temporary tables when they are used.
+     </para>
+     <para>
+      The default search_path includes the temporary schema first and so
+      identically named existing permanent tables are not chosen for new plans
       while the temporary table exists, unless they are referenced
       with schema-qualified names. Any indexes created on a temporary
       table are automatically temporary as well.
      </para>
+     </listitem>
+     </varlistentry>
+    </variablelist>
 
-     <para>
-      The <link linkend="autovacuum">autovacuum daemon</link> cannot
-      access and therefore cannot vacuum or analyze temporary tables.
-      For this reason, appropriate vacuum and analyze operations should be
-      performed via session SQL commands.  For example, if a temporary
-      table is going to be used in complex queries, it is wise to run
-      <command>ANALYZE</command> on the temporary table after it is populated.
-     </para>
+    <para>
+     The <link linkend="autovacuum">autovacuum daemon</link> cannot
+     access and therefore cannot vacuum or analyze temporary tables.
+     For this reason, appropriate vacuum and analyze operations should be
+     performed via session SQL commands.  For example, if a temporary
+     table is going to be used in complex queries, it is wise to run
+     <command>ANALYZE</command> on the temporary table after it is populated.
+    </para>
+    <para>
+     The Temporary table resembles the SQL standard, but has some differences.
+     see <xref linkend="sql-createtable-compatibility"/> below.
+    </para>
 
-     <para>
-      Optionally, <literal>GLOBAL</literal> or <literal>LOCAL</literal>
-      can be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
-      This presently makes no difference in <productname>PostgreSQL</productname>
-      and is deprecated; see
-      <xref linkend="sql-createtable-compatibility"/> below.
-     </para>
     </listitem>
    </varlistentry>
 
@@ -2133,13 +2168,17 @@ CREATE TABLE cities_partdef
    <title>Temporary Tables</title>
 
    <para>
-    Although the syntax of <literal>CREATE TEMPORARY TABLE</literal>
-    resembles that of the SQL standard, the effect is not the same.  In the
-    standard,
-    temporary tables are defined just once and automatically exist (starting
-    with empty contents) in every session that needs them.
-    <productname>PostgreSQL</productname> instead
-    requires each session to issue its own <literal>CREATE TEMPORARY
+    Although the syntax of <literal>CREATE GLOBAL/LOCAL TEMPORARY TABLE</literal>
+    resembles that of the SQL standard, the effect is not the same.
+    The global temporary table follows the SQL standards while local temporary
+    table does not.
+   </para>
+
+   <para>
+    First, in the standard, both global and local temporary tables are defined just
+    once and automatically exist (starting with empty contents) in every session
+    that needs them. For local temporary tables, <productname>PostgreSQL</productname>
+    instead requires each session to issue its own <literal>CREATE LOCAL TEMPORARY
     TABLE</literal> command for each temporary table to be used.  This allows
     different sessions to use the same temporary table name for different
     purposes, whereas the standard's approach constrains all instances of a
@@ -2147,29 +2186,14 @@ CREATE TABLE cities_partdef
    </para>
 
    <para>
-    The standard's definition of the behavior of temporary tables is
-    widely ignored.  <productname>PostgreSQL</productname>'s behavior
-    on this point is similar to that of several other SQL databases.
-   </para>
-
-   <para>
-    The SQL standard also distinguishes between global and local temporary
+    Second, the SQL standard distinguishes between global and local temporary
     tables, where a local temporary table has a separate set of contents for
     each SQL module within each session, though its definition is still shared
-    across sessions.  Since <productname>PostgreSQL</productname> does not
+    across sessions. Since <productname>PostgreSQL</productname> does not
     support SQL modules, this distinction is not relevant in
     <productname>PostgreSQL</productname>.
    </para>
 
-   <para>
-    For compatibility's sake, <productname>PostgreSQL</productname> will
-    accept the <literal>GLOBAL</literal> and <literal>LOCAL</literal> keywords
-    in a temporary table declaration, but they currently have no effect.
-    Use of these keywords is discouraged, since future versions of
-    <productname>PostgreSQL</productname> might adopt a more
-    standard-compliant interpretation of their meaning.
-   </para>
-
    <para>
     The <literal>ON COMMIT</literal> clause for temporary tables
     also resembles the SQL standard, but has some differences.
@@ -2177,7 +2201,8 @@ CREATE TABLE cities_partdef
     default behavior is <literal>ON COMMIT DELETE ROWS</literal>.  However, the
     default behavior in <productname>PostgreSQL</productname> is
     <literal>ON COMMIT PRESERVE ROWS</literal>.  The <literal>ON COMMIT
-    DROP</literal> option does not exist in SQL.
+    DROP</literal> option does not exist in SQL and is not supported by
+    global temporary table.
    </para>
   </refsect2>
 
-- 
2.30.1 (Apple Git-130)

0004-gtt-v64-regress.patchapplication/octet-stream; name=0004-gtt-v64-regress.patchDownload
diff --git a/src/test/isolation/expected/gtt-sequence.out b/src/test/isolation/expected/gtt-sequence.out
new file mode 100644
index 00000000000..31db2ebd423
--- /dev/null
+++ b/src/test/isolation/expected/gtt-sequence.out
@@ -0,0 +1,48 @@
+unused step name: s1_seq_restart
+Parsed test spec with 2 sessions
+
+starting permutation: s1_seq_next s2_seq_next s1_seq_next
+step s1_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      1
+(1 row)
+
+step s2_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      1
+(1 row)
+
+step s1_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      2
+(1 row)
+
+
+starting permutation: s1_select s2_select s1_insert s2_insert s1_select s2_select
+step s1_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+(0 rows)
+
+step s2_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+(0 rows)
+
+step s1_insert: insert into gtt_with_seq values(1);
+step s2_insert: insert into gtt_with_seq values(10);
+step s1_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+ 1| 3
+(1 row)
+
+step s2_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+10| 1
+(1 row)
+
diff --git a/src/test/isolation/expected/gtt-table.out b/src/test/isolation/expected/gtt-table.out
new file mode 100644
index 00000000000..5825773aa12
--- /dev/null
+++ b/src/test/isolation/expected/gtt-table.out
@@ -0,0 +1,675 @@
+Parsed test spec with 3 sessions
+
+starting permutation: s1_update_d
+step s1_update_d: update gtt_on_commit_delete_row set b = 'update'
+
+starting permutation: s1_select_d s1_insert_d s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_begin s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+1|test1
+(1 row)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_begin s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+1|test1
+(1 row)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_select_d s1_truncate_d s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_begin s1_insert_d s1_select_d s1_truncate_d s1_select_d s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_begin s1_insert_d s1_select_d s1_truncate_d s1_select_d s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_insert_d s1_select_d s1_begin s1_insert_d s1_select_d s1_save_1 s1_truncate_d s1_insert_d s1_select_d s1_save_2 s1_truncate_d s1_insert_d s1_select_d s1_save_3 s1_rollback_to_save_2 s1_select_d s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_save_1: SAVEPOINT save1
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_save_2: SAVEPOINT save2
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+4|test1
+(1 row)
+
+step s1_save_3: SAVEPOINT save3
+step s1_rollback_to_save_2: rollback to savepoint save2
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+5|test1
+(2 rows)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_insert_d s1_select_d s1_begin s1_insert_d s1_select_d s1_save_1 s1_truncate_d s1_insert_d s1_select_d s1_save_2 s1_truncate_d s1_insert_d s1_select_d s1_save_3 s1_rollback_to_save_2 s1_select_d s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_save_1: SAVEPOINT save1
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_save_2: SAVEPOINT save2
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+4|test1
+(1 row)
+
+step s1_save_3: SAVEPOINT save3
+step s1_rollback_to_save_2: rollback to savepoint save2
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+5|test1
+(2 rows)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_update_p
+step s2_update_p: update gtt_on_commit_preserve_row set b = 'update'
+
+starting permutation: s2_select_p s2_insert_p s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_begin s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_begin s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_select_p s2_insert_p s2_select_p s2_truncate_p s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_select_p s2_insert_p s2_begin s2_insert_p s2_select_p s2_truncate_p s2_select_p s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_insert_p s2_begin s2_insert_p s2_select_p s2_truncate_p s2_select_p s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_insert_p s2_select_p s2_begin s2_insert_p s2_select_p s2_save_1 s2_truncate_p s2_insert_p s2_select_p s2_save_2 s2_truncate_p s2_insert_p s2_select_p s2_save_3 s2_rollback_to_save_2 s2_select_p s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_save_1: SAVEPOINT save1
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_save_2: SAVEPOINT save2
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+4|test10
+(1 row)
+
+step s2_save_3: SAVEPOINT save3
+step s2_rollback_to_save_2: rollback to savepoint save2
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+
+starting permutation: s2_insert_p s2_select_p s2_begin s2_insert_p s2_select_p s2_save_1 s2_truncate_p s2_insert_p s2_select_p s2_save_2 s2_truncate_p s2_insert_p s2_select_p s2_save_3 s2_rollback_to_save_2 s2_select_p s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_save_1: SAVEPOINT save1
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_save_2: SAVEPOINT save2
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+4|test10
+(1 row)
+
+step s2_save_3: SAVEPOINT save3
+step s2_rollback_to_save_2: rollback to savepoint save2
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_insert_p s2_insert_p s1_select_p s2_select_p
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_truncate_p s2_truncate_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_begin s1_insert_d s2_insert_d s1_truncate_d s2_insert_d s1_commit
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s2_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s2_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_commit: commit
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_reindex_p s2_reindex_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_reindex_p: reindex table gtt_on_commit_preserve_row
+step s2_reindex_p: reindex table gtt_on_commit_preserve_row
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_reindex_i_p s2_reindex_i_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_reindex_i_p: reindex index gtt_on_commit_preserve_row_pkey
+step s2_reindex_i_p: reindex index gtt_on_commit_preserve_row_pkey
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_insert_c s3_create_c s3_insert_c s1_insert_c s1_analyze_c s2_analyze_c s3_analyze_c s1_select_c s2_select_c s3_select_c
+step s2_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s3_create_c: create unique index idx_temp_table_a on gtt_test_createindex(a)
+step s3_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s1_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s1_analyze_c: analyze gtt_test_createindex
+step s2_analyze_c: analyze gtt_test_createindex
+step s3_analyze_c: analyze gtt_test_createindex
+step s1_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                                               
+---------------------------------------------------------
+Index Scan using idx_temp_table_a on gtt_test_createindex
+  Index Cond: (a = 1)                                    
+(2 rows)
+
+step s2_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                      
+--------------------------------
+Seq Scan on gtt_test_createindex
+  Filter: (a = 1)               
+(2 rows)
+
+step s3_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                                               
+---------------------------------------------------------
+Index Scan using idx_temp_table_a on gtt_test_createindex
+  Index Cond: (a = 1)                                    
+(2 rows)
+
+
+starting permutation: s1_begin s2_begin s1_lock_p s2_lock_p s1_truncate_p s2_truncate_p s1_insert_p s2_insert_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_lock_p: LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE
+step s2_lock_p: LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE
+step s1_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index 99c23b16ffe..f5c8eec533d 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -97,3 +97,5 @@ test: plpgsql-toast
 test: truncate-conflict
 test: serializable-parallel
 test: serializable-parallel-2
+test: gtt-sequence
+test: gtt-table
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 12179f25146..7cf0b20e636 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -80,9 +80,30 @@ disconnect_atexit(void)
 {
 	int			i;
 
-	for (i = 0; i < nconns; i++)
+	for (i = 1; i < nconns; i++)
 		if (conns[i].conn)
 			PQfinish(conns[i].conn);
+
+	if (parseresult.destroy)
+	{
+		PGresult   *res;
+
+		res = PQexec(conns[0].conn, parseresult.destroy);
+		if (PQresultStatus(res) == PGRES_TUPLES_OK)
+		{
+			printResultSet(res);
+		}
+		else if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		{
+			fprintf(stderr, "destroy failed: %s",
+					PQerrorMessage(conns[0].conn));
+			/* don't exit on teardown failure */
+		}
+		PQclear(res);
+	}
+
+	if (conns[0].conn)
+		PQfinish(conns[0].conn);
 }
 
 int
@@ -238,6 +259,24 @@ main(int argc, char **argv)
 	PQclear(res);
 	termPQExpBuffer(&wait_query);
 
+	if (parseresult.initialize)
+	{
+		PGresult   *res;
+
+		res = PQexec(conns[0].conn, parseresult.initialize);
+		if (PQresultStatus(res) == PGRES_TUPLES_OK)
+		{
+			printResultSet(res);
+		}
+		else if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		{
+			fprintf(stderr, "initialize failed: %s",
+					PQerrorMessage(conns[0].conn));
+			/* don't exit on teardown failure */
+		}
+		PQclear(res);
+	}
+
 	/*
 	 * Run the permutations specified in the spec, or all if none were
 	 * explicitly specified.
diff --git a/src/test/isolation/isolationtester.h b/src/test/isolation/isolationtester.h
index 5f300219c20..b5a29893da9 100644
--- a/src/test/isolation/isolationtester.h
+++ b/src/test/isolation/isolationtester.h
@@ -81,6 +81,8 @@ typedef struct
 	int			nsessions;
 	Permutation **permutations;
 	int			npermutations;
+	char	   *initialize;
+	char	   *destroy;
 } TestSpec;
 
 extern TestSpec parseresult;
diff --git a/src/test/isolation/specparse.y b/src/test/isolation/specparse.y
index c25aa1a73fa..2784f758ed9 100644
--- a/src/test/isolation/specparse.y
+++ b/src/test/isolation/specparse.y
@@ -39,7 +39,7 @@ TestSpec		parseresult;			/* result of parsing is left here */
 }
 
 %type <ptr_list> setup_list
-%type <str>  opt_setup opt_teardown
+%type <str>  opt_setup opt_teardown opt_initialize opt_destroy
 %type <str> setup
 %type <ptr_list> step_list session_list permutation_list opt_permutation_list
 %type <ptr_list> permutation_step_list blocker_list
@@ -51,23 +51,27 @@ TestSpec		parseresult;			/* result of parsing is left here */
 
 %token <str> sqlblock identifier
 %token <integer> INTEGER
-%token NOTICES PERMUTATION SESSION SETUP STEP TEARDOWN TEST
+%token NOTICES PERMUTATION SESSION SETUP STEP TEARDOWN TEST INITIALIZE DESTROY
 
 %%
 
 TestSpec:
+			opt_initialize
 			setup_list
 			opt_teardown
+			opt_destroy
 			session_list
 			opt_permutation_list
 			{
-				parseresult.setupsqls = (char **) $1.elements;
-				parseresult.nsetupsqls = $1.nelements;
-				parseresult.teardownsql = $2;
-				parseresult.sessions = (Session **) $3.elements;
-				parseresult.nsessions = $3.nelements;
-				parseresult.permutations = (Permutation **) $4.elements;
-				parseresult.npermutations = $4.nelements;
+				parseresult.setupsqls = (char **) $2.elements;
+				parseresult.nsetupsqls = $2.nelements;
+				parseresult.teardownsql = $3;
+				parseresult.sessions = (Session **) $5.elements;
+				parseresult.nsessions = $5.nelements;
+				parseresult.permutations = (Permutation **) $6.elements;
+				parseresult.npermutations = $6.nelements;
+				parseresult.initialize = $1;
+				parseresult.destroy = $4;
 			}
 		;
 
@@ -100,6 +104,16 @@ opt_teardown:
 			| TEARDOWN sqlblock	{ $$ = $2; }
 		;
 
+opt_initialize:
+			/* EMPTY */			{ $$ = NULL; }
+			| INITIALIZE sqlblock	{ $$ = $2; }
+		;
+
+opt_destroy:
+			/* EMPTY */			{ $$ = NULL; }
+			| DESTROY sqlblock	{ $$ = $2; }
+		;
+
 session_list:
 			session_list session
 			{
diff --git a/src/test/isolation/specs/gtt-sequence.spec b/src/test/isolation/specs/gtt-sequence.spec
new file mode 100644
index 00000000000..88eece45e29
--- /dev/null
+++ b/src/test/isolation/specs/gtt-sequence.spec
@@ -0,0 +1,39 @@
+# Tests for global temporary relations
+
+initialize
+{
+  CREATE GLOBAL TEMPORARY TABLE if not exists gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+}
+
+destroy
+{
+  /* wait other backend exit */
+  select pg_sleep(1);
+
+  DROP TABLE gtt_with_seq;
+}
+
+# Session 1
+session "s1"
+step "s1_seq_next" { select nextval('gtt_with_seq_c2_seq'); }
+step "s1_seq_restart" { alter sequence gtt_with_seq_c2_seq RESTART; }
+step "s1_insert" { insert into gtt_with_seq values(1); }
+step "s1_select" { select * from gtt_with_seq order by c1,c2; }
+teardown
+{
+  TRUNCATE gtt_with_seq;
+}
+
+# Session 2
+session "s2"
+step "s2_seq_next" { select nextval('gtt_with_seq_c2_seq'); }
+step "s2_insert" { insert into gtt_with_seq values(10); }
+step "s2_select" { select * from gtt_with_seq order by c1,c2; }
+teardown
+{
+  TRUNCATE gtt_with_seq RESTART IDENTITY;
+}
+
+permutation "s1_seq_next" "s2_seq_next" "s1_seq_next"
+permutation "s1_select" "s2_select" "s1_insert" "s2_insert" "s1_select" "s2_select"
+
diff --git a/src/test/isolation/specs/gtt-table.spec b/src/test/isolation/specs/gtt-table.spec
new file mode 100644
index 00000000000..e0396b21ef0
--- /dev/null
+++ b/src/test/isolation/specs/gtt-table.spec
@@ -0,0 +1,135 @@
+# Tests for global temporary relations
+
+initialize
+{
+  create global temp table gtt_on_commit_delete_row(a bigserial primary key, b text) on commit delete rows;
+  create global temp table gtt_on_commit_preserve_row(a bigserial primary key, b text) on commit preserve rows;
+  create global temp table gtt_test_createindex(a int, b char(1000)) on commit preserve rows;
+}
+
+destroy
+{
+  /* wait other backend exit */
+  select pg_sleep(1);
+
+  DROP TABLE gtt_on_commit_delete_row;
+  DROP TABLE gtt_on_commit_preserve_row;
+  DROP TABLE gtt_test_createindex;
+}
+
+# Session 1
+session "s1"
+step "s1_begin" {begin}
+step "s1_commit" {commit}
+step "s1_rollback" {rollback}
+step "s1_insert_d" {insert into gtt_on_commit_delete_row (b) values('test1')}
+step "s1_select_d" {select a,b from gtt_on_commit_delete_row order by a,b}
+step "s1_insert_p" {insert into gtt_on_commit_preserve_row (b) values('test20')}
+step "s1_select_p" {select a,b from gtt_on_commit_preserve_row order by a,b}
+step "s1_truncate_d" {truncate gtt_on_commit_delete_row}
+step "s1_truncate_p" {truncate gtt_on_commit_preserve_row}
+step "s1_lock_p" {LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE}
+step "s1_update_d" {update gtt_on_commit_delete_row set b = 'update'}
+step "s1_save_1" {SAVEPOINT save1}
+step "s1_save_2" {SAVEPOINT save2}
+step "s1_save_3" {SAVEPOINT save3}
+step "s1_rollback_to_save_2" {rollback to savepoint save2}
+step "s1_reindex_p" {reindex table gtt_on_commit_preserve_row}
+step "s1_reindex_i_p" {reindex index gtt_on_commit_preserve_row_pkey}
+step "s1_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s1_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s1_analyze_c" {analyze gtt_test_createindex}
+teardown
+{
+  TRUNCATE gtt_on_commit_delete_row RESTART IDENTITY;
+  TRUNCATE gtt_on_commit_preserve_row RESTART IDENTITY;
+}
+
+# Session 2
+session "s2"
+step "s2_begin" {begin}
+step "s2_commit" {commit}
+step "s2_rollback" {rollback}
+step "s2_insert_p" {insert into gtt_on_commit_preserve_row (b) values('test10')}
+step "s2_select_p" {select a,b from gtt_on_commit_preserve_row order by a,b}
+step "s2_insert_d" {insert into gtt_on_commit_delete_row (b) values('test1')}
+step "s2_truncate_p" {truncate gtt_on_commit_preserve_row}
+step "s2_update_p" {update gtt_on_commit_preserve_row set b = 'update'}
+step "s2_lock_p" {LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE}
+step "s2_save_1" {SAVEPOINT save1}
+step "s2_save_2" {SAVEPOINT save2}
+step "s2_save_3" {SAVEPOINT save3}
+step "s2_rollback_to_save_2" {rollback to savepoint save2}
+step "s2_reindex_p" {reindex table gtt_on_commit_preserve_row}
+step "s2_reindex_i_p" {reindex index gtt_on_commit_preserve_row_pkey}
+step "s2_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s2_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s2_analyze_c" {analyze gtt_test_createindex}
+teardown
+{
+  TRUNCATE gtt_on_commit_delete_row RESTART IDENTITY;
+  TRUNCATE gtt_on_commit_preserve_row RESTART IDENTITY;
+}
+
+session "s3"
+step "s3_create_c" {create unique index idx_temp_table_a on gtt_test_createindex(a)}
+step "s3_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s3_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s3_analyze_c" {analyze gtt_test_createindex}
+
+
+#
+# test on commit delete temp table
+#
+
+# test update empty temp table
+permutation "s1_update_d"
+# test insert into temp table
+permutation "s1_select_d" "s1_insert_d" "s1_select_d"
+# test temp table in transaction(commit)
+permutation "s1_select_d" "s1_begin"    "s1_insert_d" "s1_select_d"   "s1_commit"   "s1_select_d" 
+# test temp table in transaction(rollback)
+permutation "s1_select_d" "s1_begin"    "s1_insert_d" "s1_select_d"   "s1_rollback" "s1_select_d" 
+# test truncate
+permutation "s1_select_d" "s1_insert_d" "s1_select_d" "s1_truncate_d" "s1_select_d"
+# test truncate in transaction block
+permutation "s1_select_d" "s1_insert_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_truncate_d" "s1_select_d"   "s1_insert_d" "s1_select_d" "s1_commit"   "s1_select_d" 
+permutation "s1_select_d" "s1_insert_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_truncate_d" "s1_select_d"   "s1_insert_d" "s1_select_d" "s1_rollback" "s1_select_d"
+# test temp table with subtransaction or savepoint
+permutation "s1_insert_d" "s1_select_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_save_1"     "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_2"   "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_3" "s1_rollback_to_save_2" "s1_select_d" "s1_insert_d" "s1_select_d" "s1_commit" "s1_select_d"
+permutation "s1_insert_d" "s1_select_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_save_1"     "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_2"   "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_3" "s1_rollback_to_save_2" "s1_select_d" "s1_insert_d" "s1_select_d" "s1_rollback" "s1_select_d"
+
+#
+# test on commit preserve table
+#
+
+# same as test on commit delete temp table
+permutation "s2_update_p"
+permutation "s2_select_p" "s2_insert_p" "s2_select_p"
+permutation "s2_select_p" "s2_begin"    "s2_insert_p" "s2_select_p"   "s2_commit"   "s2_select_p"
+permutation "s2_select_p" "s2_begin"    "s2_insert_p" "s2_select_p"   "s2_rollback" "s2_select_p"
+permutation "s2_select_p" "s2_insert_p" "s2_select_p" "s2_truncate_p" "s2_select_p"
+permutation "s2_select_p" "s2_insert_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_truncate_p" "s2_select_p"   "s2_insert_p" "s2_select_p" "s2_commit"   "s2_select_p" 
+permutation "s2_select_p" "s2_insert_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_truncate_p" "s2_select_p"   "s2_insert_p" "s2_select_p" "s2_rollback" "s2_select_p" 
+permutation "s2_insert_p" "s2_select_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_save_1"     "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_2"   "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_3" "s2_rollback_to_save_2" "s2_select_p" "s2_insert_p" "s2_select_p" "s2_commit" "s2_select_p"
+permutation "s2_insert_p" "s2_select_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_save_1"     "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_2"   "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_3" "s2_rollback_to_save_2" "s2_select_p" "s2_insert_p" "s2_select_p" "s2_rollback" "s2_select_p"
+
+#
+# test concurrent operation on temp table
+#
+
+#  test concurrent read
+permutation "s1_insert_p" "s2_insert_p" "s1_select_p" "s2_select_p" 
+#  test concurrent truncate
+permutation "s1_begin" "s2_begin"    "s1_insert_p" "s2_insert_p"   "s1_truncate_p" "s2_truncate_p"  "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+permutation "s1_begin" "s1_insert_d" "s2_insert_d" "s1_truncate_d" "s2_insert_d"   "s1_commit" 
+#  test concurrent reindex table
+permutation "s1_begin" "s2_begin" "s1_insert_p" "s2_insert_p" "s1_reindex_p"   "s2_reindex_p"   "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+#  test concurrent reindex index
+permutation "s1_begin" "s2_begin" "s1_insert_p" "s2_insert_p" "s1_reindex_i_p" "s2_reindex_i_p" "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+
+# test create index
+permutation "s2_insert_c" "s3_create_c" "s3_insert_c" "s1_insert_c" "s1_analyze_c" "s2_analyze_c" "s3_analyze_c" "s1_select_c" "s2_select_c" "s3_select_c"
+
+# test lock gtt
+permutation "s1_begin" "s2_begin" "s1_lock_p" "s2_lock_p" "s1_truncate_p" "s2_truncate_p" "s1_insert_p" "s2_insert_p" "s1_commit" "s2_commit" "s1_select_p" "s2_select_p"
diff --git a/src/test/isolation/specscanner.l b/src/test/isolation/specscanner.l
index d9fa6a5b54a..697db975479 100644
--- a/src/test/isolation/specscanner.l
+++ b/src/test/isolation/specscanner.l
@@ -67,6 +67,8 @@ session			{ return SESSION; }
 setup			{ return SETUP; }
 step			{ return STEP; }
 teardown		{ return TEARDOWN; }
+initialize		 { return INITIALIZE; }
+destroy			 { return DESTROY; }
 
  /* Whitespace and comments */
 [\n]			{ yyline++; }
diff --git a/src/test/regress/expected/global_temporary_table.out b/src/test/regress/expected/global_temporary_table.out
new file mode 100644
index 00000000000..2ada5fa6b92
--- /dev/null
+++ b/src/test/regress/expected/global_temporary_table.out
@@ -0,0 +1,577 @@
+--
+-- GLobal emparary table test case 
+--
+CREATE SCHEMA IF NOT EXISTS global_temporary_table;
+set search_path=global_temporary_table,sys;
+--
+--  test create global temp table basic syntax
+--
+create global temp table gtt_on_commit_default(a int primary key, b text);
+create global temp table gtt_on_commit_delete(a int primary key, b text) on commit delete rows;
+create global temp table gtt_on_commit_delete2(n int) with (on_commit_delete_rows='true');
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit PRESERVE rows;
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt_test_createindex(c0 tsvector,c1 varchar(100), c2 int);
+create global temp table gtt_test_alter(b text) with(on_commit_delete_rows=true);
+--
+-- test DML on global temp table
+--
+-- update empty temp table
+update gtt_on_commit_delete set b ='test';
+begin;
+insert into gtt_on_commit_delete values (1);
+update gtt_on_commit_delete set b ='test';
+-- should 1 row
+select * from gtt_on_commit_delete;
+ a |  b   
+---+------
+ 1 | test
+(1 row)
+
+commit;
+-- data delete after transaction commit
+-- should 0 row
+select * from gtt_on_commit_delete;
+ a | b 
+---+---
+(0 rows)
+
+-- update empty temp table
+update gtt_on_commit_preserve set b ='test';
+insert into gtt_on_commit_preserve values (2);
+begin;
+insert into gtt_on_commit_preserve values (3);
+update gtt_on_commit_preserve set b ='test';
+-- should 2 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 2 | test
+ 3 | test
+(2 rows)
+
+delete from gtt_on_commit_preserve where a=2;
+commit;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 3 | test
+(1 row)
+
+begin;
+insert into gtt_on_commit_preserve values (4);
+-- temp table support truncate;
+truncate gtt_on_commit_preserve;
+select * from gtt_on_commit_preserve order by a;
+ a | b 
+---+---
+(0 rows)
+
+rollback;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 3 | test
+(1 row)
+
+--
+-- test unsupported global temp partition table
+--
+-- should fail
+CREATE global temp TABLE global_temp_partition_01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+on commit delete rows;
+ERROR:  Only support global temporary regular table.
+CREATE TABLE regular_partition_01_2019 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+);
+CREATE TABLE regular_partition01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time);
+-- should fail
+CREATE global temp TABLE temp_partition01_2018
+PARTITION OF regular_partition01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+ERROR:  Not support global temporary partition table or inherit table.
+CREATE global temp TABLE global_temp_partition_01_2021 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+)on commit delete rows;
+-- should fail
+ALTER TABLE regular_partition01 ATTACH PARTITION global_temp_partition_01_2021 FOR VALUES FROM ('2021-01-01 00:00:00') TO ('2022-01-01 00:00:00');
+ERROR:  cannot attach a global temporary relation as partition of permanent relation "regular_partition01"
+--
+-- test unsupported inherit table
+--
+create table 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 inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent);
+ERROR:  Not support global temporary partition table or inherit table.
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent_global_temp) on commit delete rows;
+ERROR:  Not support global temporary partition table or inherit table.
+--
+-- test DDL on global temp table
+--
+create index idx_gtt_test_alter_b on gtt_test_alter (b);
+insert into gtt_test_alter values('test');
+alter table gtt_test_alter alter b type varchar;
+create index gtt_idx_1 on gtt_test_createindex using gin (c0);
+create index gtt_idx_2 on gtt_test_createindex using gist (c0);
+create index gtt_idx_3 on gtt_test_createindex using hash (c2);
+alter table gtt_test_rename rename to gtt_test_new;
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+create index CONCURRENTLY idx_b on gtt_on_commit_default (b);
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+insert into gtt_on_commit_default values(1,'test');
+create global temp table gtt_test_alter1 (a int primary key,b text);
+alter table gtt_test_alter1 alter a type varchar;
+-- should fail
+alter table gtt_on_commit_default alter a type varchar;
+ERROR:  Only support alter global temporary table in an empty context.
+HINT:  Please create a new connection and execute ALTER TABLE on the new connection.
+-- should fail
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+ERROR:  not support cluster global temporary table
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- should fail
+REINDEX (TABLESPACE pg_default) TABLE gtt_test_createindex;
+ERROR:  The tablespace of global temporary table can not be changed
+-- should fail
+REINDEX (TABLESPACE pg_default) INDEX gtt_idx_1;
+ERROR:  The tablespace of global temporary table can not be changed
+-- should fail
+alter table gtt_on_commit_default set ( on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table
+-- should fail
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temp because they do not have storage
+create table foo();
+-- should fail
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
+-- should fail
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit drop;
+ERROR:  global temporary table not support on commit drop clause
+-- should fail
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  could not create global temporary table with on commit and with clause at same time
+-- should fail
+CREATE MATERIALIZED VIEW mv_gtt_on_commit_default as select * from gtt_on_commit_default;
+ERROR:  materialized views must not use global temporary tables or views
+-- test create table as select
+CREATE GLOBAL TEMPORARY TABLE test_create_table_as AS SELECT 1 AS a;
+-- test copy stmt
+create global temp table gtt_copytest (
+        c1 int,
+        "col with , comma" text,
+        "col with "" quote"  int);
+copy gtt_copytest from stdin csv header;
+select count(*) from gtt_copytest;
+ count 
+-------
+     2
+(1 row)
+
+--
+-- test foreign key dependencies for global temp table
+--
+CREATE global temp TABLE temp_products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+CREATE global temp TABLE temp_orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+)on commit delete rows;
+-- should fail
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+CREATE global temp TABLE temp_orders_2 (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products(product_no),
+    quantity integer
+);
+--should fail
+insert into temp_orders values(1,1,1);
+ERROR:  insert or update on table "temp_orders" violates foreign key constraint "temp_orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "temp_products".
+insert into temp_products values(1,'test',1.0);
+begin;
+insert into temp_orders values(1,1,1);
+commit;
+-- should 1 row
+select count(*) from temp_products;
+ count 
+-------
+     1
+(1 row)
+
+-- should 0 row
+select count(*) from temp_orders;
+ count 
+-------
+     0
+(1 row)
+
+--
+-- test sequence on global temp table
+--
+create global temp table global_temp_with_serial (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+-- support insert data to temp table in read only transaction
+insert into global_temp_with_serial (b) values(1);
+select * from global_temp_with_serial;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_delete(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_preserve(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table temp_table_with_sequence_oncommit_delete add c2 int default nextval('seq_1');
+alter table temp_table_with_sequence_oncommit_preserve add c2 int default nextval('seq_1');
+begin;
+insert into temp_table_with_sequence_oncommit_delete (c1)values(1);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(2);
+insert into temp_table_with_sequence_oncommit_delete (c1)values(3);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(4);
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  3 |  3
+(2 rows)
+
+commit;
+-- should 0 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_preserve order by c1;
+ c1 | c2 
+----+----
+  2 |  2
+  4 |  4
+(2 rows)
+
+--
+-- test statistics on temp table
+--
+create global temp table temp_table_test_statistics(a int);
+insert into temp_table_test_statistics values(generate_series(1,100000));
+create index idx_test_1 on temp_table_test_statistics (a);
+create index idx_test_2 on temp_table_test_statistics((a*10));
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+                   QUERY PLAN                   
+------------------------------------------------
+ Bitmap Heap Scan on temp_table_test_statistics
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_test_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+                   QUERY PLAN                   
+------------------------------------------------
+ Bitmap Heap Scan on temp_table_test_statistics
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_test_2
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+-- test statistic for whole row
+explain (costs off) select count(*) from temp_table_test_statistics group by temp_table_test_statistics;
+                  QUERY PLAN                  
+----------------------------------------------
+ HashAggregate
+   Group Key: temp_table_test_statistics.*
+   ->  Seq Scan on temp_table_test_statistics
+(3 rows)
+
+-- test statistic for system column
+explain (costs off) select count(*) from temp_table_test_statistics group by tableoid;
+                  QUERY PLAN                  
+----------------------------------------------
+ HashAggregate
+   Group Key: tableoid
+   ->  Seq Scan on temp_table_test_statistics
+(3 rows)
+
+analyze temp_table_test_statistics;
+-- indexscan by idx_test_1
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Index Only Scan using idx_test_1 on temp_table_test_statistics
+   Index Cond: (a = 200000)
+(2 rows)
+
+-- indexscan by idx_test_2
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+                        QUERY PLAN                         
+-----------------------------------------------------------
+ Index Scan using idx_test_2 on temp_table_test_statistics
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+explain (costs off) select count(*) from temp_table_test_statistics group by temp_table_test_statistics;
+                  QUERY PLAN                  
+----------------------------------------------
+ HashAggregate
+   Group Key: temp_table_test_statistics.*
+   ->  Seq Scan on temp_table_test_statistics
+(3 rows)
+
+explain (costs off) select count(*) from temp_table_test_statistics group by tableoid;
+                  QUERY PLAN                  
+----------------------------------------------
+ HashAggregate
+   Group Key: tableoid
+   ->  Seq Scan on temp_table_test_statistics
+(3 rows)
+
+--
+-- test temp table with toast table
+--
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |                0 |        483328 |           98304 |                 581632
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            32768 |                  |         32768 |               0 |                  32768
+(3 rows)
+
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |             8192 |        499712 |          106496 |                 606208
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            40960 |                  |         40960 |               0 |                  40960
+(3 rows)
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,10),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',10);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |             8192 |                0 |         16384 |           32768 |                  49152
+ idx_gtt_t_kenyon_1 |            16384 |                  |         16384 |               0 |                  16384
+ idx_gtt_t_kenyon_2 |            16384 |                  |         16384 |               0 |                  16384
+(3 rows)
+
+rollback;
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |             8192 |        499712 |          106496 |                 606208
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            40960 |                  |         40960 |               0 |                  40960
+(3 rows)
+
+-- test analyze/vacuum on global temp table
+ANALYZE gtt_t_kenyon;
+VACUUM gtt_t_kenyon;
+--
+-- test global temp table system view
+--
+create global temp table temp_table_test_systemview(a int primary key, b text) on commit PRESERVE rows;
+-- should empty, storage not initialized
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+ tablename 
+-----------
+(0 rows)
+
+-- should empty, storage not initialized
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into temp_table_test_systemview values(generate_series(1,10000),'test');
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+         tablename          
+----------------------------
+ temp_table_test_systemview
+(1 row)
+
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+       schemaname       |            tablename            | relpages | reltuples | relallvisible 
+------------------------+---------------------------------+----------+-----------+---------------
+ global_temporary_table | temp_table_test_systemview      |        0 |         0 |             0
+ global_temporary_table | temp_table_test_systemview_pkey |        1 |         0 |             0
+(2 rows)
+
+-- should empty
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+analyze temp_table_test_systemview;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+       schemaname       |            tablename            | relpages | reltuples | relallvisible 
+------------------------+---------------------------------+----------+-----------+---------------
+ global_temporary_table | temp_table_test_systemview      |       55 |     10000 |             0
+ global_temporary_table | temp_table_test_systemview_pkey |       30 |     10000 |             0
+(2 rows)
+
+-- get data after analyze;
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+       schemaname       |         tablename          | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------------------+----------------------------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ global_temporary_table | temp_table_test_systemview | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ global_temporary_table | temp_table_test_systemview | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+-- get all object info in current schema
+select relname ,relkind, relpersistence, reloptions from pg_class c, pg_namespace n where c.relnamespace = n.oid and n.nspname = 'global_temporary_table' order by relkind,relpersistence,relname;
+                     relname                     | relkind | relpersistence |          reloptions           
+-------------------------------------------------+---------+----------------+-------------------------------
+ global_temp_partition_01_2021_id_seq            | S       | g              | {on_commit_delete_rows=false}
+ global_temp_with_serial_a_seq                   | S       | g              | {on_commit_delete_rows=false}
+ regular_partition01_id_seq                      | S       | p              | 
+ regular_partition_01_2019_id_seq                | S       | p              | 
+ seq_1                                           | S       | p              | 
+ gtt_idx_1                                       | i       | g              | 
+ gtt_idx_2                                       | i       | g              | 
+ gtt_idx_3                                       | i       | g              | 
+ gtt_on_commit_default_pkey                      | i       | g              | 
+ gtt_on_commit_delete_pkey                       | i       | g              | 
+ gtt_on_commit_preserve_pkey                     | i       | g              | 
+ gtt_test_alter1_pkey                            | i       | g              | 
+ gtt_test_rename_pkey                            | i       | g              | 
+ idx_b                                           | i       | g              | 
+ idx_gtt_t_kenyon_1                              | i       | g              | 
+ idx_gtt_t_kenyon_2                              | i       | g              | 
+ idx_gtt_test_alter_b                            | i       | g              | 
+ idx_test_1                                      | i       | g              | 
+ idx_test_2                                      | i       | g              | 
+ products_pkey                                   | i       | g              | 
+ temp_orders_2_pkey                              | i       | g              | 
+ temp_orders_pkey                                | i       | g              | 
+ temp_products_pkey                              | i       | g              | 
+ temp_table_test_systemview_pkey                 | i       | g              | 
+ temp_table_with_sequence_oncommit_delete_pkey   | i       | g              | 
+ temp_table_with_sequence_oncommit_preserve_pkey | i       | g              | 
+ regular_partition01                             | p       | p              | 
+ global_temp_partition_01_2021                   | r       | g              | {on_commit_delete_rows=true}
+ global_temp_with_serial                         | r       | g              | {on_commit_delete_rows=false}
+ gtt_copytest                                    | r       | g              | {on_commit_delete_rows=false}
+ gtt_on_commit_default                           | r       | g              | {on_commit_delete_rows=false}
+ gtt_on_commit_delete                            | r       | g              | {on_commit_delete_rows=true}
+ gtt_on_commit_delete2                           | r       | g              | {on_commit_delete_rows=true}
+ gtt_on_commit_preserve                          | r       | g              | {on_commit_delete_rows=false}
+ gtt_t_kenyon                                    | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_alter                                  | r       | g              | {on_commit_delete_rows=true}
+ gtt_test_alter1                                 | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_createindex                            | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_new                                    | r       | g              | {on_commit_delete_rows=false}
+ inherits_parent_global_temp                     | r       | g              | {on_commit_delete_rows=true}
+ products                                        | r       | g              | {on_commit_delete_rows=false}
+ temp_orders                                     | r       | g              | {on_commit_delete_rows=true}
+ temp_orders_2                                   | r       | g              | {on_commit_delete_rows=false}
+ temp_products                                   | r       | g              | {on_commit_delete_rows=false}
+ temp_table_test_statistics                      | r       | g              | {on_commit_delete_rows=false}
+ temp_table_test_systemview                      | r       | g              | {on_commit_delete_rows=false}
+ temp_table_with_sequence_oncommit_delete        | r       | g              | {on_commit_delete_rows=true}
+ temp_table_with_sequence_oncommit_preserve      | r       | g              | {on_commit_delete_rows=false}
+ test_create_table_as                            | r       | g              | {on_commit_delete_rows=false}
+ foo                                             | r       | p              | 
+ inherits_parent                                 | r       | p              | 
+ regular_partition_01_2019                       | r       | p              | 
+(52 rows)
+
+reset search_path;
+drop schema global_temporary_table cascade;
+NOTICE:  drop cascades to 27 other objects
+DETAIL:  drop cascades to table global_temporary_table.gtt_on_commit_default
+drop cascades to table global_temporary_table.gtt_on_commit_delete
+drop cascades to table global_temporary_table.gtt_on_commit_delete2
+drop cascades to table global_temporary_table.gtt_on_commit_preserve
+drop cascades to table global_temporary_table.gtt_test_new
+drop cascades to table global_temporary_table.gtt_test_createindex
+drop cascades to table global_temporary_table.gtt_test_alter
+drop cascades to table global_temporary_table.regular_partition_01_2019
+drop cascades to table global_temporary_table.regular_partition01
+drop cascades to table global_temporary_table.global_temp_partition_01_2021
+drop cascades to table global_temporary_table.inherits_parent
+drop cascades to table global_temporary_table.inherits_parent_global_temp
+drop cascades to table global_temporary_table.gtt_test_alter1
+drop cascades to table global_temporary_table.foo
+drop cascades to table global_temporary_table.test_create_table_as
+drop cascades to table global_temporary_table.gtt_copytest
+drop cascades to table global_temporary_table.temp_products
+drop cascades to table global_temporary_table.products
+drop cascades to table global_temporary_table.temp_orders
+drop cascades to table global_temporary_table.temp_orders_2
+drop cascades to table global_temporary_table.global_temp_with_serial
+drop cascades to sequence global_temporary_table.seq_1
+drop cascades to table global_temporary_table.temp_table_with_sequence_oncommit_delete
+drop cascades to table global_temporary_table.temp_table_with_sequence_oncommit_preserve
+drop cascades to table global_temporary_table.temp_table_test_statistics
+drop cascades to table global_temporary_table.gtt_t_kenyon
+drop cascades to table global_temporary_table.temp_table_test_systemview
+-- should empty
+select * from pg_list_gtt_relfrozenxids();
+ pid | relfrozenxid 
+-----+--------------
+(0 rows)
+
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index b58b062b10d..8469c345bf4 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1359,6 +1359,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 5b0c73d7e37..40f4fc5ca10 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -134,3 +134,6 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: global_temporary_table
diff --git a/src/test/regress/sql/global_temporary_table.sql b/src/test/regress/sql/global_temporary_table.sql
new file mode 100644
index 00000000000..51cd9bbbc0c
--- /dev/null
+++ b/src/test/regress/sql/global_temporary_table.sql
@@ -0,0 +1,315 @@
+--
+-- GLobal emparary table test case 
+--
+
+CREATE SCHEMA IF NOT EXISTS global_temporary_table;
+set search_path=global_temporary_table,sys;
+
+--
+--  test create global temp table basic syntax
+--
+create global temp table gtt_on_commit_default(a int primary key, b text);
+create global temp table gtt_on_commit_delete(a int primary key, b text) on commit delete rows;
+create global temp table gtt_on_commit_delete2(n int) with (on_commit_delete_rows='true');
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit PRESERVE rows;
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt_test_createindex(c0 tsvector,c1 varchar(100), c2 int);
+create global temp table gtt_test_alter(b text) with(on_commit_delete_rows=true);
+--
+-- test DML on global temp table
+--
+
+-- update empty temp table
+update gtt_on_commit_delete set b ='test';
+begin;
+insert into gtt_on_commit_delete values (1);
+update gtt_on_commit_delete set b ='test';
+-- should 1 row
+select * from gtt_on_commit_delete;
+commit;
+-- data delete after transaction commit
+-- should 0 row
+select * from gtt_on_commit_delete;
+
+-- update empty temp table
+update gtt_on_commit_preserve set b ='test';
+insert into gtt_on_commit_preserve values (2);
+begin;
+insert into gtt_on_commit_preserve values (3);
+update gtt_on_commit_preserve set b ='test';
+-- should 2 row
+select * from gtt_on_commit_preserve order by a;
+delete from gtt_on_commit_preserve where a=2;
+commit;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+
+begin;
+insert into gtt_on_commit_preserve values (4);
+-- temp table support truncate;
+truncate gtt_on_commit_preserve;
+select * from gtt_on_commit_preserve order by a;
+rollback;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+
+--
+-- test unsupported global temp partition table
+--
+
+-- should fail
+CREATE global temp TABLE global_temp_partition_01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+on commit delete rows;
+
+CREATE TABLE regular_partition_01_2019 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+);
+
+CREATE TABLE regular_partition01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time);
+
+-- should fail
+CREATE global temp TABLE temp_partition01_2018
+PARTITION OF regular_partition01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE global_temp_partition_01_2021 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+)on commit delete rows;
+
+-- should fail
+ALTER TABLE regular_partition01 ATTACH PARTITION global_temp_partition_01_2021 FOR VALUES FROM ('2021-01-01 00:00:00') TO ('2022-01-01 00:00:00');
+
+--
+-- test unsupported inherit table
+--
+create table 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 inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent);
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent_global_temp) on commit delete rows;
+
+--
+-- test DDL on global temp table
+--
+create index idx_gtt_test_alter_b on gtt_test_alter (b);
+insert into gtt_test_alter values('test');
+alter table gtt_test_alter alter b type varchar;
+create index gtt_idx_1 on gtt_test_createindex using gin (c0);
+create index gtt_idx_2 on gtt_test_createindex using gist (c0);
+create index gtt_idx_3 on gtt_test_createindex using hash (c2);
+alter table gtt_test_rename rename to gtt_test_new;
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+create index CONCURRENTLY idx_b on gtt_on_commit_default (b);
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+insert into gtt_on_commit_default values(1,'test');
+create global temp table gtt_test_alter1 (a int primary key,b text);
+alter table gtt_test_alter1 alter a type varchar;
+-- should fail
+alter table gtt_on_commit_default alter a type varchar;
+-- should fail
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) on commit delete rows;
+-- should fail
+REINDEX (TABLESPACE pg_default) TABLE gtt_test_createindex;
+-- should fail
+REINDEX (TABLESPACE pg_default) INDEX gtt_idx_1;
+-- should fail
+alter table gtt_on_commit_default set ( on_commit_delete_rows='true');
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) with(on_commit_delete_rows=true);
+-- should fail
+create or replace global temp view gtt_v as select 5;
+create table foo();
+-- should fail
+alter table foo set (on_commit_delete_rows='true');
+-- should fail
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit drop;
+-- should fail
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+-- should fail
+CREATE MATERIALIZED VIEW mv_gtt_on_commit_default as select * from gtt_on_commit_default;
+-- test create table as select
+CREATE GLOBAL TEMPORARY TABLE test_create_table_as AS SELECT 1 AS a;
+-- test copy stmt
+create global temp table gtt_copytest (
+        c1 int,
+        "col with , comma" text,
+        "col with "" quote"  int);
+
+copy gtt_copytest from stdin csv header;
+this is just a line full of junk that would error out if parsed
+1,a,1
+2,b,2
+\.
+select count(*) from gtt_copytest;
+
+--
+-- test foreign key dependencies for global temp table
+--
+CREATE global temp TABLE temp_products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+CREATE global temp TABLE temp_orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+)on commit delete rows;
+
+-- should fail
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+);
+
+CREATE global temp TABLE temp_orders_2 (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products(product_no),
+    quantity integer
+);
+
+--should fail
+insert into temp_orders values(1,1,1);
+
+insert into temp_products values(1,'test',1.0);
+begin;
+insert into temp_orders values(1,1,1);
+commit;
+-- should 1 row
+select count(*) from temp_products;
+-- should 0 row
+select count(*) from temp_orders;
+
+--
+-- test sequence on global temp table
+--
+create global temp table global_temp_with_serial (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+-- support insert data to temp table in read only transaction
+insert into global_temp_with_serial (b) values(1);
+select * from global_temp_with_serial;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_delete(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_preserve(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table temp_table_with_sequence_oncommit_delete add c2 int default nextval('seq_1');
+alter table temp_table_with_sequence_oncommit_preserve add c2 int default nextval('seq_1');
+begin;
+insert into temp_table_with_sequence_oncommit_delete (c1)values(1);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(2);
+insert into temp_table_with_sequence_oncommit_delete (c1)values(3);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(4);
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+commit;
+-- should 0 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_preserve order by c1;
+
+--
+-- test statistics on temp table
+--
+create global temp table temp_table_test_statistics(a int);
+insert into temp_table_test_statistics values(generate_series(1,100000));
+create index idx_test_1 on temp_table_test_statistics (a);
+create index idx_test_2 on temp_table_test_statistics((a*10));
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+-- test statistic for whole row
+explain (costs off) select count(*) from temp_table_test_statistics group by temp_table_test_statistics;
+-- test statistic for system column
+explain (costs off) select count(*) from temp_table_test_statistics group by tableoid;
+analyze temp_table_test_statistics;
+-- indexscan by idx_test_1
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+-- indexscan by idx_test_2
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+explain (costs off) select count(*) from temp_table_test_statistics group by temp_table_test_statistics;
+explain (costs off) select count(*) from temp_table_test_statistics group by tableoid;
+
+--
+-- test temp table with toast table
+--
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,10),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',10);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+rollback;
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+
+-- test analyze/vacuum on global temp table
+ANALYZE gtt_t_kenyon;
+VACUUM gtt_t_kenyon;
+
+--
+-- test global temp table system view
+--
+create global temp table temp_table_test_systemview(a int primary key, b text) on commit PRESERVE rows;
+-- should empty, storage not initialized
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+-- should empty, storage not initialized
+select count(*) from pg_list_gtt_relfrozenxids();
+insert into temp_table_test_systemview values(generate_series(1,10000),'test');
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+select count(*) from pg_list_gtt_relfrozenxids();
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+-- should empty
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+analyze temp_table_test_systemview;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+-- get data after analyze;
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+
+-- get all object info in current schema
+select relname ,relkind, relpersistence, reloptions from pg_class c, pg_namespace n where c.relnamespace = n.oid and n.nspname = 'global_temporary_table' order by relkind,relpersistence,relname;
+
+reset search_path;
+drop schema global_temporary_table cascade;
+-- should empty
+select * from pg_list_gtt_relfrozenxids();
+
-- 
2.32.0 (Apple Git-132)

0003-gtt-v64-implementation.patchapplication/octet-stream; name=0003-gtt-v64-implementation.patchDownload
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index a23d0182fc0..74a28d7de8d 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -18,6 +18,7 @@
 #include "access/toast_internals.h"
 #include "access/visibilitymap.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "funcapi.h"
 #include "miscadmin.h"
 #include "storage/bufmgr.h"
@@ -226,6 +227,8 @@ verify_heapam(PG_FUNCTION_ARGS)
 	BlockNumber last_block;
 	BlockNumber nblocks;
 	const char *skip;
+	TransactionId	relfrozenxid = InvalidTransactionId;
+	MultiXactId		relminmxid = InvalidMultiXactId;
 
 	/* Check to see if caller supports us returning a tuplestore */
 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -340,6 +343,13 @@ verify_heapam(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(ctx.rel) &&
+		!gtt_storage_attached(RelationGetRelid(ctx.rel)))
+	{
+		relation_close(ctx.rel, AccessShareLock);
+		PG_RETURN_NULL();
+	}
+
 	/* Early exit if the relation is empty */
 	nblocks = RelationGetNumberOfBlocks(ctx.rel);
 	if (!nblocks)
@@ -407,9 +417,25 @@ verify_heapam(PG_FUNCTION_ARGS)
 
 	update_cached_xid_range(&ctx);
 	update_cached_mxid_range(&ctx);
-	ctx.relfrozenxid = ctx.rel->rd_rel->relfrozenxid;
+
+	if (RELATION_IS_GLOBAL_TEMP(ctx.rel))
+	{
+		get_gtt_relstats(RelationGetRelid(ctx.rel),
+						NULL,
+						NULL,
+						NULL,
+						&relfrozenxid,
+						&relminmxid);
+	}
+	else
+	{
+		relfrozenxid = ctx.rel->rd_rel->relfrozenxid;
+		relminmxid = ctx.rel->rd_rel->relminmxid;
+	}
+
+	ctx.relfrozenxid = relfrozenxid;
 	ctx.relfrozenfxid = FullTransactionIdFromXidAndCtx(ctx.relfrozenxid, &ctx);
-	ctx.relminmxid = ctx.rel->rd_rel->relminmxid;
+	ctx.relminmxid = relminmxid;
 
 	if (TransactionIdIsNormal(ctx.relfrozenxid))
 		ctx.oldest_xid = ctx.relfrozenxid;
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index b5602f53233..21b2d2a9527 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -159,6 +159,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * In order to avoid consistency problems, the global temporary table
+	 * uses ShareUpdateExclusiveLock.
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temporary table on commit options",
+			RELOPT_KIND_HEAP,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},
 	/* list terminator */
 	{{NULL}}
 };
@@ -1834,6 +1847,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 94dbabc1988..f31bb2f318c 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1024,7 +1024,7 @@ gistproperty(Oid index_oid, int attno,
 XLogRecPtr
 gistGetFakeLSN(Relation rel)
 {
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	if (RELATION_IS_TEMP(rel))
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 81c7da7ec69..febc98e1b9c 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -152,7 +152,7 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	if (!RELATION_IS_TEMP(index))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 0b4a46b31ba..0d32b5d2d93 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -52,6 +52,7 @@
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/atomics.h"
@@ -5825,6 +5826,19 @@ heap_abort_speculative(Relation relation, ItemPointer tid)
 	BlockNumber block;
 	Buffer		buffer;
 	TransactionId prune_xid;
+	TransactionId relfrozenxid = InvalidTransactionId;
+
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		get_gtt_relstats(RelationGetRelid(relation),
+						NULL,
+						NULL,
+						NULL,
+						&relfrozenxid,
+						NULL);
+	}
+	else
+		relfrozenxid = relation->rd_rel->relfrozenxid;
 
 	Assert(ItemPointerIsValid(tid));
 
@@ -5877,8 +5891,8 @@ heap_abort_speculative(Relation relation, ItemPointer tid)
 	 * TransactionXmin, so there's no race here).
 	 */
 	Assert(TransactionIdIsValid(TransactionXmin));
-	if (TransactionIdPrecedes(TransactionXmin, relation->rd_rel->relfrozenxid))
-		prune_xid = relation->rd_rel->relfrozenxid;
+	if (TransactionIdPrecedes(TransactionXmin, relfrozenxid))
+		prune_xid = relfrozenxid;
 	else
 		prune_xid = TransactionXmin;
 	PageSetPrunable(page, prune_xid);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 9befe012a9e..26fce0c4b83 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -593,7 +593,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
@@ -645,7 +645,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(RelationGetSmgr(rel), dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index db6becfed54..f43ebc13e6e 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -47,6 +47,7 @@
 #include "access/xlog.h"
 #include "catalog/index.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
@@ -496,6 +497,25 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 	TransactionId OldestXmin;
 	TransactionId FreezeLimit;
 	MultiXactId MultiXactCutoff;
+	TransactionId	relfrozenxid = InvalidTransactionId;
+	MultiXactId		relminmxid = InvalidMultiXactId;
+	double			reltuples = 0;
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		get_gtt_relstats(RelationGetRelid(rel),
+						NULL,
+						&reltuples,
+						NULL,
+						&relfrozenxid,
+						&relminmxid);
+	}
+	else
+	{
+		relfrozenxid = rel->rd_rel->relfrozenxid;
+		relminmxid = rel->rd_rel->relminmxid;
+		reltuples = rel->rd_rel->reltuples;
+	}
 
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
@@ -531,9 +551,9 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 	 * table's minimum MultiXactId is older than or equal to the requested
 	 * mxid full-table scan limit; or if DISABLE_PAGE_SKIPPING was specified.
 	 */
-	aggressive = TransactionIdPrecedesOrEquals(rel->rd_rel->relfrozenxid,
+	aggressive = TransactionIdPrecedesOrEquals(relfrozenxid,
 											   xidFullScanLimit);
-	aggressive |= MultiXactIdPrecedesOrEquals(rel->rd_rel->relminmxid,
+	aggressive |= MultiXactIdPrecedesOrEquals(relminmxid,
 											  mxactFullScanLimit);
 	if (params->options & VACOPT_DISABLE_PAGE_SKIPPING)
 		aggressive = true;
@@ -580,9 +600,9 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 	}
 
 	vacrel->bstrategy = bstrategy;
-	vacrel->relfrozenxid = rel->rd_rel->relfrozenxid;
-	vacrel->relminmxid = rel->rd_rel->relminmxid;
-	vacrel->old_live_tuples = rel->rd_rel->reltuples;
+	vacrel->relfrozenxid = relfrozenxid;
+	vacrel->relminmxid = relminmxid;
+	vacrel->old_live_tuples = reltuples;
 
 	/* Set cutoffs for entire VACUUM */
 	vacrel->OldestXmin = OldestXmin;
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 0aea476b8ce..1e8a5d6882c 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -677,6 +678,14 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		/*
+		 * If a global temporary table storage file is not initialized in the
+		 * current backend, its index does not have a root page, just returns 0.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 4e6efda97f3..ae12fd91a1b 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -44,6 +44,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index aa7d4d5456b..595cb03eb4a 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -504,6 +504,7 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 6780ec53b7c..e790bcbbc52 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -62,6 +62,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,
@@ -343,13 +345,26 @@ heap_create(const char *relname,
 	if (!RELKIND_HAS_TABLESPACE(relkind))
 		reltablespace = InvalidOid;
 
+	/* For global temporary table, even if the storage is not initialized,
+	 * the relfilenode needs to be generated and put into the catalog.
+	 */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		create_storage = false;
+		if (OidIsValid(relfilenode))
+			elog(ERROR, "global temporary table can not reuse an existing relfilenode");
+
+		relfilenode = relid;
+	}
 	/*
 	 * Decide whether to create storage. If caller passed a valid relfilenode,
 	 * storage is already created, so don't do it here.  Also don't create it
 	 * for relkinds without physical storage.
 	 */
-	if (!RELKIND_HAS_STORAGE(relkind) || OidIsValid(relfilenode))
+	else if (!RELKIND_HAS_STORAGE(relkind) || OidIsValid(relfilenode))
+	{
 		create_storage = false;
+	}
 	else
 	{
 		create_storage = true;
@@ -397,7 +412,7 @@ heap_create(const char *relname,
 											relpersistence,
 											relfrozenxid, relminmxid);
 		else if (RELKIND_HAS_STORAGE(rel->rd_rel->relkind))
-			RelationCreateStorage(rel->rd_node, relpersistence);
+			RelationCreateStorage(rel->rd_node, relpersistence, rel);
 		else
 			Assert(false);
 	}
@@ -407,7 +422,8 @@ heap_create(const char *relname,
 	 * protected by the existence of a physical file; but for relations with
 	 * no files, add a pg_shdepend entry to account for that.
 	 */
-	if (!create_storage && reltablespace != InvalidOid)
+	if (!create_storage && reltablespace != InvalidOid &&
+		relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
 		recordDependencyOnTablespace(RelationRelationId, relid,
 									 reltablespace);
 
@@ -964,6 +980,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -989,8 +1006,21 @@ AddNewRelationTuple(Relation pg_class_desc,
 		new_rel_reltup->reltuples = 1;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/*
+	 * The transaction information of the global temporary table is stored
+	 * in the local hash table, not in catalog.
+	 */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1371,6 +1401,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1455,8 +1486,9 @@ heap_create_with_catalog(const char *relname,
 	/*
 	 * If there's a special on-commit action, remember it
 	 */
-	if (oncommit != ONCOMMIT_NOOP)
-		register_on_commit_action(relid, oncommit);
+	if (oncommit != ONCOMMIT_NOOP &&
+		relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+		register_on_commit_action(relid, oncommit, false);
 
 	/*
 	 * ok, the relation has been cataloged, so close our relations and return
@@ -1953,6 +1985,19 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/*
+	 * Only when other sessions are not using this Global temporary table,
+	 * is it allowed to DROP it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot drop global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3239,7 +3284,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3251,7 +3296,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3287,7 +3332,7 @@ RelationTruncateIndexes(Relation heapRelation)
  * ON COMMIT truncation of temporary tables, where it doesn't matter.
  */
 void
-heap_truncate(List *relids)
+heap_truncate(List *relids, bool is_global_temp)
 {
 	List	   *relations = NIL;
 	ListCell   *cell;
@@ -3297,8 +3342,23 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode;
+
+		/*
+		 * Truncate global temporary table only clears local data,
+		 * so only low-level locks need to be held.
+		 */
+		if (is_global_temp)
+		{
+			if (!gtt_storage_attached(rid))
+				continue;
 
-		rel = table_open(rid, AccessExclusiveLock);
+			lockmode = RowExclusiveLock;
+		}
+		else
+			lockmode = AccessExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3331,6 +3391,7 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3339,23 +3400,39 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	/*
+	 * Truncate GTT only clears local data, so only low-level locks
+	 * need to be held.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		lockmode = AccessShareLock;
+	else
+		lockmode = AccessExclusiveLock;
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	/*
+	 * After the data is cleaned up on the GTT, the transaction information
+	 * for the data(stored in local hash table) is also need reset.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(RelationGetRelid(rel), 0, 0, 0, RecentXmin, GetOldestMultiXactId());
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 1757cd3446f..946e391d92d 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -54,6 +54,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"
@@ -734,6 +735,22 @@ index_create(Relation heapRelation,
 	TransactionId relfrozenxid;
 	MultiXactId relminmxid;
 
+	/* For global temporary table only */
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* No support create index on global temporary table with concurrent mode */
+		Assert(!concurrent);
+
+		/*
+		 * For the case that some backend is applied relcache message to create
+		 * an index on a global temporary table, if this table in the current
+		 * backend are not initialized, the creation of index storage on the
+		 * table are also skipped.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/* constraint flags can only be set when a constraint is requested */
 	Assert((constr_flags == 0) ||
 		   ((flags & INDEX_CREATE_ADD_CONSTRAINT) != 0));
@@ -2119,7 +2136,7 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 * lock (see comments in RemoveRelations), and a non-concurrent DROP is
 	 * more efficient.
 	 */
-	Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+	Assert(!RelpersistenceTsTemp(get_rel_persistence(indexId)) ||
 		   (!concurrent && !concurrent_lock_mode));
 
 	/*
@@ -2151,6 +2168,21 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/*
+	 * Allow to drop index on global temporary table when only current
+	 * backend use it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation) &&
+		is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+	{
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("cannot drop index %s on global temporary table %s",
+					RelationGetRelationName(userIndexRelation), RelationGetRelationName(userHeapRelation)),
+					errdetail("Because the index is created on the global temporary table and other backend attached it."),
+					errhint("Please try detach all sessions using this temporary table and try again.")));
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2759,6 +2791,7 @@ index_update_stats(Relation rel,
 	HeapTuple	tuple;
 	Form_pg_class rd_rel;
 	bool		dirty;
+	bool		is_gtt = RELATION_IS_GLOBAL_TEMP(rel);
 
 	/*
 	 * We always update the pg_class row using a non-transactional,
@@ -2853,20 +2886,37 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
+		/* For global temporary table */
+		if (is_gtt)
 		{
-			rd_rel->relpages = (int32) relpages;
-			dirty = true;
+			/* Update GTT'statistics into local relcache */
+			rel->rd_rel->relpages = (int32) relpages;
+			rel->rd_rel->reltuples = (float4) reltuples;
+			rel->rd_rel->relallvisible = (int32) relallvisible;
+
+			/* Update GTT'statistics into local hashtable */
+			up_gtt_relstats(RelationGetRelid(rel), relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
 		}
-		if (rd_rel->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;
+			}
 		}
 	}
 
@@ -2979,6 +3029,26 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, progress_index, progress_vals);
 	}
 
+	/* For build index on global temporary table */
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		/*
+		 * If the storage for the index in this session is not initialized,
+		 * it needs to be created.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			/* Before create init storage, fix the local Relcache first */
+			force_enable_gtt_index(indexRelation);
+
+			Assert(gtt_storage_attached(RelationGetRelid(heapRelation)));
+
+			/* Init storage for index */
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3520,6 +3590,8 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	PGRUsage	ru0;
 	bool		progress = ((params->options & REINDEXOPT_REPORT_PROGRESS) != 0);
 	bool		set_tablespace = false;
+	LOCKMODE	lockmode_on_heap = ShareLock;
+	LOCKMODE	lockmode_on_index = AccessExclusiveLock;
 
 	pg_rusage_init(&ru0);
 
@@ -3533,10 +3605,34 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	if (!OidIsValid(heapId))
 		return;
 
+	/*
+	 * For reindex on global temporary table, If the storage for the index
+	 * in current backend is not initialized, nothing is done.
+	 */
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		if (OidIsValid(params->tablespaceOid))
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("The tablespace of global temporary table can not be changed")));
+
+		if (!gtt_storage_attached(indexId))
+		{
+			/* Suppress use of the target index while rebuilding it */
+			SetReindexProcessing(heapId, indexId);
+			/* Re-allow use of target index */
+			ResetReindexProcessing();
+			return;
+		}
+
+		lockmode_on_heap = AccessShareLock;
+		lockmode_on_index = AccessShareLock;
+	}
+
 	if ((params->options & REINDEXOPT_MISSING_OK) != 0)
-		heapRelation = try_table_open(heapId, ShareLock);
+		heapRelation = try_table_open(heapId, lockmode_on_heap);
 	else
-		heapRelation = table_open(heapId, ShareLock);
+		heapRelation = table_open(heapId, lockmode_on_heap);
 
 	/* if relation is gone, leave */
 	if (!heapRelation)
@@ -3562,7 +3658,7 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	 * Open the target index relation and get an exclusive lock on it, to
 	 * ensure that no one else is touching this particular index.
 	 */
-	iRel = index_open(indexId, AccessExclusiveLock);
+	iRel = index_open(indexId, lockmode_on_index);
 
 	if (progress)
 		pgstat_progress_update_param(PROGRESS_CREATEIDX_ACCESS_METHOD_OID,
@@ -3813,6 +3909,12 @@ reindex_relation(Oid relid, int flags, ReindexParams *params)
 	bool		result;
 	ListCell   *indexId;
 	int			i;
+	LOCKMODE	lockmode;
+
+	if (flags & REINDEX_REL_PROCESS_GLOBAL_TEMP)
+		lockmode = AccessShareLock;
+	else
+		lockmode = ShareLock;
 
 	/*
 	 * Open and lock the relation.  ShareLock is sufficient since we only need
@@ -3820,9 +3922,9 @@ reindex_relation(Oid relid, int flags, ReindexParams *params)
 	 * should match ReindexTable().
 	 */
 	if ((params->options & REINDEXOPT_MISSING_OK) != 0)
-		rel = try_table_open(relid, ShareLock);
+		rel = try_table_open(relid, lockmode);
 	else
-		rel = table_open(relid, ShareLock);
+		rel = table_open(relid, lockmode);
 
 	/* if relation is gone, leave */
 	if (!rel)
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 4de8400fd0f..fe3fcc712cb 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -656,6 +656,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/* Do not allow create global temporary table in temporary schemas */
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp table in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index c5ad28d71fe..707068a6fd8 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,6 +27,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
@@ -61,6 +62,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			temprelOid;			/* InvalidOid if not a global temporary rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -115,7 +117,7 @@ AddPendingSync(const RelFileNode *rnode)
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -126,7 +128,12 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/*
+		 * Global temporary table and local temporary table use same
+		 * design on storage module.
+		 */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -154,6 +161,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
@@ -165,6 +173,21 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		AddPendingSync(&rnode);
 	}
 
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		Assert(rel && RELATION_IS_GLOBAL_TEMP(rel));
+
+		/*
+		 * Remember the reloid of global temporary table, which is used for
+		 * transaction commit or rollback.
+		 * see smgrDoPendingDeletes.
+		 */
+		pending->temprelOid = RelationGetRelid(rel);
+
+		/* Remember global temporary table storage info to localhash */
+		remember_gtt_storage_info(rnode, rel);
+	}
+
 	return srel;
 }
 
@@ -201,11 +224,20 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/*
+	 * Remember the reloid of global temporary table, which is used for
+	 * transaction commit or rollback.
+	 * see smgrDoPendingDeletes.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->temprelOid = RelationGetRelid(rel);
+
 	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
@@ -618,6 +650,7 @@ smgrDoPendingDeletes(bool isCommit)
 	int			nrels = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -647,14 +680,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->temprelOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -664,12 +701,21 @@ smgrDoPendingDeletes(bool isCommit)
 
 	if (nrels > 0)
 	{
+		int	i;
+
 		smgrdounlinkall(srels, nrels, false);
 
-		for (int i = 0; i < nrels; i++)
+		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* Delete global temporary table info in localhash */
+			if (gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 00000000000..d50e1fa9afe
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1509 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  The body implementation of Global Temparary table.
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *	  See src/backend/catalog/GTT_README for Global temparary table's
+ *	  requirements and design.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_info_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+
+int		vacuum_gtt_defer_check_age = 0;
+
+/*
+ * The Global temporary table's shared hash table data structure
+ */
+typedef struct gtt_ctl_data
+{
+	LWLock		lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+/* record this global temporary table in which backends are being used */
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+/*
+ * The Global temporary table's local hash table data structure
+ */
+/* Record the storage information and statistical information of the global temporary table */
+typedef struct
+{
+	Oid			relfilenode;
+	Oid			spcnode;
+
+	/* pg_class relstat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+
+	/* pg_statistic column stat */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_free_statistics(gtt_relfilenode *rnode);
+static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, Oid spcnode, bool missing_ok);
+static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok);
+static Bitmapset *copy_active_gtt_bitmap(Oid relid);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+/*
+ * Calculate shared hash table entry size for GTT.
+ */
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	/* hash entry header size */
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	/*
+	 * hash entry data size
+	 * this is a bitmap in shared memory, each backend have a bit.
+	 */
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+/*
+ * Calculate shared hash table max size for GTT.
+ */
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	/* shared hash header size */
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	/* hash entry size */
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	/* max size */
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+/*
+ * Initialization shared hash table for GTT.
+ */
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&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();
+	}
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(gtt_fnode);
+	info.entrysize = action_gtt_shared_hash_entry_size();
+	active_gtt_shared_hash =
+		ShmemInitHash("active gtt shared hash",
+						gtt_shared_ctl->max_entry,
+						gtt_shared_ctl->max_entry,
+						&info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+/*
+ * Record GTT relid to shared hash table, which means that current backend is using this GTT.
+ */
+static void
+gtt_storage_checkin(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			found;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *)&(fnode), 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_global_temporary_table.")));
+	}
+
+	if (!found)
+	{
+		int			wordnum;
+
+		/* init bitmap */
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	/* record itself in bitmap */
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+/*
+ * Remove the GTT relid record from the shared hash table which means that current backend is
+ * not use this GTT.
+ */
+static void
+gtt_storage_checkout(Oid relid, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		if (isCommit)
+			elog(WARNING, "relid %u not exist in gtt shared hash when drop local storage", relid);
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+	/* remove itself from bitmap */
+	bms_del_member(entry->map, MyBackendId);
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return;
+}
+
+/*
+ * Gets usage information for a GTT from shared hash table.
+ * The information is in the form of bitmap.
+ * Quickly copy the entire bitmap from shared memory and return it.
+ * that to avoid holding locks for a long time.
+ */
+static Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	Bitmapset		*map_copy = NULL;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry)
+	{
+		Assert(entry->map);
+		/* copy the entire bitmap */
+		if (!bms_is_empty(entry->map))
+			map_copy = bms_copy(entry->map);
+	}
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return map_copy;
+}
+
+/*
+ * Check if there are other backends using this GTT besides the current backend.
+ */
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			in_use = false;
+	int			num_use = 0;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		return false;
+	}
+
+	Assert(entry->map);
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+	/* how many backend are using this GTT */
+	num_use = bms_num_members(entry->map);
+	if (num_use == 0)
+		in_use = false;
+	else if (num_use == 1)
+	{
+		/* check if this is itself */
+		if(bms_is_member(MyBackendId, entry->map))
+			in_use = false;
+		else
+			in_use = true;
+	}
+	else
+		in_use = true;
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return in_use;
+}
+
+/*
+ * Record GTT information to local hash.
+ * They include GTT storage info, transaction info and statistical info.
+ */
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+	gtt_local_hash_entry		*entry;
+	MemoryContext			oldcontext;
+	gtt_relfilenode			*new_node = NULL;
+	Oid				relid = RelationGetRelid(rel);
+	int				natts = 0;
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	/* First time through: initialize the hash table */
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		HASHCTL		ctl;
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_info_context =
+			AllocSetContextCreate(CacheMemoryContext,
+								"gtt info context",
+								ALLOCSET_DEFAULT_SIZES);
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		ctl.hcxt = gtt_info_context;
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+	}
+
+	Assert(CacheMemoryContext);
+	Assert(gtt_info_context);
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	entry = gtt_search_by_relid(relid, true);
+	if (!entry)
+	{
+		bool		found = false;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash,
+						(void *) &relid, HASH_ENTER, &found);
+
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			/* record the on commit clause */
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS, true);
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			gtt_storage_checkin(relid);
+		}
+	}
+
+	/* record storage info relstat columnstats and transaction info to relfilenode list */
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	new_node->natts = 0;
+	new_node->attnum = NULL;
+	new_node->att_stat_tups = NULL;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* init column stats structure */
+	natts = RelationGetNumberOfAttributes(rel);
+	new_node->attnum = palloc0(sizeof(int) * natts);
+	new_node->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+	new_node->natts = natts;
+
+	/* only heap rel or toast rel have transaction info */
+	if (entry->relkind == RELKIND_RELATION ||
+		entry->relkind == RELKIND_TOASTVALUE)
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+
+	/* Registration callbacks are used to trigger cleanup during process exit */
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+/*
+ * Remove GTT information from local hash when transaction commit/rollback.
+ */
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	gtt_relfilenode		*d_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+		return;
+	}
+
+	d_rnode = gtt_search_relfilenode(entry, rnode.relNode, rnode.spcNode, true);
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+		else
+		{
+			/* rollback transaction */
+			if (entry->relfilenode_list == NIL)
+			{
+				if (entry->relkind == RELKIND_RELATION ||
+					entry->relkind == RELKIND_SEQUENCE)
+					gtt_storage_checkout(relid, isCommit);
+
+				hash_search(gtt_storage_local_hash,
+						(void *) &(relid), HASH_REMOVE, NULL);
+			}
+
+			return;
+		}
+	}
+
+	/* Clean up transaction info from Local order list and MyProc */
+	if (entry->relkind == RELKIND_RELATION ||
+		entry->relkind == RELKIND_TOASTVALUE)
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+
+		/* this is valid relfrozenxid */
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	/* delete relfilenode from rel entry */
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	gtt_free_statistics(d_rnode);
+
+	if (entry->relfilenode_list == NIL)
+	{
+		/* tell shared hash that current backend will no longer use this GTT */
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(relid, isCommit);
+
+		hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_REMOVE, NULL);
+	}
+
+	return;
+}
+
+/*
+ * Check if current backend is using this GTT.
+ */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool			found = false;
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (!OidIsValid(relid))
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry)
+		found = true;
+
+	return found;
+}
+
+/*
+ * When backend exit, bulk cleaning all GTT storage and local buffer of this backend.
+ */
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS			status;
+	gtt_local_hash_entry	*entry;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	/* Need to ensure we have a usable transaction. */
+	AbortOutOfAnyTransaction();
+
+	/* Search all relfilenode for GTT in current backend */
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel[1];
+			RelFileNode		rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel[0] = smgropen(rnode, MyBackendId);
+			smgrdounlinkall(srel, 1, false);
+			smgrclose(srel[0]);
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(entry->relid, false);
+
+		hash_search(gtt_storage_local_hash, (void *) &(entry->relid), HASH_REMOVE, NULL);
+	}
+
+	/* set to global area */
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;
+
+	return;
+}
+
+/*
+ * Update GTT relstats(relpage/reltuple/relallvisible)
+ * to local hash.
+ */
+void
+up_gtt_relstats(Oid relid,
+					BlockNumber num_pages,
+					double num_tuples,
+					BlockNumber num_all_visible_pages,
+					TransactionId relfrozenxid,
+					TransactionId relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (!OidIsValid(relid))
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (num_pages > 0 &&
+		gtt_rnode->relpages != (int32)num_pages)
+		gtt_rnode->relpages = (int32)num_pages;
+
+	if (num_tuples > 0 &&
+		gtt_rnode->reltuples != (float4)num_tuples)
+		gtt_rnode->reltuples = (float4)num_tuples;
+
+	/* only heap contain transaction information and relallvisible */
+	if (entry->relkind == RELKIND_RELATION ||
+		entry->relkind == RELKIND_TOASTVALUE)
+	{
+		if (num_all_visible_pages > 0 &&
+			gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+		{
+			gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+		}
+
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNextTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			/* set to local order list */
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			/* set to global area */
+			set_gtt_session_relfrozenxid();
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search GTT relstats(relpage/reltuple/relallvisible)
+ * from local has.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update GTT info(definition is same as pg_statistic)
+ * to local hash.
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+	MemoryContext		oldcontext;
+	bool		found = false;
+	int			i = 0;
+
+	/* not support whole row or system column */
+	if (attnum <= 0)
+		return;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return;
+
+	Assert(entry->relid == reloid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	/* switch context to gtt_info_context for store tuple at heap_form_tuple */
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == 0)
+		{
+			Assert(gtt_rnode->att_stat_tups[i] == NULL);
+			gtt_rnode->attnum[i] = attnum;
+			gtt_rnode->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+			found = true;
+			break;
+		}
+		else if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			heap_freetuple(gtt_rnode->att_stat_tups[i]);
+			gtt_rnode->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+			found = true;
+			break;
+		}
+	}
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!found)
+		elog(WARNING, "analyze can not update relid %u column %d statistics after add or drop column, try truncate table first", reloid, attnum);
+
+	return;
+}
+
+/*
+ * Search GTT statistic info(definition is same as pg_statistic)
+ * from local hash.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	int			i = 0;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	/* not support whole row or system column */
+	if (attnum <= 0)
+		return NULL;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return NULL;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return NULL;
+
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			return gtt_rnode->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Insert a RelfrozenXID into the list and keep the list in order.
+ */
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int		i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Remove a RelfrozenXID from order list gtt_session_relfrozenxid_list.
+ */
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+/*
+ * Update of backend Level oldest relfrozenxid to MyProc.
+ * This makes each backend's oldest RelFrozenxID globally visible.
+ */
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list != NIL)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	if (MyProc->backend_gtt_frozenxid != gtt_frozenxid)
+		MyProc->backend_gtt_frozenxid = gtt_frozenxid;
+}
+
+/*
+ * Get GTT column level data statistics.
+ */
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate		*tupstore;
+	HeapTuple		tuple;
+	Relation		rel = NULL;
+	Oid			reloid = PG_GETARG_OID(0);
+	int			attnum = PG_GETARG_INT32(1);
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	if (!RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	/* get data from local hash */
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum		values[Natts_pg_statistic];
+		bool		isnull[Natts_pg_statistic];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, AccessShareLock);
+	relation_close(pg_tatistic, AccessShareLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get GTT table level data statistics.
+ */
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate	*tupstore;
+	TupleDesc	tupdesc;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid			reloid = PG_GETARG_OID(0);
+	Oid			relnode = 0;
+	BlockNumber		relpages = 0;
+	BlockNumber		relallvisible = 0;
+	uint32			relfrozenxid = 0;
+	uint32			relminmxid = 0;
+	double			reltuples = 0;
+	Relation		rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	if (!RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[6];
+		bool	isnull[6];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get a list of backend pids that are currently using this GTT.
+ */
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	PGPROC			*proc = NULL;
+	Bitmapset		*map = NULL;
+	Tuplestorestate		*tupstore;
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	HeapTuple		tuple;
+	Oid			reloid = PG_GETARG_OID(0);
+	Relation		rel = NULL;
+	int				backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	if (!RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	/* get data from share hash */
+	map = copy_active_gtt_bitmap(reloid);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			/* backendid map to process pid */
+			proc = BackendIdGetProc(backendid);
+			if (proc && proc->pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+				pid_t	pid = proc->pid;
+
+				memset(isnull, 0, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, AccessShareLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get backend level oldest relfrozenxid of each backend using GTT in current database.
+ */
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate		*tupstore;
+	int			*pids = NULL;
+	uint32			*xids = NULL;
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	HeapTuple		tuple;
+	int			num_xid = MaxBackends + 1;
+	int			i = 0;
+	int			j = 0;
+	uint32			oldest = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	if (pids == NULL || xids == NULL)
+		elog(ERROR, "out of memory");
+
+	/* Get backend level oldest relfrozenxid in all backend that in MyDatabaseId use GTT */
+	oldest = list_all_backend_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		/* save oldest relfrozenxid */
+		pids[i] = 0;
+		xids[i] = oldest;
+		i++;
+
+		/* save relfrozenxid for each session */
+		for (j = 0; j < i; j++)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, 0, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
+/*
+ * In order to build the GTT index, force enable GTT'index.
+ */
+void
+force_enable_gtt_index(Relation index)
+{
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(RelationGetRelid(index)));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+/*
+ * Fix the local state of the GTT's index.
+ */
+void
+gtt_fix_index_backend_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid heapOid = index->rd_index->indrelid;
+
+	/* Must be GTT */
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	/*
+	 * If this GTT is not initialized in the current backend,
+	 * its index status is temporarily set to invalid(local relcache).
+	 */
+	if (gtt_storage_attached(heapOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+/*
+ * During the SQL initialization of the executor (InitPlan)
+ * Initialize storage of GTT GTT'indexes and build empty index.
+ */
+void
+init_gtt_storage(CmdType operation, Relation relation)
+{
+	Oid			toastrelid;
+	List		*indexoidlist = NIL;
+	ListCell	*l;
+
+	if (!(operation == CMD_INSERT))
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	/* Each GTT is initialized once in each backend */
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	/* init heap storage */
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	indexoidlist = RelationGetIndexList(relation);
+	foreach(l, indexoidlist)
+	{
+		Oid			indexOid = lfirst_oid(l);
+		Relation	index = index_open(indexOid, RowExclusiveLock);
+		IndexInfo	*info = BuildDummyIndexInfo(index);
+
+		index_build(relation, index, info, true, false);
+		/* after build index, index re-enabled */
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+		index_close(index, NoLock);
+	}
+	list_free(indexoidlist);
+
+	/* rebuild index for global temp toast table */
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+
+		/* init index storage */
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid			indexId = lfirst_oid(indlist);
+			Relation	index = index_open(indexId, RowExclusiveLock);
+			IndexInfo	*info = BuildDummyIndexInfo(index);
+
+			/* build empty index */
+			index_build(toastrel, index, info, true, false);
+			Assert(index->rd_index->indisvalid);
+			Assert(index->rd_index->indislive);
+			Assert(index->rd_index->indisready);
+			index_close(index, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+/*
+ * Release the data structure memory used to store GTT storage info.
+ */
+static void
+gtt_free_statistics(gtt_relfilenode *rnode)
+{
+	int i;
+
+	Assert(rnode);
+
+	for (i = 0; i < rnode->natts; i++)
+	{
+		if (rnode->att_stat_tups[i])
+		{
+			heap_freetuple(rnode->att_stat_tups[i]);
+			rnode->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (rnode->attnum)
+		pfree(rnode->attnum);
+
+	if (rnode->att_stat_tups)
+		pfree(rnode->att_stat_tups);
+
+	pfree(rnode);
+
+	return;
+}
+
+/*
+ * Get the current relfilenode of this GTT.
+ */
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
+/*
+ * Get a relfilenode used by this GTT during the transaction life cycle.
+ */
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, Oid spcnode, bool missing_ok)
+{
+	gtt_relfilenode		*rnode = NULL;
+	ListCell		*lc;
+
+	Assert(entry);
+
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == relfilenode &&
+			gtt_rnode->spcnode == spcnode)
+		{
+			rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (!missing_ok && rnode == NULL)
+		elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+	return rnode;
+}
+
+/*
+ * Get one GTT info from local hash.
+ */
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry == NULL && !missing_ok)
+		elog(ERROR, "relid %u not found in local hash", relid);
+
+	return entry;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 61b515cdb85..dcf2bdbbde3 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index cd77907fc74..446a713a9b1 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -105,7 +106,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);
 
@@ -186,6 +187,17 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -602,14 +614,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]));
 		}
 
 		/*
@@ -1623,7 +1636,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;
@@ -1725,31 +1738,48 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/*
+		 * For global temporary table,
+		 * Update column statistic to localhash, not catalog.
+		 */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 9d22f648a84..668aaa8ad57 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/toasting.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
@@ -390,6 +391,22 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+	{
+		if (gtt_storage_attached(RelationGetRelid(OldHeap)))
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("not support cluster global temporary table")));
+
+		relation_close(OldHeap, AccessExclusiveLock);
+		pgstat_progress_end_command();
+		return;
+	}
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
@@ -585,6 +602,8 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
 	TransactionId frozenXid;
 	MultiXactId cutoffMulti;
 
+	Assert(!RELATION_IS_GLOBAL_TEMP(OldHeap));
+
 	/* Mark the correct index as clustered */
 	if (OidIsValid(indexOid))
 		mark_index_clustered(OldHeap, indexOid, true);
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 53f48531419..c03191cce94 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -289,7 +289,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP_ON_CURRENT_SESSION(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, whereClause,
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index f366a818a14..fb0e9349a6e 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -30,6 +30,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/copyfrom_internal.h"
 #include "commands/progress.h"
@@ -652,7 +653,7 @@ CopyFrom(CopyFromState cstate)
 	 */
 	ExecInitRangeTable(estate, cstate->range_table);
 	resultRelInfo = target_resultRelInfo = makeNode(ResultRelInfo);
-	ExecInitResultRelation(estate, resultRelInfo, 1);
+	ExecInitResultRelation(estate, resultRelInfo, 1, CMD_INSERT);
 
 	/* Verify the named relation is a valid target for INSERT */
 	CheckValidResultRel(resultRelInfo, CMD_INSERT);
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 09828517153..b151fbdd80f 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -38,6 +38,7 @@
 #include "commands/prepare.h"
 #include "commands/tablecmds.h"
 #include "commands/view.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
@@ -520,6 +521,8 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 	 */
 	intoRelationDesc = table_open(intoRelationAddr.objectId, AccessExclusiveLock);
 
+	init_gtt_storage(CMD_INSERT, intoRelationDesc);
+
 	/*
 	 * Make sure the constructed table does not have RLS enabled.
 	 *
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 8d3104821ee..ae570b07c1e 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -111,6 +111,7 @@ struct ReindexIndexCallbackState
 {
 	ReindexParams params;		/* options from statement */
 	Oid			locked_table_oid;	/* tracks previously locked table */
+	LOCKMODE	lockmode;
 };
 
 /*
@@ -570,7 +571,7 @@ DefineIndex(Oid relationId,
 	 * is more efficient.  Do this before any use of the concurrent option is
 	 * done.
 	 */
-	if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+	if (stmt->concurrent && !RelpersistenceTsTemp(get_rel_persistence(relationId)))
 		concurrent = true;
 	else
 		concurrent = false;
@@ -2581,9 +2582,9 @@ ReindexIndex(RangeVar *indexRelation, ReindexParams *params, bool isTopLevel)
 	 */
 	state.params = *params;
 	state.locked_table_oid = InvalidOid;
+	state.lockmode = AccessShareLock;
 	indOid = RangeVarGetRelidExtended(indexRelation,
-									  (params->options & REINDEXOPT_CONCURRENTLY) != 0 ?
-									  ShareUpdateExclusiveLock : AccessExclusiveLock,
+									  AccessShareLock,
 									  0,
 									  RangeVarCallbackForReindexIndex,
 									  &state);
@@ -2594,11 +2595,25 @@ ReindexIndex(RangeVar *indexRelation, ReindexParams *params, bool isTopLevel)
 	 */
 	persistence = get_rel_persistence(indOid);
 	relkind = get_rel_relkind(indOid);
+	if (persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		LOCKMODE	lockmode;
+
+		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0)
+			lockmode = ShareUpdateExclusiveLock;
+		else
+			lockmode = AccessExclusiveLock;
+
+		/* lock heap first */
+		Assert(OidIsValid(state.locked_table_oid));
+		LockRelationOid(state.locked_table_oid, lockmode);
+		LockRelationOid(indOid, lockmode);
+	}
 
 	if (relkind == RELKIND_PARTITIONED_INDEX)
 		ReindexPartitions(indOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 persistence != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(persistence))
 		ReindexRelationConcurrently(indOid, params);
 	else
 	{
@@ -2620,15 +2635,7 @@ RangeVarCallbackForReindexIndex(const RangeVar *relation,
 {
 	char		relkind;
 	struct ReindexIndexCallbackState *state = arg;
-	LOCKMODE	table_lockmode;
-
-	/*
-	 * Lock level here should match table lock in reindex_index() for
-	 * non-concurrent case and table locks used by index_concurrently_*() for
-	 * concurrent case.
-	 */
-	table_lockmode = (state->params.options & REINDEXOPT_CONCURRENTLY) != 0 ?
-		ShareUpdateExclusiveLock : ShareLock;
+	LOCKMODE	table_lockmode = state->lockmode;
 
 	/*
 	 * If we previously locked some other index's heap, and the name we're
@@ -2689,6 +2696,8 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 {
 	Oid			heapOid;
 	bool		result;
+	char		relpersistence;
+	int 		reindex_flags = 0;
 
 	/*
 	 * The lock level used here should match reindex_relation().
@@ -2699,15 +2708,27 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 	 * locks on our temporary table.
 	 */
 	heapOid = RangeVarGetRelidExtended(relation,
-									   (params->options & REINDEXOPT_CONCURRENTLY) != 0 ?
-									   ShareUpdateExclusiveLock : ShareLock,
+									   AccessShareLock,
 									   0,
 									   RangeVarCallbackOwnsTable, NULL);
 
+	relpersistence = get_rel_persistence(heapOid);
+	if (relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		LOCKMODE	lockmode;
+
+		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0)
+			lockmode = ShareUpdateExclusiveLock;
+		else
+			lockmode = ShareLock;
+
+		LockRelationOid(heapOid, lockmode);
+	}
+
 	if (get_rel_relkind(heapOid) == RELKIND_PARTITIONED_TABLE)
 		ReindexPartitions(heapOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(relpersistence))
 	{
 		result = ReindexRelationConcurrently(heapOid, params);
 
@@ -2721,9 +2742,14 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 		ReindexParams newparams = *params;
 
 		newparams.options |= REINDEXOPT_REPORT_PROGRESS;
+
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			reindex_flags |= REINDEX_REL_PROCESS_GLOBAL_TEMP;
+
+		reindex_flags |= REINDEX_REL_PROCESS_TOAST;
+		reindex_flags |= REINDEX_REL_CHECK_CONSTRAINTS;
 		result = reindex_relation(heapOid,
-								  REINDEX_REL_PROCESS_TOAST |
-								  REINDEX_REL_CHECK_CONSTRAINTS,
+								  reindex_flags,
 								  &newparams);
 		if (!result)
 			ereport(NOTICE,
@@ -3119,7 +3145,7 @@ ReindexMultipleInternal(List *relids, ReindexParams *params)
 		Assert(!RELKIND_HAS_PARTITIONS(relkind));
 
 		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			relpersistence != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(relpersistence))
 		{
 			ReindexParams newparams = *params;
 
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 62465bacd81..ef37f79ba68 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -51,12 +51,32 @@ LockTableCommand(LockStmt *lockstmt)
 		RangeVar   *rv = (RangeVar *) lfirst(p);
 		bool		recurse = rv->inh;
 		Oid			reloid;
+		LOCKMODE	lockmode = lockstmt->mode;
+		char		relpersistence;
 
-		reloid = RangeVarGetRelidExtended(rv, lockstmt->mode,
-										  lockstmt->nowait ? RVR_NOWAIT : 0,
+		reloid = RangeVarGetRelidExtended(rv, NoLock, 0,
 										  RangeVarCallbackForLockTable,
 										  (void *) &lockstmt->mode);
 
+		relpersistence = get_rel_persistence(reloid);
+		if (relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			if (!lockstmt->nowait)
+				LockRelationOid(reloid, lockmode);
+			else if (!ConditionalLockRelationOid(reloid, lockmode))
+			{
+				/* try to throw error by name; relation could be deleted... */
+				char	   *relname = get_rel_name(reloid);
+
+				if (!relname)
+					return;		/* child concurrently dropped, just skip it */
+				ereport(ERROR,
+						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						 errmsg("could not obtain lock on relation \"%s\"",
+								relname)));
+			}
+		}
+
 		if (get_rel_relkind(reloid) == RELKIND_VIEW)
 			LockViewRecurse(reloid, lockstmt->mode, lockstmt->nowait, NIL);
 		else if (recurse)
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 72bfdc07a49..79119c57e54 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -220,9 +223,12 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	rel = table_open(seqoid, AccessExclusiveLock);
 	tupDesc = RelationGetDescr(rel);
 
-	/* now initialize the sequence's data */
-	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	if (!RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		/* now initialize the sequence's data */
+		tuple = heap_form_tuple(tupDesc, value, null);
+		fill_seq_with_data(rel, tuple);
+	}
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -275,8 +281,6 @@ ResetSequence(Oid seq_relid)
 	Buffer		buf;
 	HeapTupleData seqdatatuple;
 	HeapTuple	tuple;
-	HeapTuple	pgstuple;
-	Form_pg_sequence pgsform;
 	int64		startv;
 
 	/*
@@ -287,12 +291,7 @@ ResetSequence(Oid seq_relid)
 	init_sequence(seq_relid, &elm, &seq_rel);
 	(void) read_seq_tuple(seq_rel, &buf, &seqdatatuple);
 
-	pgstuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seq_relid));
-	if (!HeapTupleIsValid(pgstuple))
-		elog(ERROR, "cache lookup failed for sequence %u", seq_relid);
-	pgsform = (Form_pg_sequence) GETSTRUCT(pgstuple);
-	startv = pgsform->seqstart;
-	ReleaseSysCache(pgstuple);
+	startv = get_seqence_start_value(seq_relid);
 
 	/*
 	 * Copy the existing sequence tuple.
@@ -451,6 +450,15 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 
 	init_sequence(relid, &elm, &seqrel);
 
+	if (RELATION_IS_GLOBAL_TEMP(seqrel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(seqrel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot alter global temporary sequence %s when other backend attached it.",
+						RelationGetRelationName(seqrel))));
+	}
+
 	rel = table_open(SequenceRelationId, RowExclusiveLock);
 	seqtuple = SearchSysCacheCopy1(SEQRELID,
 								   ObjectIdGetDatum(relid));
@@ -611,7 +619,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +944,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1153,6 +1161,14 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	/* Initializes the storage for sequence which the global temporary table belongs. */
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_seq(seqrel);
+	}
 }
 
 
@@ -1927,3 +1943,58 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+/*
+ * Get the startValue of the sequence from syscache.
+ */
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+/*
+ * Initialize sequence which global temporary table belongs.
+ */
+void
+gtt_init_seq(Relation rel)
+{
+	Datum		value[SEQ_COL_LASTCOL];
+	bool		null[SEQ_COL_LASTCOL];
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL - 1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+	null[SEQ_COL_LASTVAL - 1] = false;
+
+	value[SEQ_COL_LOG - 1] = Int64GetDatum((int64)0);
+	null[SEQ_COL_LOG - 1] = false;
+
+	value[SEQ_COL_CALLED - 1] = BoolGetDatum(false);
+	null[SEQ_COL_CALLED - 1] = false;
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple);
+	heap_freetuple(tuple);
+
+	return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index bf42587e383..37a347cbfd0 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -118,6 +119,7 @@ typedef struct OnCommitItem
 	 */
 	SubTransactionId creating_subid;
 	SubTransactionId deleting_subid;
+	bool			 is_global_temp;
 } OnCommitItem;
 
 static List *on_commits = NIL;
@@ -607,7 +609,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
 static char GetAttributeCompression(Oid atttypid, char *compression);
-
+static OnCommitAction gtt_oncommit_option(List *options);
 
 /* ----------------------------------------------------------------
  *		DefineRelation
@@ -652,6 +654,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -663,7 +666,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * Check consistency of arguments
 	 */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !RelpersistenceTsTemp(stmt->relation->relpersistence))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -693,7 +696,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	if (RelpersistenceTsTemp(stmt->relation->relpersistence)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -794,6 +797,50 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* For global temporary table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		if (!(relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE))
+			elog(ERROR, "Only support global temporary regular table.");
+
+		/* Check parent table */
+		if (inheritOids)
+			elog(ERROR, "Not support global temporary partition table or inherit table.");
+
+		/* Check oncommit clause and save to reloptions */
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else
+				elog(ERROR, "global temporary table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1418,7 +1465,7 @@ RemoveRelations(DropStmt *drop)
 		 * relation persistence cannot be known without its OID.
 		 */
 		if (drop->concurrent &&
-			get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(get_rel_persistence(relOid)))
 		{
 			Assert(list_length(drop->objects) == 1 &&
 				   drop->removeType == OBJECT_INDEX);
@@ -1627,9 +1674,9 @@ ExecuteTruncate(TruncateStmt *stmt)
 		Relation	rel;
 		bool		recurse = rv->inh;
 		Oid			myrelid;
-		LOCKMODE	lockmode = AccessExclusiveLock;
+		LOCKMODE	lockmode;
 
-		myrelid = RangeVarGetRelidExtended(rv, lockmode,
+		myrelid = RangeVarGetRelidExtended(rv, AccessShareLock,
 										   0, RangeVarCallbackForTruncate,
 										   NULL);
 
@@ -1637,9 +1684,21 @@ ExecuteTruncate(TruncateStmt *stmt)
 		if (list_member_oid(relids, myrelid))
 			continue;
 
-		/* open the relation, we already hold a lock on it */
+		/* open the relation, we need hold a low-level lock first */
 		rel = table_open(myrelid, NoLock);
 
+		/*
+		 * Truncate global temp table only cleans up the data in current backend,
+		 * only low-level locks are required.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel))
+			lockmode = AccessShareLock;
+		else
+		{
+			lockmode = AccessExclusiveLock;
+			LockRelationOid(myrelid, lockmode);
+		}
+
 		/*
 		 * RangeVarGetRelidExtended() has done most checks with its callback,
 		 * but other checks with the now-opened Relation remain.
@@ -1889,6 +1948,7 @@ ExecuteTruncateGuts(List *explicit_rels,
 	foreach(cell, rels)
 	{
 		Relation	rel = (Relation) lfirst(cell);
+		LOCKMODE	lockmode;
 
 		/* Skip partitioned tables as there is nothing to do */
 		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
@@ -1939,6 +1999,19 @@ ExecuteTruncateGuts(List *explicit_rels,
 			continue;
 		}
 
+		/*
+		 * Skip the global temporary table that is not initialized for storage
+		 * in current backend.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel))
+		{
+			lockmode = AccessShareLock;
+			if (!gtt_storage_attached(RelationGetRelid(rel)))
+				continue;
+		}
+		else
+			lockmode = AccessExclusiveLock;
+
 		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
@@ -1957,6 +2030,7 @@ ExecuteTruncateGuts(List *explicit_rels,
 			Oid			heap_relid;
 			Oid			toast_relid;
 			ReindexParams reindex_params = {0};
+			int			reindex_flags = 0;
 
 			/*
 			 * This effectively deletes all rows in the table, and may be done
@@ -1984,17 +2058,21 @@ ExecuteTruncateGuts(List *explicit_rels,
 			if (OidIsValid(toast_relid))
 			{
 				Relation	toastrel = relation_open(toast_relid,
-													 AccessExclusiveLock);
+													 lockmode);
 
 				RelationSetNewRelfilenode(toastrel,
 										  toastrel->rd_rel->relpersistence);
 				table_close(toastrel, NoLock);
 			}
 
+			reindex_flags = REINDEX_REL_PROCESS_TOAST;
+			if (RELATION_IS_GLOBAL_TEMP(rel))
+				reindex_flags |= REINDEX_REL_PROCESS_GLOBAL_TEMP;
+
 			/*
 			 * Reconstruct the indexes to match, and we're done.
 			 */
-			reindex_relation(heap_relid, REINDEX_REL_PROCESS_TOAST,
+			reindex_relation(heap_relid, reindex_flags,
 							 &reindex_params);
 		}
 
@@ -3266,6 +3344,11 @@ CheckRelationTableSpaceMove(Relation rel, Oid newTableSpaceId)
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot move temporary tables of other sessions")));
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("The tablespace of global temporary table can not be changed")));
+
 	return true;
 }
 
@@ -4035,6 +4118,16 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temporary table only current backend use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot alter global temporary table %s when other backend attached it.",
+						RelationGetRelationName(rel))));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -5366,6 +5459,24 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 
 			rel = table_open(tab->relid, NoLock);
 			find_composite_type_dependencies(rel->rd_rel->reltype, rel, NULL);
+
+			if (RELATION_IS_GLOBAL_TEMP(rel) && tab->rewrite > 0)
+			{
+				if (tab->chgPersistence)
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot change global temporary table persistence setting")));
+
+				if(gtt_storage_attached(tab->relid))
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("Only support alter global temporary table in an empty context."),
+						 errhint("Please create a new connection and execute ALTER TABLE on the new connection.")));
+
+				/* There is no need to override the whole temp table */
+				tab->rewrite = 0;
+			}
+
 			table_close(rel, NoLock);
 		}
 
@@ -5417,6 +5528,8 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot rewrite temporary tables of other sessions")));
 
+			Assert(!RELATION_IS_GLOBAL_TEMP(OldHeap));
+
 			/*
 			 * Select destination tablespace (same as original unless user
 			 * requested a change)
@@ -9064,6 +9177,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -13179,7 +13298,9 @@ TryReuseIndex(Oid oldId, IndexStmt *stmt)
 		Relation	irel = index_open(oldId, NoLock);
 
 		/* If it's a partitioned index, there is no storage to share. */
-		if (irel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
+		/* multiple global temp table are not allow use same relfilenode */
+		if (irel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX &&
+			!RELATION_IS_GLOBAL_TEMP(irel))
 		{
 			stmt->oldNode = irel->rd_node.relNode;
 			stmt->oldCreateSubid = irel->rd_createSubid;
@@ -13841,6 +13962,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -14341,7 +14465,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(RelationGetSmgr(rel), dstrel, MAIN_FORKNUM,
@@ -15945,6 +16069,7 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
 	switch (rel->rd_rel->relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -16386,7 +16511,7 @@ AlterSeqNamespaces(Relation classRel, Relation rel,
  * Register a newly-created relation's ON COMMIT action.
  */
 void
-register_on_commit_action(Oid relid, OnCommitAction action)
+register_on_commit_action(Oid relid, OnCommitAction action, bool is_gloal_temp)
 {
 	OnCommitItem *oc;
 	MemoryContext oldcxt;
@@ -16405,6 +16530,7 @@ register_on_commit_action(Oid relid, OnCommitAction action)
 	oc->oncommit = action;
 	oc->creating_subid = GetCurrentSubTransactionId();
 	oc->deleting_subid = InvalidSubTransactionId;
+	oc->is_global_temp = is_gloal_temp;
 
 	/*
 	 * We use lcons() here so that ON COMMIT actions are processed in reverse
@@ -16450,6 +16576,7 @@ PreCommit_on_commit_actions(void)
 	ListCell   *l;
 	List	   *oids_to_truncate = NIL;
 	List	   *oids_to_drop = NIL;
+	List	   *oids_to_truncate_gtt = NIL;
 
 	foreach(l, on_commits)
 	{
@@ -16473,7 +16600,12 @@ PreCommit_on_commit_actions(void)
 				 * tables, as they must still be empty.
 				 */
 				if ((MyXactFlags & XACT_FLAGS_ACCESSEDTEMPNAMESPACE))
-					oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid);
+				{
+					if (oc->is_global_temp)
+						oids_to_truncate_gtt = lappend_oid(oids_to_truncate_gtt, oc->relid);
+					else
+						oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid);
+				}
 				break;
 			case ONCOMMIT_DROP:
 				oids_to_drop = lappend_oid(oids_to_drop, oc->relid);
@@ -16490,7 +16622,10 @@ PreCommit_on_commit_actions(void)
 	 * exists at truncation time.
 	 */
 	if (oids_to_truncate != NIL)
-		heap_truncate(oids_to_truncate);
+		heap_truncate(oids_to_truncate, false);
+
+	if (oids_to_truncate_gtt != NIL)
+		heap_truncate(oids_to_truncate_gtt, true);
 
 	if (oids_to_drop != NIL)
 	{
@@ -17489,6 +17624,13 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot attach temporary relation of another session as partition")));
 
+	/* If the parent is permanent, so must be all of its partitions. */
+	if (attachrel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach a global temporary relation as partition of permanent relation \"%s\"",
+						RelationGetRelationName(rel))));
+
 	/* Check if there are any columns in attachrel that aren't in the parent */
 	tupleDesc = RelationGetDescr(attachrel);
 	natts = tupleDesc->natts;
@@ -18959,3 +19101,40 @@ GetAttributeCompression(Oid atttypid, char *compression)
 
 	return cmethod;
 }
+
+/*
+ * Parse the on commit clause for the temporary table
+ */
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			bool	res = false;
+			char	*sval = defGetString(def);
+
+			/* It has to be a Boolean value */
+			if (!parse_bool(sval, &res))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
+
+			if (res)
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 5c4bc15b441..3a861c47946 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"
@@ -1315,6 +1316,27 @@ vac_update_relstats(Relation relation,
 	HeapTuple	ctup;
 	Form_pg_class pgcform;
 	bool		dirty;
+	bool		is_gtt = RELATION_IS_GLOBAL_TEMP(relation);
+
+	 /* For global temporary table */
+	if (is_gtt)
+	{
+		/* Store relation statistics and transaction information to the localhash */
+		up_gtt_relstats(RelationGetRelid(relation),
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
+
+		/* Update relation statistics to local relcache */
+		relation->rd_rel->relpages = (int32) num_pages;
+		relation->rd_rel->reltuples = (float4) num_tuples;
+		relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+		if (TransactionIdIsNormal(frozenxid))
+			relation->rd_rel->relfrozenxid = frozenxid;
+
+		if (MultiXactIdIsValid(minmulti))
+			relation->rd_rel->relminmxid = minmulti;
+	}
 
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
@@ -1328,17 +1350,23 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
+
+	if (!is_gtt &&
+		pgcform->relpages != (int32) num_pages)
 	{
 		pgcform->relpages = (int32) num_pages;
 		dirty = true;
 	}
-	if (pgcform->reltuples != (float4) num_tuples)
+
+	if (!is_gtt &&
+		pgcform->reltuples != (float4) num_tuples)
 	{
 		pgcform->reltuples = (float4) num_tuples;
 		dirty = true;
 	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+	if (!is_gtt &&
+		pgcform->relallvisible != (int32) num_all_visible_pages)
 	{
 		pgcform->relallvisible = (int32) num_all_visible_pages;
 		dirty = true;
@@ -1351,7 +1379,15 @@ vac_update_relstats(Relation relation,
 		/*
 		 * If we didn't find any indexes, reset relhasindex.
 		 */
-		if (pgcform->relhasindex && !hasindex)
+		if (is_gtt &&
+			RelationGetIndexList(relation) != NIL)
+		{
+			/*
+			 * Global temporary tables may contain indexes that are not valid locally.
+			 * The catalog should not be updated based on local invalid index.
+			 */
+		}
+		else if (pgcform->relhasindex && !hasindex)
 		{
 			pgcform->relhasindex = false;
 			dirty = true;
@@ -1383,7 +1419,8 @@ vac_update_relstats(Relation relation,
 	 * This should match vac_update_datfrozenxid() concerning what we consider
 	 * to be "in the future".
 	 */
-	if (TransactionIdIsNormal(frozenxid) &&
+	if (!is_gtt &&
+		TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
 		 TransactionIdPrecedes(ReadNextTransactionId(),
@@ -1394,7 +1431,8 @@ vac_update_relstats(Relation relation,
 	}
 
 	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
+	if (!is_gtt &&
+		MultiXactIdIsValid(minmulti) &&
 		pgcform->relminmxid != minmulti &&
 		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
 		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1502,6 +1540,13 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/*
+		 * The relfrozenxid for a global temporary talble is stored in localhash,
+		 * not pg_class, See list_all_session_gtt_frozenxids()
+		 */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1559,6 +1604,43 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/* If enable global temporary table */
+	if (max_active_gtt > 0)
+	{
+		TransactionId	safe_age;
+		/*  */
+		TransactionId	oldest_gtt_frozenxid =
+			list_all_backend_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid))
+		{
+			safe_age = oldest_gtt_frozenxid + vacuum_gtt_defer_check_age;
+			if (safe_age < FirstNormalTransactionId)
+				safe_age += FirstNormalTransactionId;
+
+			/*
+			 * We tolerate that the minimum age of gtt is less than
+			 * the minimum age of conventional tables, otherwise it will
+			 * throw warning message.
+			 */
+			if (TransactionIdIsNormal(safe_age) &&
+				TransactionIdPrecedes(safe_age, newFrozenXid))
+			{
+				ereport(WARNING,
+					(errmsg("global temp table oldest relfrozenxid %u is the oldest in the entire db",
+							oldest_gtt_frozenxid),
+					 errdetail("The oldest relfrozenxid in pg_class is %u", newFrozenXid),
+					 errhint("If they differ greatly, please consider cleaning up the data in global temp table.")));
+			}
+
+			/*
+			 * We need to ensure that the clog required by gtt is not cleand.
+			 */
+			if (TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+				newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1910,6 +1992,19 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	/*
+	 * Skip those global temporary table that are not initialized in
+	 * current backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel) &&
+		!gtt_storage_attached(RelationGetRelid(rel)))
+	{
+		relation_close(rel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 4df05a0b33d..4c181e2e14e 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -527,6 +527,12 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	/* Global temporary table are not sensible. */
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temp because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b3ce4bae530..611e3f18a70 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -784,6 +784,10 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		/* This is one kind of temp table */
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 5c723bc54e1..191e0f6fd21 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 4ab1302313f..b6dfb46fd57 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -50,6 +50,7 @@
 #include "access/table.h"
 #include "access/tableam.h"
 #include "access/transam.h"
+#include "catalog/storage_gtt.h"
 #include "executor/executor.h"
 #include "executor/execPartition.h"
 #include "jit/jit.h"
@@ -832,7 +833,7 @@ ExecGetRangeTableRelation(EState *estate, Index rti)
  */
 void
 ExecInitResultRelation(EState *estate, ResultRelInfo *resultRelInfo,
-					   Index rti)
+					   Index rti, CmdType operation)
 {
 	Relation	resultRelationDesc;
 
@@ -843,6 +844,9 @@ ExecInitResultRelation(EState *estate, ResultRelInfo *resultRelInfo,
 					  NULL,
 					  estate->es_instrument);
 
+	/* Check and init global temporary table storage in current backend */
+	init_gtt_storage(operation, resultRelationDesc);
+
 	if (estate->es_result_relations == NULL)
 		estate->es_result_relations = (ResultRelInfo **)
 			palloc0(estate->es_range_table_size * sizeof(ResultRelInfo *));
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d328856ae5b..c540874789b 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -38,6 +38,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -2754,13 +2755,13 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	{
 		mtstate->rootResultRelInfo = makeNode(ResultRelInfo);
 		ExecInitResultRelation(estate, mtstate->rootResultRelInfo,
-							   node->rootRelation);
+							   node->rootRelation, operation);
 	}
 	else
 	{
 		mtstate->rootResultRelInfo = mtstate->resultRelInfo;
 		ExecInitResultRelation(estate, mtstate->resultRelInfo,
-							   linitial_int(node->resultRelations));
+							   linitial_int(node->resultRelations), operation);
 	}
 
 	/* set up epqstate with dummy subplan data for the moment */
@@ -2788,7 +2789,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 		if (resultRelInfo != mtstate->rootResultRelInfo)
 		{
-			ExecInitResultRelation(estate, resultRelInfo, resultRelation);
+			ExecInitResultRelation(estate, resultRelInfo, resultRelation, operation);
 
 			/*
 			 * For child result relations, store the root result relation
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 296dd75c1b6..d971aea2546 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -48,7 +48,7 @@
 #include "partitioning/partprune.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/lsyscache.h"
-
+#include "utils/rel.h"
 
 /* results of subquery_is_pushdown_safe */
 typedef struct pushdown_safety_info
@@ -619,7 +619,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			if (RelpersistenceTsTemp(get_rel_persistence(rte->relid)))
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index bd01ec0526f..ff4e81ca2cf 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6071,7 +6071,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
-	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+	if (RELATION_IS_TEMP(heap) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 564a38a13e9..1cf2d31b034 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -30,6 +30,7 @@
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temporary table in current backend */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 146ee8dd1ea..2d4e9393f00 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2907,6 +2907,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 3d4dd43e47b..6af3302e908 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3434,17 +3434,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; }
@@ -11845,19 +11839,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 c5c3f26ecf1..2a2b2789077 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -82,6 +82,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);
 
 
 /*
@@ -3665,3 +3666,53 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * Like function isQueryUsingTempRelation_walker
+ * return true if any relation underlying
+ * the query is a global temporary table.
+ */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* Check if the query uses global temporary table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 2d857a301bb..c52bb35a3a8 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -447,6 +447,13 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	/*
+	 * If a sequence is bound to a global temporary table, then the sequence
+	 * must been "global temporary"
+	 */
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
@@ -3326,6 +3333,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		cxt.isforeign = false;
 	}
 	cxt.relation = stmt->relation;
+	/* Sets the table persistence to the context */
+	cxt.relation->relpersistence = RelationGetRelPersistence(rel);
 	cxt.rel = rel;
 	cxt.inhRelations = NIL;
 	cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index f6d05628764..0bd3914d706 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2116,6 +2116,14 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/*
+			 * Aotuvacuum cannot vacuum the private data stored in each backend
+			 * that belongs to global temporary table, so skip them.
+			 */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2182,7 +2190,7 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		if (RelpersistenceTsTemp(classForm->relpersistence))
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index b4532948d3f..1325f5e12ad 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlogutils.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -2934,7 +2935,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
-	if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind))
+	/*
+	 * Returns 0 if this global temporary table is not initialized in current
+	 * backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+	else if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind))
 	{
 		/*
 		 * Not every table AM uses BLCKSZ wide fixed size blocks.
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 9fa3e0631e6..cc3eb928bc6 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -23,6 +23,7 @@
 #include "access/syncscan.h"
 #include "access/twophase.h"
 #include "commands/async.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
@@ -143,6 +144,7 @@ CalculateShmemSize(int *num_semaphores)
 	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
@@ -246,6 +248,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* For global temporary table shared hashtable */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index a9945c80eb4..63d9d2ee80f 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -65,6 +65,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -5161,3 +5162,82 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temporary table.
+ */
+int
+list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct		*arrayP = NULL;
+	TransactionId		result = InvalidTransactionId;
+	int			index = 0;
+	int			i = 0;
+	uint8		flags = 0;
+
+	if (n)
+		*n = 0;
+
+	/* return 0 if feature is disabled */
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+	}
+
+	/* Disable in standby node */
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	arrayP = procArray;
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		uint8           statusFlags = ProcGlobal->statusFlags[index];
+
+		if (statusFlags & flags)
+			continue;
+
+		/* Fetch all backend that is belonging to MyDatabaseId */
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->backend_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->backend_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->backend_gtt_frozenxid, result))
+				result = proc->backend_gtt_frozenxid;
+
+			/* save backend pid and backend level oldest relfrozenxid */
+			if (pids)
+				pids[i] = proc->pid;
+
+			if (xids)
+				xids[i] = proc->backend_gtt_frozenxid;
+
+			i++;
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (n)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 862097352bb..4edd3b31f7a 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -176,7 +176,9 @@ static const char *const BuiltinTrancheNames[] = {
 	/* LWTRANCHE_PARALLEL_APPEND: */
 	"ParallelAppend",
 	/* LWTRANCHE_PER_XACT_PREDICATE_LIST: */
-	"PerXactPredicateList"
+	"PerXactPredicateList",
+	/* LWTRANCHE_GTT_CTL */
+	"GlobalTempTableControl"
 };
 
 StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index d1d3cd0dc88..fdc8f8e70bb 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -391,6 +391,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;	/* init backend level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
@@ -576,6 +577,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->backend_gtt_frozenxid = InvalidTransactionId;	/* init backend level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index d5a7fb13f3c..8225cf6219f 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -982,6 +982,13 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/*
+			 * For global temporary table ,each backend has its own storage,
+			 * also only sees its own storage. Use Backendid to identify them.
+			 */
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 10895fb2876..66255eb7604 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -108,6 +108,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -5115,12 +5116,26 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								/* For global temporary table, get statistic data from localhash */
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -5368,15 +5383,28 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6820,6 +6848,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;
@@ -6837,6 +6866,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6848,6 +6885,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;
@@ -6863,6 +6902,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7781,6 +7828,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))
@@ -7793,6 +7842,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7805,6 +7863,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.
@@ -7824,6 +7884,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 9176514a962..6df1675b1f8 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -3113,6 +3114,19 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		/* For global temporary table, get statistic data from localhash */
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 105d8d4601c..18656f48442 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -65,6 +65,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1152,6 +1153,36 @@ retry:
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+				TransactionId	relfrozenxid = InvalidTransactionId;
+				MultiXactId 	relminmxid = InvalidMultiXactId;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+
+				/* For global temporary table, get relstat data from localhash */
+				get_gtt_relstats(RelationGetRelid(relation),
+								&relpages,
+								&reltuples,
+								&relallvisible,
+								&relfrozenxid,
+								&relminmxid);
+
+				/* And put them to local relcache */
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+				if (TransactionIdIsNormal(relfrozenxid))
+					relation->rd_rel->relfrozenxid = relfrozenxid;
+
+				if (MultiXactIdIsValid(relminmxid))
+					relation->rd_rel->relminmxid = relminmxid;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1212,6 +1243,10 @@ retry:
 	else
 		Assert(relation->rd_rel->relam == InvalidOid);
 
+	/* The state of the global temporary table's index may need to be set */
+	if (relation->rd_rel->relkind == RELKIND_INDEX)
+		gtt_fix_index_backend_state(relation);
+
 	/* extract reloptions if any */
 	RelationParseRelOptions(relation, pg_class_tuple);
 
@@ -1335,7 +1370,22 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+			/*
+			 * For global temporary table, get the latest relfilenode
+			 * from localhash and put it in relcache.
+			 */
+			if (OidIsValid(newrelnode) &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2288,6 +2338,9 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		/* The state of the global temporary table's index may need to be set */
+		gtt_fix_index_backend_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3568,6 +3621,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3674,28 +3731,39 @@ void
 RelationSetNewRelfilenode(Relation relation, char persistence)
 {
 	Oid			newrelfilenode;
-	Relation	pg_class;
-	HeapTuple	tuple;
+	Relation	pg_class = NULL;
+	HeapTuple	tuple = NULL;
 	Form_pg_class classform;
 	MultiXactId minmulti = InvalidMultiXactId;
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
 
+	/*
+	 * For global temporary table, storage information for the table is
+	 * maintained locally, not in catalog.
+	 */
+	bool		update_catalog = !RELATION_IS_GLOBAL_TEMP(relation);
+
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
 
-	/*
-	 * Get a writable copy of the pg_class tuple for the given relation.
-	 */
-	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+	memset(&classform, 0, sizeof(classform));
 
-	tuple = SearchSysCacheCopy1(RELOID,
-								ObjectIdGetDatum(RelationGetRelid(relation)));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "could not find tuple for relation %u",
-			 RelationGetRelid(relation));
-	classform = (Form_pg_class) GETSTRUCT(tuple);
+	if (update_catalog)
+	{
+		/*
+		 * Get a writable copy of the pg_class tuple for the given relation.
+		 */
+		pg_class = table_open(RelationRelationId, RowExclusiveLock);
+
+		tuple = SearchSysCacheCopy1(RELOID,
+									ObjectIdGetDatum(RelationGetRelid(relation)));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u",
+				 RelationGetRelid(relation));
+		classform = (Form_pg_class) GETSTRUCT(tuple);
+	}
 
 	/*
 	 * Schedule unlinking of the old storage at transaction commit.
@@ -3724,7 +3792,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);
 	}
 	else
@@ -3734,6 +3802,18 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 			 RelationGetRelationName(relation));
 	}
 
+	/* For global temporary table */
+	if (!update_catalog)
+	{
+		Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+		Assert(RELATION_IS_GLOBAL_TEMP(relation));
+		Assert(!RelationIsMapped(relation));
+
+		/* Make cache invalid and set new relnode to local cache. */
+		CacheInvalidateRelcache(relation);
+		relation->rd_node.relNode = relnode;
+	}
 	/*
 	 * If we're dealing with a mapped index, pg_class.relfilenode doesn't
 	 * change; instead we have to send the update to the relation mapper.
@@ -3743,7 +3823,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	 * possibly-inaccurate values of relpages etc, but those will be fixed up
 	 * later.
 	 */
-	if (RelationIsMapped(relation))
+	else if (RelationIsMapped(relation))
 	{
 		/* This case is only supported for indexes */
 		Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3789,9 +3869,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 		CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
 	}
 
-	heap_freetuple(tuple);
+	if (update_catalog)
+	{
+		heap_freetuple(tuple);
 
-	table_close(pg_class, RowExclusiveLock);
+		table_close(pg_class, RowExclusiveLock);
+	}
 
 	/*
 	 * Make the pg_class row change or relation map change visible.  This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 7b030463013..d650382b2a8 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -44,6 +44,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "commands/prepare.h"
 #include "commands/trigger.h"
@@ -153,6 +154,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temporary table feature.
+ * table schema are still saved in catalog.
+ *
+ * num > 0 means allows the database to manage multiple active tables at the same time.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2131,6 +2144,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("Sets the amount of time to wait before forcing a "
@@ -2702,6 +2724,16 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"vacuum_gtt_defer_check_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("The defer check age of GTT, used to check expired data after vacuum."),
+			NULL
+		},
+		&vacuum_gtt_defer_check_age,
+		10000, 0, 1000000,
+		NULL, NULL, NULL
+	},
+
 	/*
 	 * See also CheckRequiredParameterValues() if this parameter changes
 	 */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ac291fbef21..5a2751ea4de 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2525,6 +2525,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
 		dopt->no_unlogged_table_data)
 		return;
 
+	/* Don't dump data in global temporary table/sequence */
+	if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		return;
+
 	/* Check that the data is not explicitly excluded */
 	if (simple_oid_list_member(&tabledata_exclude_oids,
 							   tbinfo->dobj.catId.oid))
@@ -14883,6 +14887,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
 		char	   *foreign = "";
+		char	   *table_type = NULL;
 
 		/*
 		 * Set reltypename, and collect any relkind-specific data that we
@@ -14958,9 +14963,15 @@ dumpTableSchema(Archive *fout, const 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);
 
@@ -15328,6 +15339,15 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 			}
 		}
 
+		/*
+		 * Transaction information for the global temporary table is not stored
+		 * in the pg_class.
+		 */
+		if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			Assert(tbinfo->frozenxid == 0);
+			Assert(tbinfo->minmxid == 0);
+		}
 		/*
 		 * In binary_upgrade mode, arrange to restore the old relfrozenxid and
 		 * relminmxid of all vacuumable relations.  (While vacuum.c processes
@@ -15335,7 +15355,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		 * of other relations; so this "if" lacks RELKIND_TOASTVALUE, and the
 		 * child toast table is handled below.)
 		 */
-		if (dopt->binary_upgrade &&
+		else if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
 			 tbinfo->relkind == RELKIND_MATVIEW))
 		{
@@ -16355,6 +16375,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	PQExpBuffer query = createPQExpBuffer();
 	PQExpBuffer delqry = createPQExpBuffer();
 	char	   *qseqname;
+	bool	   global_temp_seq = false;
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
@@ -16364,9 +16385,12 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
-						  "FROM pg_catalog.pg_sequence "
-						  "WHERE seqrelid = '%u'::oid",
+						  "seqcache, seqcycle, "
+						  "c.relpersistence "
+						  "FROM pg_catalog.pg_sequence s, "
+						  "pg_catalog.pg_class c "
+						  "WHERE seqrelid = '%u'::oid "
+						  "and s.seqrelid = c.oid",
 						  tbinfo->dobj.catId.oid);
 	}
 	else
@@ -16403,6 +16427,9 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
 
+	if (fout->remoteVersion >= 140000)
+		global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
 	if (strcmp(seqtype, "smallint") == 0)
@@ -16480,9 +16507,13 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	}
 	else
 	{
-		appendPQExpBuffer(query,
-						  "CREATE SEQUENCE %s\n",
-						  fmtQualifiedDumpable(tbinfo));
+		appendPQExpBuffer(query, "CREATE ");
+
+		if (global_temp_seq)
+			appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+		appendPQExpBuffer(query, "SEQUENCE %s\n",
+								  fmtQualifiedDumpable(tbinfo));
 
 		if (strcmp(seqtype, "bigint") != 0)
 			appendPQExpBuffer(query, "    AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index bc5fbd93c6c..2fe6a7c0959 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -88,7 +88,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -174,7 +174,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 1dbc09e6422..fb55f0b638e 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -298,9 +298,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
 {
 	int			dbnum;
 
@@ -310,7 +312,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -395,7 +397,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -438,8 +440,17 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
 			 "         ON c.relnamespace = n.oid "
 			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
+			 CppAsString2(RELKIND_MATVIEW) ") AND ");
+
+	if (skip_gtt)
+	{
+		/* exclude global temp tables */
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+			"    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND ");
+	}
+
 	/* exclude possible orphaned temp tables */
+	snprintf(query + strlen(query), sizeof(query) - strlen(query),
 			 "    ((n.nspname !~ '^pg_temp_' AND "
 			 "      n.nspname !~ '^pg_toast_temp_' AND "
 			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index f85cb2e2620..e3b0a1f161b 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -410,7 +410,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -648,7 +648,10 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+			/* exclude global temp tables */
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -659,7 +662,10 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+		/* exclude global temp tables */
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 22169f10021..fb4c1f748ca 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -378,7 +378,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
 void		print_maps(FileNameMap *maps, int n,
 					   const char *db_name);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index c28788e84fa..d086d2f9dfa 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3739,7 +3739,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		 * Show whether a relation is permanent, temporary, or unlogged.
 		 */
 		appendPQExpBuffer(&buf,
-						  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+						  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+						  gettext_noop("session"),
 						  gettext_noop("permanent"),
 						  gettext_noop("temporary"),
 						  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b524dc87fc1..73ca85d1fab 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1059,6 +1059,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2624,6 +2626,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
@@ -2854,6 +2859,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE", "SEQUENCE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 6ce480b49c1..dda3f3c5a60 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -85,7 +85,7 @@ extern Oid	heap_create_with_catalog(const char *relname,
 
 extern void heap_drop_with_catalog(Oid relid);
 
-extern void heap_truncate(List *relids);
+extern void heap_truncate(List *relids, bool is_global_temp);
 
 extern void heap_truncate_one_rel(Relation rel);
 
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 008f723e104..875b1003899 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -157,6 +157,7 @@ extern void reindex_index(Oid indexId, bool skip_constraint_checks,
 #define REINDEX_REL_CHECK_CONSTRAINTS		0x04
 #define REINDEX_REL_FORCE_INDEXES_UNLOGGED	0x08
 #define REINDEX_REL_FORCE_INDEXES_PERMANENT 0x10
+#define REINDEX_REL_PROCESS_GLOBAL_TEMP		0x20
 
 extern bool reindex_relation(Oid relid, int flags, ReindexParams *params);
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 93338d267c1..5237dd00921 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -172,6 +172,7 @@ DECLARE_INDEX(pg_class_tblspc_relfilenode_index, 3455, ClassTblspcRelfilenodeInd
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4d992dc2241..34465b2865d 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5729,6 +5729,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '9874',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '9875',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '9876',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '9877',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 0ab32b44e91..92e9f8ba485 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,7 +22,7 @@
 /* GUC variables */
 extern int	wal_skip_threshold;
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationPreTruncate(Relation rel);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 00000000000..cc023da8acd
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,45 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern int		vacuum_gtt_defer_check_age;
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Oid relid,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void force_enable_gtt_index(Relation index);
+extern void gtt_fix_index_backend_state(Relation index);
+extern void init_gtt_storage(CmdType operation, Relation relation);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index 40544dd4c70..7b66d808fc5 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 336549cc5f0..3e8167134b7 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -86,7 +86,7 @@ extern void find_composite_type_dependencies(Oid typeOid,
 
 extern void check_of_type(HeapTuple typetuple);
 
-extern void register_on_commit_action(Oid relid, OnCommitAction action);
+extern void register_on_commit_action(Oid relid, OnCommitAction action, bool is_gloal_temp);
 extern void remove_on_commit_action(Oid relid);
 
 extern void PreCommit_on_commit_actions(void);
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index cd57a704adc..717632637a9 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -575,7 +575,7 @@ exec_rt_fetch(Index rti, EState *estate)
 
 extern Relation ExecGetRangeTableRelation(EState *estate, Index rti);
 extern void ExecInitResultRelation(EState *estate, ResultRelInfo *resultRelInfo,
-								   Index rti);
+								   Index rti, CmdType operation);
 
 extern int	executor_errposition(EState *estate, int location);
 
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 8336c2c5a29..bddcfe7256d 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
 
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index c86ccdaf608..6b395551c1d 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -399,6 +399,8 @@ do { \
 #define PageClearPrunable(page) \
 	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
 
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+	((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
 
 /* ----------------------------------------------------------------
  *		extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index a8f052e4845..4b4ed1a13aa 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -189,6 +189,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_SHARED_TIDBITMAP,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_PER_XACT_PREDICATE_LIST,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 44b477f49d7..1d621e3290e 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -164,6 +164,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId backend_gtt_frozenxid;	/* backend level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index b01fa52139a..8efffa55ac5 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -94,4 +94,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index aa18d304ac0..524c9d7de3f 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -288,6 +288,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 31281279cf9..39925543ac5 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	SMgrRelation rd_smgr;		/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -327,6 +327,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -609,11 +610,13 @@ RelationGetSmgr(Relation rel)
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -621,6 +624,7 @@ RelationGetSmgr(Relation rel)
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -633,6 +637,30 @@ RelationGetSmgr(Relation rel)
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP_ON_CURRENT_SESSION
+ *		Test a rel is either local temp relation of this session
+ *		or global temp relation.
+ */
+#define RELATION_IS_TEMP_ON_CURRENT_SESSION(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is local temp relation or global temporary relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RelpersistenceTsTemp
+ *		Test a relpersistence is local temp relation or global temporary relation.
+ */
+#define RelpersistenceTsTemp(relpersistence) \
+	(relpersistence == RELPERSISTENCE_TEMP || \
+	 relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -678,6 +706,19 @@ RelationGetSmgr(Relation rel)
 	 (relation)->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&	\
 	 !IsCatalogRelation(relation))
 
+/* For global temporary table */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/* Get on commit clause value only for global temporary table */
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+/* Get relpersistence for relation */
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
-- 
2.32.0 (Apple Git-132)

#348Andrew Bille
andrewbille@gmail.com
In reply to: wenjing (#347)
Re: [Proposal] Global temporary tables

Hi!

I could not detect crashes with your last patch, so I think the patch is
ready for a review.
Please, also consider fixing error messages, as existing ones don't follow
message writing guidelines.
https://www.postgresql.org/docs/14/error-style-guide.html

Regards, Andrew

On Thu, Dec 23, 2021 at 7:36 PM wenjing <wjzeng2012@gmail.com> wrote:

Show quoted text

Andrew Bille <andrewbille@gmail.com> 于2021年12月21日周二 14:00写道:

Hi!
Thanks for new patches.
Yet another crash reproduced on master with v63 patches:

CREATE TABLESPACE ts LOCATION '/tmp/ts';
CREATE GLOBAL TEMP TABLE tbl (num1 bigint);
INSERT INTO tbl (num1) values (1);
CREATE INDEX tbl_idx ON tbl (num1);
REINDEX (TABLESPACE ts) TABLE tbl;

This is a feature made in PG14 that supports reindex change tablespaces.
Thank you for pointing that out and I fixed it in v64.
Waiting for your feedback.

#349Wenjing Zeng
wjzeng2012@gmail.com
In reply to: Andrew Bille (#348)
4 attachment(s)
Re: [Proposal] Global temporary tables

very glad to see your reply.
Thank you very much for your review of the code and found so many problems.
There was a conflict between the latest code and patch, I have corrected it
and provided a new patch (V65).
Waiting for your feedback.

Regards, Wenjing.

Andrew Bille <andrewbille@gmail.com> 于2022年1月10日周一 17:17写道:

Hi!

I could not detect crashes with your last patch, so I think the patch is
ready for a review.
Please, also consider fixing error messages, as existing ones don't follow
message writing guidelines.
https://www.postgresql.org/docs/14/error-style-guide.html

I corrected the ERROR message of GTT according to the link and the existing
error message.
Some comments and code refactoring were also done.

Show quoted text

Regards, Andrew

On Thu, Dec 23, 2021 at 7:36 PM wenjing <wjzeng2012@gmail.com> wrote:

Andrew Bille <andrewbille@gmail.com> 于2021年12月21日周二 14:00写道:

Hi!
Thanks for new patches.
Yet another crash reproduced on master with v63 patches:

CREATE TABLESPACE ts LOCATION '/tmp/ts';
CREATE GLOBAL TEMP TABLE tbl (num1 bigint);
INSERT INTO tbl (num1) values (1);
CREATE INDEX tbl_idx ON tbl (num1);
REINDEX (TABLESPACE ts) TABLE tbl;

This is a feature made in PG14 that supports reindex change tablespaces.
Thank you for pointing that out and I fixed it in v64.
Waiting for your feedback.

Attachments:

0001-gtt-v65-reademe.patchapplication/octet-stream; name=0001-gtt-v65-reademe.patchDownload
diff --git a/README.gtt.txt b/README.gtt.txt
new file mode 100644
index 00000000000..d181df9acd7
--- /dev/null
+++ b/README.gtt.txt
@@ -0,0 +1,172 @@
+Global Temporary Table(GTT)
+=========================================
+
+Feature description
+-----------------------------------------
+
+Previously, temporary tables are defined once and automatically
+exist (starting with empty contents) in every session before using them.
+
+The temporary table implementation in PostgreSQL, known as Local temp tables(LTT),
+did not fully comply with the SQL standard. This version added the support of
+Global Temporary Table .
+
+The metadata of Global Temporary Table is persistent and shared among sessions.
+The data stored in the Global temporary table is independent of sessions. This
+means, when a session creates a Global Temporary Table and writes some data.
+Other sessions cannot see those data, but they have an empty Global Temporary
+Table with same schema.
+
+Like local temporary table, Global Temporary Table supports ON COMMIT PRESERVE ROWS
+or ON COMMIT DELETE ROWS clause, so that data in the temporary table can be
+cleaned up or preserved automatically when a session exits or a transaction COMMITs.
+
+Unlike Local Temporary Table, Global Temporary Table does not support
+ON COMMIT DROP clauses.
+
+In following paragraphs, we use GTT for Global Temporary Table and LTT for
+local temporary table.
+
+Main design ideas
+-----------------------------------------
+In general, GTT and LTT use the same storage and buffer design and
+implementation. The storage files for both types of temporary tables are named
+as t_backendid_relfilenode, and the local buffer is used to cache the data.
+
+The schema of GTTs is shared among sessions while their data are not. We build
+a new mechanisms to manage those non-shared data and their statistics.
+Here is the summary of changes:
+
+1) CATALOG
+GTTs store session-specific data. The storage information of GTTs'data, their
+transaction information, and their statistics are not stored in the catalog.
+
+2) STORAGE INFO & STATISTICS INFO & TRANSACTION INFO
+In order to maintain durability and availability of GTTs'session-specific data,
+their storage information, statistics, and transaction information is managed
+in a local hash table tt_storage_local_hash.
+
+3) DDL
+Currently, GTT supports almost all table'DDL except CLUSTER/VACUUM FULL.
+Part of the DDL behavior is limited by shared definitions and multiple copies of
+local data, and we added some structures to handle this.
+
+A shared hash table active_gtt_shared_hash is added to track the state of the
+GTT in a different session. This information is recorded in the hash table
+during the DDL execution of the GTT.
+
+The data stored in a GTT can only be modified or accessed by owning session.
+The statements that only modify data in a GTT do not need a high level of
+table locking. The operations making those changes include truncate GTT,
+reindex GTT, and lock GTT.
+
+4) MVCC commit log(clog) cleanup
+Each GTT in a session has its own piece of data, and they have their own
+transaction information. We set up data structures to track and maintain
+this information. The cleaning of CLOGs also needs to consider the transaction
+information of GTT.
+
+Detailed design
+-----------------------------------------
+
+1. CATALOG
+1.1 relpersistence
+define RELPERSISTENCEGLOBALTEMP 'g'
+Mark Global Temporary Table in pg_class relpersistence to 'g'. The relpersistence
+of indexes created on the GTT, sequences on GTT and toast tables on GTT are
+also set to 'g'
+
+1.2 on commit clause
+LTT's status associated with on commit DELETE ROWS and on commit PRESERVE ROWS
+is not stored in catalog. Instead, GTTs need a bool value on_commit_delete_rows
+in reloptions which is shared among sessions.
+
+1.3 gram.y
+GTT is already supported in syntax tree. We remove the warning message
+"GLOBAL is deprecated in temporary table creation" and mark
+relpersistence = RELPERSISTENCEGLOBALTEMP.
+
+2. STORAGE INFO & STATISTICS INFO & TRANSACTION INFO
+2.1. gtt_storage_local_hash
+Each backend creates a local hashtable gtt_storage_local_hash to track a GTT's
+storage file information, statistics, and transaction information.
+
+2.2 GTT storage file info track
+1) When one session inserts data into a GTT for the first time, record the
+storage info to gtt_storage_local_hash.
+2) Use beforeshmemexit to ensure that all files of session GTT are deleted when
+the session exits.
+
+2.3 statistics info
+1) relpages reltuples relallvisible relfilenode
+2) The statistics of each column from pg_statistic
+All the above information is stored in gtt_storage_local_hash.
+When doing vacuum or analyze, GTT's statistic is updated, which is used by
+the SQL planner.
+The statistics summarizes only data in the current session.
+
+2.3 transaction info track
+frozenxid minmulti from pg_class is stored to gtt_storage_local_hash.
+
+3 DDL
+3.1. active_gtt_shared_hash
+This is the hash table created in shared memory to trace the GTT files initialized
+in each session. Each hash entry contains a bitmap that records the backendid of
+the initialized GTT file. With this hash table, we know which backend/session
+is using this GTT. Such information is used during GTT's DDL operations.
+
+3.2 DROP GTT
+One GTT is allowed to be deleted when there is only one session using the table
+and the session is the current session. After holding the lock on GTT,
+active_gtt_shared_hash is checked to ensure that.
+
+3.3 ALTER GTT/DROP INDEX ON GTT
+Same as drop GTT.
+
+3.4 CREATE INDEX ON GTT
+1) create index on GTT statements build index based on local data in a session.
+2) After the index is created, record the index metadata to the catalog.
+3) Other sessions can enable or disable the local GTT index.
+
+3.5 TRUNCATE/REINDEX GTT
+The SQL truncate/reindex command open the GTT using AccessShareLock lock,
+not AccessExclusiveLock, because this command only cleans up local data and
+local buffers in current session. This allows these operations to be executed
+concurrently between sessions, unlike normal tables.
+
+3.6 LOCK GTT
+A lock GTT statement does not hold any relation lock.
+
+3.7 CLUSTER GTT/VACUUM FULL GTT
+The current version does not support.
+
+4 MVCC commit log(clog) cleanup
+
+The GTT storage file contains transaction information. Queries for GTT data rely
+on transaction information such as clog. The transaction information required by
+each session may be completely different. We need to ensure that the transaction
+information of the GTT data is not cleaned up during its lifetime and that
+transaction resources are recycled at the instance level.
+
+4.1 The session level GTT oldest frozenxid
+1) To manage all GTT transaction information, add session level oldest frozenxid
+in each session. When one GTT is created or removed, record the session level
+oldest frozenxid and store it in MyProc.
+2) When vacuum advances the database's frozenxid, session level oldest frozenxid
+should be considered. This is acquired by searching all of MyProc. This way,
+we can avoid the clog required by GTTs to be cleaned.
+
+4.2 vacuum GTT
+Users can perform vacuum over a GTT to clean up local data in the GTT.
+
+4.3 autovacuum GTT
+Autovacuum skips all GTTs, because the data in GTTs is only visible in current session.
+
+5 OTHERS
+5.1 Parallel query
+Planner does not produce parallel query plans for SQL related to GTT. Because
+GTT private data cannot be accessed across processes.
+
+5.2 WAL and Logical replication
+Like LTT, the DML on GTT does not record WAL and is not parsed or replay by
+the logical replication.
\ No newline at end of file
-- 
2.30.1 (Apple Git-130)

0002-gtt-v65-doc.patchapplication/octet-stream; name=0002-gtt-v65-doc.patchDownload
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 473a0a4aeb..e510bde8ac 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -169,32 +169,67 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     <listitem>
      <para>
       If specified, the table is created as a temporary table.
-      Temporary tables are automatically dropped at the end of a
-      session, or optionally at the end of the current transaction
-      (see <literal>ON COMMIT</literal> below).  The default
-      search_path includes the temporary schema first and so identically
-      named existing permanent tables are not chosen for new plans
+      Optionally, <literal>GLOBAL</literal> or <literal>LOCAL</literal>
+      can be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
+      They represent two types of temporary tables supported by <productname>PostgreSQL</productname>:
+      global temporary table and local temporary table. Without specified
+      GLOBAL or LOCAL, a local temporary table is created by default.
+     </para>
+
+    <para>
+     Both types of temporary tables’ data are truncated at the
+     end of a session or optionally at the end of the current transaction.
+     (see <literal>ON COMMIT</literal> below). For global temporary table,
+     its schema is reserved and reused by future sessions or transactions.
+     For local temporary table, both its data and its schema are dropped.
+    </para>
+
+    <variablelist>
+     <varlistentry>
+      <term><literal>Global Temporary Table</literal></term>
+      <listitem>
+       <para>
+        Global temporary table are defined just once and automatically exist
+        (starting with empty contents) in every session that needs them.
+        The schema definition of temporary tables is persistent and shared among sessions.
+        However, the data in temporary tables are kept private to sessions themselves,
+        even though they use same name and same schema.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>Local Temporary Table</literal></term>
+     <listitem>
+     <para>
+      Local temporary table are automatically dropped at the end of a
+      session (include schema and data). Future sessions need to create
+      their own temporary tables when they are used.
+     </para>
+     <para>
+      The default search_path includes the temporary schema first and so
+      identically named existing permanent tables are not chosen for new plans
       while the temporary table exists, unless they are referenced
       with schema-qualified names. Any indexes created on a temporary
       table are automatically temporary as well.
      </para>
+     </listitem>
+     </varlistentry>
+    </variablelist>
 
-     <para>
-      The <link linkend="autovacuum">autovacuum daemon</link> cannot
-      access and therefore cannot vacuum or analyze temporary tables.
-      For this reason, appropriate vacuum and analyze operations should be
-      performed via session SQL commands.  For example, if a temporary
-      table is going to be used in complex queries, it is wise to run
-      <command>ANALYZE</command> on the temporary table after it is populated.
-     </para>
+    <para>
+     The <link linkend="autovacuum">autovacuum daemon</link> cannot
+     access and therefore cannot vacuum or analyze temporary tables.
+     For this reason, appropriate vacuum and analyze operations should be
+     performed via session SQL commands.  For example, if a temporary
+     table is going to be used in complex queries, it is wise to run
+     <command>ANALYZE</command> on the temporary table after it is populated.
+    </para>
+    <para>
+     The Temporary table resembles the SQL standard, but has some differences.
+     see <xref linkend="sql-createtable-compatibility"/> below.
+    </para>
 
-     <para>
-      Optionally, <literal>GLOBAL</literal> or <literal>LOCAL</literal>
-      can be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
-      This presently makes no difference in <productname>PostgreSQL</productname>
-      and is deprecated; see
-      <xref linkend="sql-createtable-compatibility"/> below.
-     </para>
     </listitem>
    </varlistentry>
 
@@ -2133,13 +2168,17 @@ CREATE TABLE cities_partdef
    <title>Temporary Tables</title>
 
    <para>
-    Although the syntax of <literal>CREATE TEMPORARY TABLE</literal>
-    resembles that of the SQL standard, the effect is not the same.  In the
-    standard,
-    temporary tables are defined just once and automatically exist (starting
-    with empty contents) in every session that needs them.
-    <productname>PostgreSQL</productname> instead
-    requires each session to issue its own <literal>CREATE TEMPORARY
+    Although the syntax of <literal>CREATE GLOBAL/LOCAL TEMPORARY TABLE</literal>
+    resembles that of the SQL standard, the effect is not the same.
+    The global temporary table follows the SQL standards while local temporary
+    table does not.
+   </para>
+
+   <para>
+    First, in the standard, both global and local temporary tables are defined just
+    once and automatically exist (starting with empty contents) in every session
+    that needs them. For local temporary tables, <productname>PostgreSQL</productname>
+    instead requires each session to issue its own <literal>CREATE LOCAL TEMPORARY
     TABLE</literal> command for each temporary table to be used.  This allows
     different sessions to use the same temporary table name for different
     purposes, whereas the standard's approach constrains all instances of a
@@ -2147,29 +2186,14 @@ CREATE TABLE cities_partdef
    </para>
 
    <para>
-    The standard's definition of the behavior of temporary tables is
-    widely ignored.  <productname>PostgreSQL</productname>'s behavior
-    on this point is similar to that of several other SQL databases.
-   </para>
-
-   <para>
-    The SQL standard also distinguishes between global and local temporary
+    Second, the SQL standard distinguishes between global and local temporary
     tables, where a local temporary table has a separate set of contents for
     each SQL module within each session, though its definition is still shared
-    across sessions.  Since <productname>PostgreSQL</productname> does not
+    across sessions. Since <productname>PostgreSQL</productname> does not
     support SQL modules, this distinction is not relevant in
     <productname>PostgreSQL</productname>.
    </para>
 
-   <para>
-    For compatibility's sake, <productname>PostgreSQL</productname> will
-    accept the <literal>GLOBAL</literal> and <literal>LOCAL</literal> keywords
-    in a temporary table declaration, but they currently have no effect.
-    Use of these keywords is discouraged, since future versions of
-    <productname>PostgreSQL</productname> might adopt a more
-    standard-compliant interpretation of their meaning.
-   </para>
-
    <para>
     The <literal>ON COMMIT</literal> clause for temporary tables
     also resembles the SQL standard, but has some differences.
@@ -2177,7 +2201,8 @@ CREATE TABLE cities_partdef
     default behavior is <literal>ON COMMIT DELETE ROWS</literal>.  However, the
     default behavior in <productname>PostgreSQL</productname> is
     <literal>ON COMMIT PRESERVE ROWS</literal>.  The <literal>ON COMMIT
-    DROP</literal> option does not exist in SQL.
+    DROP</literal> option does not exist in SQL and is not supported by
+    global temporary table.
    </para>
   </refsect2>
 
-- 
2.30.1 (Apple Git-130)

0003-gtt-v65-implementation.patchapplication/octet-stream; name=0003-gtt-v65-implementation.patchDownload
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index f996f9a5727..18f1f7a42c0 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -18,6 +18,7 @@
 #include "access/toast_internals.h"
 #include "access/visibilitymap.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "funcapi.h"
 #include "miscadmin.h"
 #include "storage/bufmgr.h"
@@ -340,6 +341,13 @@ verify_heapam(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(ctx.rel) &&
+		!gtt_storage_attached(RelationGetRelid(ctx.rel)))
+	{
+		relation_close(ctx.rel, AccessShareLock);
+		PG_RETURN_NULL();
+	}
+
 	/* Early exit if the relation is empty */
 	nblocks = RelationGetNumberOfBlocks(ctx.rel);
 	if (!nblocks)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index d592655258a..051fe802824 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -159,6 +159,18 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * For global temporary table save on commit clause info to reloptions.
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temporary table on commit options",
+			RELOPT_KIND_HEAP,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},
 	/* list terminator */
 	{{NULL}}
 };
@@ -1834,6 +1846,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 d4bf0c7563d..b623e958c38 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1024,7 +1024,7 @@ gistproperty(Oid index_oid, int attno,
 XLogRecPtr
 gistGetFakeLSN(Relation rel)
 {
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	if (RELATION_IS_TEMP(rel))
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index d48c8a45498..a6afb55fbc0 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -152,7 +152,7 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	if (!RELATION_IS_TEMP(index))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 6ec57f3d8b2..7a143fe545a 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -5835,6 +5835,7 @@ heap_abort_speculative(Relation relation, ItemPointer tid)
 	Buffer		buffer;
 	TransactionId prune_xid;
 
+	Assert(TransactionIdIsNormal(relation->rd_rel->relfrozenxid));
 	Assert(ItemPointerIsValid(tid));
 
 	block = ItemPointerGetBlockNumber(tid);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 39ef8a0b77d..0bb308d1c5e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -593,7 +593,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
@@ -645,7 +645,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(RelationGetSmgr(rel), dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 1749cc2a476..de5914db777 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -324,6 +324,9 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 	TransactionId FreezeLimit;
 	MultiXactId MultiXactCutoff;
 
+	Assert(TransactionIdIsNormal(rel->rd_rel->relfrozenxid));
+        Assert(MultiXactIdIsValid(rel->rd_rel->relminmxid));
+
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (IsAutoVacuumWorkerProcess() &&
 							  params->log_min_duration >= 0));
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 6b5f01e1d07..099f942c936 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -677,6 +678,14 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		/*
+		 * If a global temporary table storage file is not initialized in the
+		 * this session, its index does not have a root page, just returns 0.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index df5268fbc30..8b8a60c2ac5 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -44,6 +44,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index dfd5fb669ee..c0fd68652f7 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -504,6 +504,7 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 7e99de88b34..019e1105bc4 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -62,6 +62,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"
@@ -102,6 +103,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -349,8 +351,21 @@ heap_create(const char *relname,
 	if (!RELKIND_HAS_TABLESPACE(relkind))
 		reltablespace = InvalidOid;
 
+	/*
+	 * For create global temporary table, initialization storage information
+	 * and recorded in into pg_class, but not initialization stroage file.
+	 * When data is inserted into a temporary table, its storage file is initialized.
+	 */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		create_storage = false;
+		if (OidIsValid(relfilenode))
+			elog(ERROR, "global temporary table can not reuse an existing relfilenode");
+
+		relfilenode = relid;
+	}
 	/* Don't create storage for relkinds without physical storage. */
-	if (!RELKIND_HAS_STORAGE(relkind))
+	else if (!RELKIND_HAS_STORAGE(relkind))
 		create_storage = false;
 	else
 	{
@@ -403,7 +418,7 @@ heap_create(const char *relname,
 											relpersistence,
 											relfrozenxid, relminmxid);
 		else if (RELKIND_HAS_STORAGE(rel->rd_rel->relkind))
-			RelationCreateStorage(rel->rd_node, relpersistence);
+			RelationCreateStorage(rel->rd_node, relpersistence, rel);
 		else
 			Assert(false);
 	}
@@ -970,6 +985,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -995,8 +1011,21 @@ AddNewRelationTuple(Relation pg_class_desc,
 		new_rel_reltup->reltuples = 1;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/*
+	 * The transaction information of the global temporary table is stored
+	 * in hash table, not in pg_class.
+	 */
+	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;
@@ -1404,6 +1433,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1486,10 +1516,13 @@ 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
+	 * For local temporary table, if there's a special on-commit action, remember it.
+	 * For global temporary table, on-commit action is recorded during initial storage.
+	 * See remember_gtt_storage_info.
 	 */
-	if (oncommit != ONCOMMIT_NOOP)
-		register_on_commit_action(relid, oncommit);
+	if (oncommit != ONCOMMIT_NOOP &&
+		relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+		register_on_commit_action(relid, oncommit, false);
 
 	/*
 	 * ok, the relation has been cataloged, so close our relations and return
@@ -1986,6 +2019,13 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/*
+	 * Only when other sessions are not using this global temporary table,
+	 * is it allowed to drop it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		CheckGlobalTempTableNotInUse(rel, "DROP GLOBAL TEMPORARY TABLE");
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3272,7 +3312,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3284,7 +3324,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3320,31 +3360,49 @@ RelationTruncateIndexes(Relation heapRelation)
  * ON COMMIT truncation of temporary tables, where it doesn't matter.
  */
 void
-heap_truncate(List *relids)
+heap_truncate(List *relids, bool is_global_temp)
 {
 	List	   *relations = NIL;
-	ListCell   *cell;
+	List	   *lock_modes = NIL;
+	ListCell   *cell_rel;
+	ListCell   *cell_lock;
 
 	/* Open relations for processing, and grab exclusive access on each */
-	foreach(cell, relids)
+	foreach(cell_rel, relids)
 	{
-		Oid			rid = lfirst_oid(cell);
+		Oid			rid = lfirst_oid(cell_rel);
 		Relation	rel;
+		LOCKMODE	lockmode;
+
+		/*
+		 * Truncate global temporary table only clears local data,
+		 * so only low-level locks need to be held.
+		 */
+		if (is_global_temp)
+		{
+			lockmode = RowExclusiveLock;
+			if (!gtt_storage_attached(rid))
+				continue;
+		}
+		else
+			lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
+		lock_modes = lappend_int(lock_modes, lockmode);
 	}
 
 	/* Don't allow truncate on tables that are referenced by foreign keys */
 	heap_truncate_check_FKs(relations, true);
 
 	/* OK to do it */
-	foreach(cell, relations)
+	forboth(cell_rel, relations, cell_lock, lock_modes)
 	{
-		Relation	rel = lfirst(cell);
+		Relation	rel = lfirst(cell_rel);
+		LOCKMODE	lockmode = lfirst_int(cell_lock);
 
 		/* Truncate the relation */
-		heap_truncate_one_rel(rel);
+		heap_truncate_one_rel(rel, lockmode);
 
 		/* Close the relation, but keep exclusive lock on it until commit */
 		table_close(rel, NoLock);
@@ -3361,7 +3419,7 @@ heap_truncate(List *relids)
  * checked permissions etc, and must have obtained AccessExclusiveLock.
  */
 void
-heap_truncate_one_rel(Relation rel)
+heap_truncate_one_rel(Relation rel, LOCKMODE lockmode)
 {
 	Oid			toastrelid;
 
@@ -3376,16 +3434,16 @@ heap_truncate_one_rel(Relation rel)
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
@@ -3856,3 +3914,15 @@ StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
 
 	CacheInvalidateRelcache(parent);
 }
+
+void
+CheckGlobalTempTableNotInUse(Relation rel, const char *stmt)
+{
+	if (RELATION_IS_GLOBAL_TEMP(rel) &&
+		is_other_backend_use_gtt(RelationGetRelid(rel)))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_IN_USE),
+				 errmsg("cannot %s \"%s\" because it is being used by other session",
+						stmt, RelationGetRelationName(rel)),
+				 errdetail("Please try detach all other sessions using this table and try again.")));
+}
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index e4203819a0e..17b79212365 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -54,6 +54,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"
@@ -125,7 +126,8 @@ static void UpdateIndexRelation(Oid indexoid, Oid heapoid,
 								bool isready);
 static void index_update_stats(Relation rel,
 							   bool hasindex,
-							   double reltuples);
+							   double reltuples,
+							   bool isreindex);
 static void IndexCheckExclusion(Relation heapRelation,
 								Relation indexRelation,
 								IndexInfo *indexInfo);
@@ -736,6 +738,21 @@ index_create(Relation heapRelation,
 	MultiXactId relminmxid;
 	bool		create_storage = !OidIsValid(relFileNode);
 
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* disable create index on global temporary table with concurrent mode */
+		concurrent = false;
+
+		/*
+		 * For the case that some backend is applied relcache message to create
+		 * an index on a global temporary table, if this table in the current
+		 * backend are not initialized, the creation of index storage on the
+		 * table are also skipped.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/* constraint flags can only be set when a constraint is requested */
 	Assert((constr_flags == 0) ||
 		   ((flags & INDEX_CREATE_ADD_CONSTRAINT) != 0));
@@ -1242,7 +1259,8 @@ index_create(Relation heapRelation,
 		 */
 		index_update_stats(heapRelation,
 						   true,
-						   -1.0);
+						   -1.0,
+						   false);
 		/* Make the above update visible */
 		CommandCounterIncrement();
 	}
@@ -2138,7 +2156,7 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 * lock (see comments in RemoveRelations), and a non-concurrent DROP is
 	 * more efficient.
 	 */
-	Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+	Assert(!RelpersistenceTsTemp(get_rel_persistence(indexId)) ||
 		   (!concurrent && !concurrent_lock_mode));
 
 	/*
@@ -2170,6 +2188,14 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/*
+	 * Allow to drop index on global temporary table when only current
+	 * backend use it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation))
+		CheckGlobalTempTableNotInUse(userHeapRelation,
+									 "DROP INDEX ON GLOBAL TEMPORARY TABLE");
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2771,7 +2797,8 @@ FormIndexDatum(IndexInfo *indexInfo,
 static void
 index_update_stats(Relation rel,
 				   bool hasindex,
-				   double reltuples)
+				   double reltuples,
+				   bool isreindex)
 {
 	Oid			relid = RelationGetRelid(rel);
 	Relation	pg_class;
@@ -2779,6 +2806,13 @@ index_update_stats(Relation rel,
 	Form_pg_class rd_rel;
 	bool		dirty;
 
+	/*
+	 * Most of the global Temp table data is updated to the local hash, and reindex
+	 * does not refresh relcache, so call a separate function.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		return index_update_gtt_relstats(rel, hasindex, reltuples, isreindex);
+
 	/*
 	 * We always update the pg_class row using a non-transactional,
 	 * overwrite-in-place update.  There are several reasons for this:
@@ -2998,6 +3032,25 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, progress_index, progress_vals);
 	}
 
+	/* For build index on global temporary table */
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		/*
+		 * If the storage for the index in this session is not initialized,
+		 * it needs to be created.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			/* Before create init storage, fix the local Relcache first */
+			force_enable_gtt_index(indexRelation);
+
+			Assert(gtt_storage_attached(RelationGetRelid(heapRelation)));
+
+			/* Init storage for index */
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3080,11 +3133,13 @@ index_build(Relation heapRelation,
 	 */
 	index_update_stats(heapRelation,
 					   true,
-					   stats->heap_tuples);
+					   stats->heap_tuples,
+					   isreindex);
 
 	index_update_stats(indexRelation,
 					   false,
-					   stats->index_tuples);
+					   stats->index_tuples,
+					   isreindex);
 
 	/* Make the updated catalog row versions visible */
 	CommandCounterIncrement();
@@ -3539,6 +3594,8 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	PGRUsage	ru0;
 	bool		progress = ((params->options & REINDEXOPT_REPORT_PROGRESS) != 0);
 	bool		set_tablespace = false;
+	LOCKMODE	lockmode_on_heap = ShareLock;
+	LOCKMODE	lockmode_on_index = AccessExclusiveLock;
 
 	pg_rusage_init(&ru0);
 
@@ -3552,10 +3609,35 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	if (!OidIsValid(heapId))
 		return;
 
+	/*
+	 * For reindex on global temporary table, If the storage for the index
+	 * in current session is not initialized, nothing is done.
+	 */
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		if (OidIsValid(params->tablespaceOid))
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot change tablespace of global temporary table")));
+
+		if (!gtt_storage_attached(indexId))
+		{
+			/* Suppress use of the target index while rebuilding it */
+			SetReindexProcessing(heapId, indexId);
+			/* Re-allow use of target index */
+			ResetReindexProcessing();
+			return;
+		}
+
+		/* For global temp table reindex handles local data, using low-level locks */
+		lockmode_on_heap = AccessShareLock;
+		lockmode_on_index = AccessShareLock;
+	}
+
 	if ((params->options & REINDEXOPT_MISSING_OK) != 0)
-		heapRelation = try_table_open(heapId, ShareLock);
+		heapRelation = try_table_open(heapId, lockmode_on_heap);
 	else
-		heapRelation = table_open(heapId, ShareLock);
+		heapRelation = table_open(heapId, lockmode_on_heap);
 
 	/* if relation is gone, leave */
 	if (!heapRelation)
@@ -3581,7 +3663,7 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	 * Open the target index relation and get an exclusive lock on it, to
 	 * ensure that no one else is touching this particular index.
 	 */
-	iRel = index_open(indexId, AccessExclusiveLock);
+	iRel = index_open(indexId, lockmode_on_index);
 
 	if (progress)
 		pgstat_progress_update_param(PROGRESS_CREATEIDX_ACCESS_METHOD_OID,
@@ -3823,7 +3905,7 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
  * index rebuild.
  */
 bool
-reindex_relation(Oid relid, int flags, ReindexParams *params)
+reindex_relation(Oid relid, int flags, ReindexParams *params, LOCKMODE lockmode)
 {
 	Relation	rel;
 	Oid			toast_relid;
@@ -3839,9 +3921,9 @@ reindex_relation(Oid relid, int flags, ReindexParams *params)
 	 * should match ReindexTable().
 	 */
 	if ((params->options & REINDEXOPT_MISSING_OK) != 0)
-		rel = try_table_open(relid, ShareLock);
+		rel = try_table_open(relid, lockmode);
 	else
-		rel = table_open(relid, ShareLock);
+		rel = table_open(relid, lockmode);
 
 	/* if relation is gone, leave */
 	if (!rel)
@@ -3948,7 +4030,7 @@ reindex_relation(Oid relid, int flags, ReindexParams *params)
 
 		newparams.options &= ~(REINDEXOPT_MISSING_OK);
 		newparams.tablespaceOid = InvalidOid;
-		result |= reindex_relation(toast_relid, flags, &newparams);
+		result |= reindex_relation(toast_relid, flags, &newparams, lockmode);
 	}
 
 	return result;
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 5dbac9c437a..033fef26fe3 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -656,6 +656,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/* Do not allow create global temporary table in temporary schemas */
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temporary relation in temporary schema")));
+			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 9b8075536a7..733d89c0e8a 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,10 +27,12 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
 #include "utils/hsearch.h"
+#include "utils/inval.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
 
@@ -61,6 +63,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			temprelOid;			/* InvalidOid if not a global temporary rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -115,7 +118,7 @@ AddPendingSync(const RelFileNode *rnode)
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -126,7 +129,12 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/*
+		 * global temporary table and local temporary table use same
+		 * design on storage module.
+		 */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -154,6 +162,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
@@ -165,6 +174,30 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		AddPendingSync(&rnode);
 	}
 
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		Assert(rel && RELATION_IS_GLOBAL_TEMP(rel));
+
+		/*
+		 * Remember the reloid of global temporary table, which is used for
+		 * transaction commit or rollback.
+		 * see smgrDoPendingDeletes.
+		 */
+		pending->temprelOid = RelationGetRelid(rel);
+
+		/* Remember global temporary table storage info to localhash */
+		remember_gtt_storage_info(rnode, rel);
+
+		/* Make cache invalid and set new relnode to local relcache. */
+		CacheInvalidateRelcache(rel);
+
+		/*
+		 * Make the pg_class row change or relation map change visible.  This will
+		 * cause the relcache entry to get updated, too.
+		 */
+		CommandCounterIncrement();
+	}
+
 	return srel;
 }
 
@@ -201,11 +234,20 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/*
+	 * Remember the reloid of global temporary table, which is used for
+	 * transaction commit or rollback.
+	 * see smgrDoPendingDeletes.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->temprelOid = RelationGetRelid(rel);
+
 	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
@@ -618,6 +660,7 @@ smgrDoPendingDeletes(bool isCommit)
 	int			nrels = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -647,14 +690,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->temprelOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -664,12 +711,21 @@ smgrDoPendingDeletes(bool isCommit)
 
 	if (nrels > 0)
 	{
+		int	i;
+
 		smgrdounlinkall(srels, nrels, false);
 
-		for (int i = 0; i < nrels; i++)
+		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* free global temporary table info in localhash */
+			if (gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 00000000000..ea349a78072
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1669 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  The body implementation of Global Temparary table.
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *	  See src/backend/catalog/GTT_README for Global temparary table's
+ *	  requirements and design.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/heapam.h"
+#include "access/multixact.h"
+#include "access/visibilitymap.h"
+#include "catalog/catalog.h"
+#include "catalog/index.h"
+#include "catalog/pg_statistic.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
+#include "commands/tablecmds.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "nodes/pg_list.h"
+#include "storage/bufmgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/sinvaladt.h"
+#include "utils/catcache.h"
+#include "utils/guc.h"
+#include "utils/inval.h"
+#include "utils/syscache.h"
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_info_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+
+int		vacuum_gtt_defer_check_age = 0;
+
+/*
+ * The Global temporary table's shared hash table data structure
+ */
+typedef struct gtt_ctl_data
+{
+	LWLock		lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+/* record this global temporary table in which backends are being used */
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+/*
+ * The Global temporary table's local hash table data structure
+ */
+/* Record the storage information and statistical information of the global temporary table */
+typedef struct
+{
+	Oid			relfilenode;
+	Oid			spcnode;
+
+	/* pg_class relstat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+
+	/* pg_statistic column stat */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_free_statistics(gtt_relfilenode *rnode);
+static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, Oid spcnode, bool missing_ok);
+static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok);
+static Bitmapset *copy_active_gtt_bitmap(Oid relid);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+/*
+ * Calculate shared hash table entry size for GTT.
+ */
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(MaxBackends + 1);
+	/* hash entry header size */
+	hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+	/*
+	 * hash entry data size
+	 * this is a bitmap in shared memory, each backend have a bit.
+	 */
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+/*
+ * Calculate shared hash table max size for GTT.
+ */
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	/* shared hash header size */
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	/* hash entry size */
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	/* max size */
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+/*
+ * Initialization shared hash table for GTT.
+ */
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl =
+		ShmemInitStruct("gtt_shared_ctl",
+						sizeof(gtt_ctl_data),
+						&found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GLOBAL_TEMP_TABLE_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&gtt_shared_ctl->lock, LWTRANCHE_GLOBAL_TEMP_TABLE_CTL);
+		gtt_shared_ctl->max_entry = max_active_gtt;
+		gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+	}
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(gtt_fnode);
+	info.entrysize = action_gtt_shared_hash_entry_size();
+	active_gtt_shared_hash =
+		ShmemInitHash("active gtt shared hash",
+						gtt_shared_ctl->max_entry,
+						gtt_shared_ctl->max_entry,
+						&info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+/*
+ * Record GTT relid to shared hash table, which means that current session is using this GTT.
+ */
+static void
+gtt_storage_checkin(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			found;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *)&(fnode), 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_global_temporary_table.")));
+	}
+
+	if (!found)
+	{
+		int			wordnum;
+
+		/* init bitmap */
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	/* record itself in bitmap */
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+/*
+ * Remove the GTT relid record from the shared hash table which means that current session is
+ * not use this GTT.
+ */
+static void
+gtt_storage_checkout(Oid relid, bool isCommit)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		if (isCommit)
+			elog(WARNING, "relid %u not exist in gtt shared hash when drop local storage", relid);
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+	/* remove itself from bitmap */
+	bms_del_member(entry->map, MyBackendId);
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return;
+}
+
+/*
+ * Gets usage information for a GTT from shared hash table.
+ * The information is in the form of bitmap.
+ * Quickly copy the entire bitmap from shared memory and return it.
+ * that to avoid holding locks for a long time.
+ */
+static Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	Bitmapset		*map_copy = NULL;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry)
+	{
+		Assert(entry->map);
+		/* copy the entire bitmap */
+		if (!bms_is_empty(entry->map))
+			map_copy = bms_copy(entry->map);
+	}
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return map_copy;
+}
+
+/*
+ * Check if there are other sessions using this GTT besides the current session.
+ */
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			in_use = false;
+	int			num_use = 0;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+						(void *) &(fnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		return false;
+	}
+
+	Assert(entry->map);
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+	/* how many backend are using this GTT */
+	num_use = bms_num_members(entry->map);
+	if (num_use == 0)
+		in_use = false;
+	else if (num_use == 1)
+	{
+		/* check if this is itself */
+		if(bms_is_member(MyBackendId, entry->map))
+			in_use = false;
+		else
+			in_use = true;
+	}
+	else
+		in_use = true;
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return in_use;
+}
+
+/*
+ * Record GTT information to local hash.
+ * They include GTT storage info, transaction info and statistical info.
+ */
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+	gtt_local_hash_entry		*entry;
+	MemoryContext			oldcontext;
+	gtt_relfilenode			*new_node = NULL;
+	Oid				relid = RelationGetRelid(rel);
+	int				natts = 0;
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+	/* First time through: initialize the hash table */
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		HASHCTL		ctl;
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_info_context =
+			AllocSetContextCreate(CacheMemoryContext,
+								"gtt info context",
+								ALLOCSET_DEFAULT_SIZES);
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		ctl.hcxt = gtt_info_context;
+		gtt_storage_local_hash =
+			hash_create("global temporary table info",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+	}
+
+	Assert(CacheMemoryContext);
+	Assert(gtt_info_context);
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	entry = gtt_search_by_relid(relid, true);
+	if (!entry)
+	{
+		bool		found = false;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash,
+						(void *) &relid, HASH_ENTER, &found);
+
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			/* record the on commit clause */
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS, true);
+			}
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+		{
+			gtt_storage_checkin(relid);
+		}
+	}
+
+	/* record storage info relstat columnstats and transaction info to relfilenode list */
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	new_node->natts = 0;
+	new_node->attnum = NULL;
+	new_node->att_stat_tups = NULL;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* init column stats structure */
+	natts = RelationGetNumberOfAttributes(rel);
+	new_node->attnum = palloc0(sizeof(int) * natts);
+	new_node->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+	new_node->natts = natts;
+
+	/* remember transaction info */
+	if (RELKIND_HAS_TABLE_AM(entry->relkind))
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+
+	/* Registration callbacks are used to trigger cleanup during process exit */
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+/*
+ * Remove GTT information from local hash when transaction commit/rollback.
+ */
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	gtt_relfilenode		*d_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+		return;
+	}
+
+	d_rnode = gtt_search_relfilenode(entry, rnode.relNode, rnode.spcNode, true);
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+		else
+		{
+			/* rollback transaction */
+			if (entry->relfilenode_list == NIL)
+			{
+				if (entry->relkind == RELKIND_RELATION ||
+					entry->relkind == RELKIND_SEQUENCE)
+					gtt_storage_checkout(relid, isCommit);
+
+				hash_search(gtt_storage_local_hash,
+						(void *) &(relid), HASH_REMOVE, NULL);
+			}
+
+			return;
+		}
+	}
+
+	/* Clean up transaction info from Local order list and MyProc */
+	if (entry->relkind == RELKIND_RELATION ||
+		entry->relkind == RELKIND_TOASTVALUE)
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+
+		/* this is valid relfrozenxid */
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	/* delete relfilenode from rel entry */
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	gtt_free_statistics(d_rnode);
+
+	if (entry->relfilenode_list == NIL)
+	{
+		/* tell shared hash that current session will no longer use this GTT */
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(relid, isCommit);
+
+		hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_REMOVE, NULL);
+	}
+
+	return;
+}
+
+/*
+ * Check if current session is using this GTT.
+ */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool			found = false;
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (!OidIsValid(relid))
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry)
+		found = true;
+
+	return found;
+}
+
+/*
+ * When backend exit, bulk cleaning all GTT storage and local buffer of this backend.
+ */
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS			status;
+	gtt_local_hash_entry	*entry;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	/* Need to ensure we have a usable transaction. */
+	AbortOutOfAnyTransaction();
+
+	/* Search all relfilenode for GTT in current session */
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel[1];
+			RelFileNode		rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel[0] = smgropen(rnode, MyBackendId);
+			smgrdounlinkall(srel, 1, false);
+			smgrclose(srel[0]);
+		}
+
+		if (entry->relkind == RELKIND_RELATION ||
+			entry->relkind == RELKIND_SEQUENCE)
+			gtt_storage_checkout(entry->relid, false);
+
+		hash_search(gtt_storage_local_hash, (void *) &(entry->relid), HASH_REMOVE, NULL);
+	}
+
+	/* set to global area */
+	MyProc->gtt_frozenxid = InvalidTransactionId;
+
+	return;
+}
+
+/*
+ * Update GTT relstats(relpage/reltuple/relallvisible)
+ * to local hash.
+ */
+void
+gtt_update_relstats(Relation relation, BlockNumber relpages, double reltuples,
+						BlockNumber relallvisible, TransactionId relfrozenxid,
+						TransactionId relminmxid)
+{
+	Oid						relid = RelationGetRelid(relation);
+	gtt_relfilenode			*gtt_rnode = NULL;
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (!OidIsValid(relid))
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (relpages > 0 &&
+		gtt_rnode->relpages != (int32)relpages)
+	{
+		gtt_rnode->relpages = (int32)relpages;
+		relation->rd_rel->relpages = (int32) relpages;
+	}
+
+	if (reltuples > 0 &&
+		gtt_rnode->reltuples != (float4)reltuples)
+	{
+		gtt_rnode->reltuples = (float4)reltuples;
+		relation->rd_rel->reltuples = (float4)reltuples;
+	}
+
+	/* only heap contain transaction information and relallvisible */
+	if (RELKIND_HAS_TABLE_AM(entry->relkind))
+	{
+		if (relallvisible > 0 &&
+			gtt_rnode->relallvisible != (int32)relallvisible)
+		{
+			gtt_rnode->relallvisible = (int32)relallvisible;
+			relation->rd_rel->relallvisible = (int32)relallvisible;
+		}
+
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNextTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			/* set to local order list */
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			/* set to global area */
+			set_gtt_session_relfrozenxid();
+			relation->rd_rel->relfrozenxid = relfrozenxid;
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+			relation->rd_rel->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search GTT relstats(relpage/reltuple/relallvisible)
+ * from local has.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update GTT info(definition is same as pg_statistic)
+ * to local hash.
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+	MemoryContext		oldcontext;
+	bool		found = false;
+	int			i = 0;
+
+	/* not support whole row or system column */
+	if (attnum <= 0)
+		return;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return;
+
+	Assert(entry->relid == reloid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	/* switch context to gtt_info_context for store tuple at heap_form_tuple */
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == 0)
+		{
+			Assert(gtt_rnode->att_stat_tups[i] == NULL);
+			gtt_rnode->attnum[i] = attnum;
+			gtt_rnode->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+			found = true;
+			break;
+		}
+		else if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			heap_freetuple(gtt_rnode->att_stat_tups[i]);
+			gtt_rnode->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+			found = true;
+			break;
+		}
+	}
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!found)
+		elog(WARNING, "analyze can not update relid %u column %d statistics after add or drop column, try truncate table first", reloid, attnum);
+
+	return;
+}
+
+/*
+ * Search GTT statistic info(definition is same as pg_statistic)
+ * from local hash.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	int			i = 0;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	/* not support whole row or system column */
+	if (attnum <= 0)
+		return NULL;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return NULL;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return NULL;
+
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			return gtt_rnode->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Insert a RelfrozenXID into the list and keep the list in order.
+ */
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int		i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Remove a RelfrozenXID from order list gtt_session_relfrozenxid_list.
+ */
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+/*
+ * Update of backend Level oldest relfrozenxid to MyProc.
+ * This makes each backend's oldest RelFrozenxID globally visible.
+ */
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list != NIL)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	if (MyProc->gtt_frozenxid != gtt_frozenxid)
+		MyProc->gtt_frozenxid = gtt_frozenxid;
+}
+
+/*
+ * Get GTT column level data statistics.
+ */
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate		*tupstore;
+	HeapTuple		tuple;
+	Relation		rel = NULL;
+	Oid			reloid = PG_GETARG_OID(0);
+	int			attnum = PG_GETARG_INT32(1);
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("materialize mode required, but it is not " \
+				"allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+				rsinfo->econtext->ecxt_per_query_memory);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	if (!RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	/* get data from local hash */
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum		values[Natts_pg_statistic];
+		bool		isnull[Natts_pg_statistic];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, AccessShareLock);
+	relation_close(pg_tatistic, AccessShareLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get GTT table level data statistics.
+ */
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate	*tupstore;
+	TupleDesc	tupdesc;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid			reloid = PG_GETARG_OID(0);
+	Oid			relnode = 0;
+	BlockNumber		relpages = 0;
+	BlockNumber		relallvisible = 0;
+	uint32			relfrozenxid = 0;
+	uint32			relminmxid = 0;
+	double			reltuples = 0;
+	Relation		rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	if (!RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[6];
+		bool	isnull[6];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get a list of backend pids that are currently using this GTT.
+ */
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	PGPROC			*proc = NULL;
+	Bitmapset		*map = NULL;
+	Tuplestorestate		*tupstore;
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	HeapTuple		tuple;
+	Oid			reloid = PG_GETARG_OID(0);
+	Relation		rel = NULL;
+	int				backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	if (!RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	/* get data from share hash */
+	map = copy_active_gtt_bitmap(reloid);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			/* backendid map to process pid */
+			proc = BackendIdGetProc(backendid);
+			if (proc && proc->pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+				pid_t	pid = proc->pid;
+
+				memset(isnull, 0, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, AccessShareLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get backend level oldest relfrozenxid of each backend using GTT in current database.
+ */
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate		*tupstore;
+	TupleDesc		tupdesc;
+	MemoryContext	oldcontext;
+	TransactionId	oldest = InvalidTransactionId;
+	List			*pids = NULL;
+	List			*xids = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	/* Get all session level oldest relfrozenxid that in current database use global temp table */
+	oldest = gtt_get_oldest_frozenxids_in_current_database(&pids, &xids);
+	if (TransactionIdIsValid(oldest))
+	{
+		HeapTuple		tuple;
+		ListCell		*lc1 = NULL;
+		ListCell		*lc2 = NULL;
+
+		/* Save db level oldest relfrozenxid */
+		pids = lappend_int(pids, 0);
+		xids = lappend_oid(xids, oldest);
+
+		Assert(list_length(pids) == list_length(xids));
+		forboth(lc1, pids, lc2, xids)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, 0, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(lfirst_int(lc1));
+			values[1] = UInt32GetDatum(lfirst_oid(lc2));
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	list_free(pids);
+	list_free(xids);
+
+	return (Datum) 0;
+}
+
+/*
+ * In order to build the GTT index, force enable GTT'index.
+ */
+void
+force_enable_gtt_index(Relation index)
+{
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(RelationGetRelid(index)));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+/*
+ * Fix the local state of the GTT's index.
+ */
+void
+gtt_correct_index_session_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid heapOid = index->rd_index->indrelid;
+
+	/* Must be GTT */
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	/*
+	 * If this GTT is not initialized in the current session,
+	 * its index status is temporarily set to invalid(local relcache).
+	 */
+	if (gtt_storage_attached(heapOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+/*
+ * Initialize storage of GTT and build empty index in this session.
+ */
+void
+gtt_init_storage(CmdType operation, Relation relation)
+{
+	Oid			toastrelid;
+	List		*indexoidlist = NIL;
+	ListCell	*l;
+
+	if (!(operation == CMD_INSERT))
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	/* Each GTT is initialized once in each backend */
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	/* init heap storage */
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	indexoidlist = RelationGetIndexList(relation);
+	foreach(l, indexoidlist)
+	{
+		Oid			indexOid = lfirst_oid(l);
+		Relation	index = index_open(indexOid, RowExclusiveLock);
+		IndexInfo	*info = BuildDummyIndexInfo(index);
+
+		index_build(relation, index, info, true, false);
+		/* after build index, index re-enabled */
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+		index_close(index, NoLock);
+	}
+	list_free(indexoidlist);
+
+	/* rebuild index for global temp toast table */
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+
+		/* init index storage */
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid			indexId = lfirst_oid(indlist);
+			Relation	index = index_open(indexId, RowExclusiveLock);
+			IndexInfo	*info = BuildDummyIndexInfo(index);
+
+			/* build empty index */
+			index_build(toastrel, index, info, true, false);
+			Assert(index->rd_index->indisvalid);
+			Assert(index->rd_index->indislive);
+			Assert(index->rd_index->indisready);
+			index_close(index, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+/*
+ * Release the data structure memory used to store GTT storage info.
+ */
+static void
+gtt_free_statistics(gtt_relfilenode *rnode)
+{
+	int i;
+
+	Assert(rnode);
+
+	for (i = 0; i < rnode->natts; i++)
+	{
+		if (rnode->att_stat_tups[i])
+		{
+			heap_freetuple(rnode->att_stat_tups[i]);
+			rnode->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (rnode->attnum)
+		pfree(rnode->attnum);
+
+	if (rnode->att_stat_tups)
+		pfree(rnode->att_stat_tups);
+
+	pfree(rnode);
+
+	return;
+}
+
+/*
+ * Get the current relfilenode of this GTT.
+ */
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
+/*
+ * Get a relfilenode used by this GTT during the transaction life cycle.
+ */
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, Oid spcnode, bool missing_ok)
+{
+	gtt_relfilenode		*rnode = NULL;
+	ListCell		*lc;
+
+	Assert(entry);
+
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == relfilenode &&
+			gtt_rnode->spcnode == spcnode)
+		{
+			rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (!missing_ok && rnode == NULL)
+		elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+	return rnode;
+}
+
+/*
+ * Get one GTT info from local hash.
+ */
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry == NULL && !missing_ok)
+		elog(ERROR, "relid %u not found in local hash", relid);
+
+	return entry;
+}
+
+/*
+ * update pg_class entry after CREATE INDEX or REINDEX for global temp table
+ */
+void
+index_update_gtt_relstats(Relation rel, bool hasindex, double reltuples, bool isreindex)
+{
+	Oid			relid = RelationGetRelid(rel);
+
+	Assert(RELATION_IS_GLOBAL_TEMP(rel));
+
+	/* see index_update_stats() */
+	if (reltuples == 0 && rel->rd_rel->reltuples < 0)
+		reltuples = -1;
+
+	/* update reltuples relpages relallvisible to localhash */
+	if (reltuples >= 0)
+	{
+		BlockNumber relpages = RelationGetNumberOfBlocks(rel);
+		BlockNumber relallvisible = 0;
+
+		if (rel->rd_rel->relkind != RELKIND_INDEX)
+			visibilitymap_count(rel, &relallvisible, NULL);
+		else
+			relallvisible = 0;
+
+		gtt_update_relstats(rel, relpages, reltuples, relallvisible,
+						InvalidTransactionId, InvalidMultiXactId);
+	}
+
+	/* update relhasindex to pg_class */
+	if (hasindex != rel->rd_rel->relhasindex)
+	{
+		Relation		pg_class = table_open(RelationRelationId, RowExclusiveLock);
+		Form_pg_class	rd_rel;
+		HeapTuple		tuple;
+
+		Assert(rel->rd_rel->relkind != RELKIND_INDEX);
+		tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u", relid);
+
+		rd_rel = (Form_pg_class) GETSTRUCT(tuple);
+		rd_rel->relhasindex = hasindex;
+		heap_inplace_update(pg_class, tuple);
+		heap_freetuple(tuple);
+		table_close(pg_class, RowExclusiveLock);
+	}
+	else if (!isreindex)
+	{
+		/*
+		 * For global temp table
+		 * Even if pg_class does not change, relcache needs to be rebuilt
+		 * for flush rd_indexlist list (for a table) at create index process.
+		 *
+		 * Each session index has an independent data and cache(rd_amcache)
+		 * so relcache of the table and index do not need to be refreshed at reindex process.
+		 * This is different from the reindex of a regular table.
+		 */
+		CacheInvalidateRelcache(rel);
+	}
+}
+
+/*
+ * update statistics for one global temp relation
+ */
+void
+vac_update_gtt_relstats(Relation relation,
+					BlockNumber num_pages, double num_tuples,
+					BlockNumber num_all_visible_pages,
+					bool hasindex, TransactionId frozenxid,
+					MultiXactId minmulti, bool in_outer_xact)
+{
+	Oid			relid = RelationGetRelid(relation);
+	Relation	pg_class;
+	HeapTuple	ctup;
+	Form_pg_class pgcform;
+	bool		dirty = false;
+	List		*idxs = NIL;
+
+	Assert(RELATION_IS_GLOBAL_TEMP(relation));
+
+	/* For global temporary table, store relstats and transaction info to the localhash */
+	gtt_update_relstats(relation, num_pages, num_tuples,
+						num_all_visible_pages, frozenxid, minmulti);
+
+	if (relation->rd_rel->relkind == RELKIND_RELATION)
+		idxs = RelationGetIndexList(relation);
+
+	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+
+	/* Fetch a copy of the tuple to scribble on */
+	ctup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(ctup))
+		elog(ERROR, "pg_class entry for relid %u vanished during vacuuming",
+			 relid);
+	pgcform = (Form_pg_class) GETSTRUCT(ctup);
+
+	/* Apply DDL updates, but not inside an outer transaction (see above) */
+	if (!in_outer_xact)
+	{
+		/*
+		 * If we didn't find any indexes, reset relhasindex.
+		 *
+		 * Global temporary table may contain indexes that are not valid locally.
+		 * The catalog should not be updated based on local invalid index.
+		 */
+		if (pgcform->relhasindex && !hasindex && idxs == NIL)
+		{
+			pgcform->relhasindex = false;
+			dirty = true;
+		}
+
+		/* We also clear relhasrules and relhastriggers if needed */
+		if (pgcform->relhasrules && relation->rd_rules == NULL)
+		{
+			pgcform->relhasrules = false;
+			dirty = true;
+		}
+		if (pgcform->relhastriggers && relation->trigdesc == NULL)
+		{
+			pgcform->relhastriggers = false;
+			dirty = true;
+		}
+	}
+
+	/* If anything changed, write out the tuple. */
+	if (dirty)
+		heap_inplace_update(pg_class, ctup);
+
+	table_close(pg_class, RowExclusiveLock);
+
+	list_free(idxs);
+}
+
+void
+GlobalTempRelationSetNewRelfilenode(Relation relation)
+{
+	Oid			newrelfilenode;
+	MultiXactId minmulti = InvalidMultiXactId;
+	TransactionId freezeXid = InvalidTransactionId;
+	RelFileNode newrnode;
+
+	Assert(RELATION_IS_GLOBAL_TEMP(relation));
+	Assert(!RelationIsMapped(relation));
+
+	/* Allocate a new relfilenode */
+	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
+									   RELPERSISTENCE_GLOBAL_TEMP);
+
+	/*
+	 * Schedule unlinking of the old storage at transaction commit.
+	 */
+	RelationDropStorage(relation);
+
+	newrnode = relation->rd_node;
+	newrnode.relNode = newrelfilenode;
+
+	if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind))
+	{
+		table_relation_set_new_filenode(relation, &newrnode,
+										RELPERSISTENCE_GLOBAL_TEMP,
+										&freezeXid, &minmulti);
+	}
+	else if (RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+	{
+		/* handle these directly, at least for now */
+		SMgrRelation srel;
+
+		srel = RelationCreateStorage(newrnode, RELPERSISTENCE_GLOBAL_TEMP, relation);
+		smgrclose(srel);
+	}
+	else
+	{
+		/* we shouldn't be called for anything else */
+		elog(ERROR, "relation \"%s\" does not have storage",
+			 RelationGetRelationName(relation));
+	}
+
+	RelationAssumeNewRelfilenode(relation);
+
+	/* The local relcache and hashtable have been updated */
+	Assert(gtt_fetch_current_relfilenode(RelationGetRelid(relation)) == newrelfilenode);
+	Assert(relation->rd_node.relNode == newrelfilenode);
+}
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 3cb69b1f87b..1625ee9345b 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index a0da998c2ea..181bbb93f03 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -105,7 +106,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);
 
@@ -186,6 +187,17 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -602,14 +614,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]));
 		}
 
 		/* Build extended statistics (if there are any). */
@@ -1617,7 +1630,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;
@@ -1719,31 +1732,48 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/*
+		 * For global temporary table,
+		 * Update column statistic to localhash, not pg_statistic.
+		 */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 61853e6dec4..e0b8d1d0db7 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/toasting.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
@@ -390,6 +391,22 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+	{
+		if (gtt_storage_attached(RelationGetRelid(OldHeap)))
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("cannot cluster global temporary table")));
+
+		relation_close(OldHeap, AccessExclusiveLock);
+		pgstat_progress_end_command();
+		return;
+	}
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
@@ -585,6 +602,9 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
 	TransactionId frozenXid;
 	MultiXactId cutoffMulti;
 
+	/* not support cluster global temp table yet */
+	Assert(!RELATION_IS_GLOBAL_TEMP(OldHeap));
+
 	/* Mark the correct index as clustered */
 	if (OidIsValid(indexOid))
 		mark_index_clustered(OldHeap, indexOid, true);
@@ -1429,7 +1449,7 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 	pgstat_progress_update_param(PROGRESS_CLUSTER_PHASE,
 								 PROGRESS_CLUSTER_PHASE_REBUILD_INDEX);
 
-	reindex_relation(OIDOldHeap, reindex_flags, &reindex_params);
+	reindex_relation(OIDOldHeap, reindex_flags, &reindex_params, ShareLock);
 
 	/* Report that we are now doing clean up */
 	pgstat_progress_update_param(PROGRESS_CLUSTER_PHASE,
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index bb9c21bc6b4..18cd1d8c282 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -289,7 +289,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP_ON_CURRENT_SESSION(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, whereClause,
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 7b3f5a84b82..03692b4a73a 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -30,6 +30,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/copyfrom_internal.h"
 #include "commands/progress.h"
@@ -652,7 +653,7 @@ CopyFrom(CopyFromState cstate)
 	 */
 	ExecInitRangeTable(estate, cstate->range_table);
 	resultRelInfo = target_resultRelInfo = makeNode(ResultRelInfo);
-	ExecInitResultRelation(estate, resultRelInfo, 1);
+	ExecInitResultRelation(estate, resultRelInfo, 1, CMD_INSERT);
 
 	/* Verify the named relation is a valid target for INSERT */
 	CheckValidResultRel(resultRelInfo, CMD_INSERT);
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 9abbb6b5552..a944f244496 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -32,6 +32,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/namespace.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/toasting.h"
 #include "commands/createas.h"
 #include "commands/matview.h"
@@ -520,6 +521,12 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 	 */
 	intoRelationDesc = table_open(intoRelationAddr.objectId, AccessExclusiveLock);
 
+	/*
+	 * Try initializing the global Temp table storage file before writing data
+	 * to the table.
+	 */
+	gtt_init_storage(CMD_INSERT, intoRelationDesc);
+
 	/*
 	 * Make sure the constructed table does not have RLS enabled.
 	 *
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index e5cf1bde13f..8daaed03bbc 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -111,6 +111,7 @@ struct ReindexIndexCallbackState
 {
 	ReindexParams params;		/* options from statement */
 	Oid			locked_table_oid;	/* tracks previously locked table */
+	LOCKMODE	lockmode;
 };
 
 /*
@@ -570,7 +571,7 @@ DefineIndex(Oid relationId,
 	 * is more efficient.  Do this before any use of the concurrent option is
 	 * done.
 	 */
-	if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+	if (stmt->concurrent && !RelpersistenceTsTemp(get_rel_persistence(relationId)))
 		concurrent = true;
 	else
 		concurrent = false;
@@ -2581,24 +2582,46 @@ ReindexIndex(RangeVar *indexRelation, ReindexParams *params, bool isTopLevel)
 	 */
 	state.params = *params;
 	state.locked_table_oid = InvalidOid;
+	state.lockmode = AccessShareLock;
 	indOid = RangeVarGetRelidExtended(indexRelation,
-									  (params->options & REINDEXOPT_CONCURRENTLY) != 0 ?
-									  ShareUpdateExclusiveLock : AccessExclusiveLock,
+									  AccessShareLock,
 									  0,
 									  RangeVarCallbackForReindexIndex,
 									  &state);
 
 	/*
 	 * Obtain the current persistence and kind of the existing index.  We
-	 * already hold a lock on the index.
+	 * already hold a AccessShareLock on the index.
+	 * If this is not a global temp object, apply a larger lock.
 	 */
 	persistence = get_rel_persistence(indOid);
-	relkind = get_rel_relkind(indOid);
+	if (persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		LOCKMODE	table_lockmode;
+		LOCKMODE	index_lockmode;
+
+		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0)
+		{
+			table_lockmode = ShareUpdateExclusiveLock;
+			index_lockmode = ShareUpdateExclusiveLock;
+		}
+		else
+		{
+			table_lockmode = ShareLock;
+			index_lockmode = AccessExclusiveLock;
+		}
 
+		/* lock heap first */
+		Assert(OidIsValid(state.locked_table_oid));
+		LockRelationOid(state.locked_table_oid, table_lockmode);
+		LockRelationOid(indOid, index_lockmode);
+	}
+
+	relkind = get_rel_relkind(indOid);
 	if (relkind == RELKIND_PARTITIONED_INDEX)
 		ReindexPartitions(indOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 persistence != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(persistence))
 		ReindexRelationConcurrently(indOid, params);
 	else
 	{
@@ -2620,15 +2643,7 @@ RangeVarCallbackForReindexIndex(const RangeVar *relation,
 {
 	char		relkind;
 	struct ReindexIndexCallbackState *state = arg;
-	LOCKMODE	table_lockmode;
-
-	/*
-	 * Lock level here should match table lock in reindex_index() for
-	 * non-concurrent case and table locks used by index_concurrently_*() for
-	 * concurrent case.
-	 */
-	table_lockmode = (state->params.options & REINDEXOPT_CONCURRENTLY) != 0 ?
-		ShareUpdateExclusiveLock : ShareLock;
+	LOCKMODE	table_lockmode = state->lockmode;
 
 	/*
 	 * If we previously locked some other index's heap, and the name we're
@@ -2689,6 +2704,8 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 {
 	Oid			heapOid;
 	bool		result;
+	char		relpersistence;
+	LOCKMODE	lockmode;
 
 	/*
 	 * The lock level used here should match reindex_relation().
@@ -2699,15 +2716,27 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 	 * locks on our temporary table.
 	 */
 	heapOid = RangeVarGetRelidExtended(relation,
-									   (params->options & REINDEXOPT_CONCURRENTLY) != 0 ?
-									   ShareUpdateExclusiveLock : ShareLock,
+									   AccessShareLock,
 									   0,
 									   RangeVarCallbackOwnsTable, NULL);
 
+	relpersistence = get_rel_persistence(heapOid);
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		lockmode = AccessShareLock;
+	else
+	{
+		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0)
+			lockmode = ShareUpdateExclusiveLock;
+		else
+			lockmode = ShareLock;
+
+		LockRelationOid(heapOid, lockmode);
+	}
+
 	if (get_rel_relkind(heapOid) == RELKIND_PARTITIONED_TABLE)
 		ReindexPartitions(heapOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(relpersistence))
 	{
 		result = ReindexRelationConcurrently(heapOid, params);
 
@@ -2724,7 +2753,8 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 		result = reindex_relation(heapOid,
 								  REINDEX_REL_PROCESS_TOAST |
 								  REINDEX_REL_CHECK_CONSTRAINTS,
-								  &newparams);
+								  &newparams,
+								  lockmode);
 		if (!result)
 			ereport(NOTICE,
 					(errmsg("table \"%s\" has no indexes to reindex",
@@ -3119,7 +3149,7 @@ ReindexMultipleInternal(List *relids, ReindexParams *params)
 		Assert(!RELKIND_HAS_PARTITIONS(relkind));
 
 		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			relpersistence != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(relpersistence))
 		{
 			ReindexParams newparams = *params;
 
@@ -3141,13 +3171,20 @@ ReindexMultipleInternal(List *relids, ReindexParams *params)
 		{
 			bool		result;
 			ReindexParams newparams = *params;
+			LOCKMODE	lockmode;
+
+			if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+				lockmode = AccessShareLock;
+			else
+				lockmode = ShareLock;
 
 			newparams.options |=
 				REINDEXOPT_REPORT_PROGRESS | REINDEXOPT_MISSING_OK;
 			result = reindex_relation(relid,
 									  REINDEX_REL_PROCESS_TOAST |
 									  REINDEX_REL_CHECK_CONSTRAINTS,
-									  &newparams);
+									  &newparams,
+									  lockmode);
 
 			if (result && (params->options & REINDEXOPT_VERBOSE) != 0)
 				ereport(INFO,
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 4b3f79704f8..c544885f53e 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -51,12 +51,33 @@ LockTableCommand(LockStmt *lockstmt)
 		RangeVar   *rv = (RangeVar *) lfirst(p);
 		bool		recurse = rv->inh;
 		Oid			reloid;
+		LOCKMODE	lockmode = lockstmt->mode;
+		char		relpersistence;
 
-		reloid = RangeVarGetRelidExtended(rv, lockstmt->mode,
-										  lockstmt->nowait ? RVR_NOWAIT : 0,
+		reloid = RangeVarGetRelidExtended(rv, NoLock, 0,
 										  RangeVarCallbackForLockTable,
 										  (void *) &lockstmt->mode);
 
+		relpersistence = get_rel_persistence(reloid);
+		/* lock statement does not hold any lock on global temp table */
+		if (relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			if (!lockstmt->nowait)
+				LockRelationOid(reloid, lockmode);
+			else if (!ConditionalLockRelationOid(reloid, lockmode))
+			{
+				/* try to throw error by name; relation could be deleted... */
+				char	   *relname = get_rel_name(reloid);
+
+				if (!relname)
+					return;		/* child concurrently dropped, just skip it */
+				ereport(ERROR,
+						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						 errmsg("could not obtain lock on relation \"%s\"",
+								relname)));
+			}
+		}
+
 		if (get_rel_relkind(reloid) == RELKIND_VIEW)
 			LockViewRecurse(reloid, lockstmt->mode, lockstmt->nowait, NIL);
 		else if (recurse)
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 27cb6307581..093f76f2950 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -25,11 +25,14 @@
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
 #include "catalog/dependency.h"
+#include "catalog/heap.h"
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -108,6 +111,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -220,9 +224,16 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	rel = table_open(seqoid, AccessExclusiveLock);
 	tupDesc = RelationGetDescr(rel);
 
-	/* now initialize the sequence's data */
-	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	/*
+	 * For global temp sequence, the storage is not initialized
+	 * when it is created, but when it is used.
+	 */
+	if (!RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		/* now initialize the sequence's data */
+		tuple = heap_form_tuple(tupDesc, value, null);
+		fill_seq_with_data(rel, tuple);
+	}
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -275,8 +286,6 @@ ResetSequence(Oid seq_relid)
 	Buffer		buf;
 	HeapTupleData seqdatatuple;
 	HeapTuple	tuple;
-	HeapTuple	pgstuple;
-	Form_pg_sequence pgsform;
 	int64		startv;
 
 	/*
@@ -287,12 +296,7 @@ ResetSequence(Oid seq_relid)
 	init_sequence(seq_relid, &elm, &seq_rel);
 	(void) read_seq_tuple(seq_rel, &buf, &seqdatatuple);
 
-	pgstuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seq_relid));
-	if (!HeapTupleIsValid(pgstuple))
-		elog(ERROR, "cache lookup failed for sequence %u", seq_relid);
-	pgsform = (Form_pg_sequence) GETSTRUCT(pgstuple);
-	startv = pgsform->seqstart;
-	ReleaseSysCache(pgstuple);
+	startv = get_seqence_start_value(seq_relid);
 
 	/*
 	 * Copy the existing sequence tuple.
@@ -451,6 +455,9 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 
 	init_sequence(relid, &elm, &seqrel);
 
+	if (RELATION_IS_GLOBAL_TEMP(seqrel))
+		CheckGlobalTempTableNotInUse(seqrel, "ALTER GLOBAL TEMPORARY SEQUENCE");
+
 	rel = table_open(SequenceRelationId, RowExclusiveLock);
 	seqtuple = SearchSysCacheCopy1(SEQRELID,
 								   ObjectIdGetDatum(relid));
@@ -611,7 +618,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -936,7 +943,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1153,6 +1160,14 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	/* Initializes the storage for sequence which the global temporary table belongs. */
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_sequence(seqrel);
+	}
 }
 
 
@@ -1927,3 +1942,57 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+/*
+ * Get the startValue of the sequence from syscache.
+ */
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+/*
+ * Initialize sequence which global temporary table belongs.
+ */
+void
+gtt_init_sequence(Relation rel)
+{
+	Datum		value[SEQ_COL_LASTCOL];
+	bool		null[SEQ_COL_LASTCOL];
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL - 1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+	null[SEQ_COL_LASTVAL - 1] = false;
+
+	value[SEQ_COL_LOG - 1] = Int64GetDatum((int64)0);
+	null[SEQ_COL_LOG - 1] = false;
+
+	value[SEQ_COL_CALLED - 1] = BoolGetDatum(false);
+	null[SEQ_COL_CALLED - 1] = false;
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple);
+	heap_freetuple(tuple);
+
+	return;
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1f0654c2f51..a558e121596 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -47,6 +47,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "catalog/toasting.h"
+#include "catalog/storage_gtt.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -118,6 +119,7 @@ typedef struct OnCommitItem
 	 */
 	SubTransactionId creating_subid;
 	SubTransactionId deleting_subid;
+	bool			 is_global_temp;
 } OnCommitItem;
 
 static List *on_commits = NIL;
@@ -624,7 +626,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
 static char GetAttributeCompression(Oid atttypid, char *compression);
-
+static OnCommitAction gtt_oncommit_option(List *options);
 
 /* ----------------------------------------------------------------
  *		DefineRelation
@@ -669,6 +671,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -680,7 +683,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * Check consistency of arguments
 	 */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !RelpersistenceTsTemp(stmt->relation->relpersistence))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -710,7 +713,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	if (RelpersistenceTsTemp(stmt->relation->relpersistence)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -811,6 +814,59 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* For global temporary table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		if (!(relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("global temporary relation can only be a regular table or sequence")));
+
+		if (inheritOids)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot create global temporary inherit table or global temporary partitioned table")));
+
+		/* Check oncommit clause and save to reloptions */
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot specify both ON COMMIT clause and on_commit_delete_rows")));
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_DROP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("specifying ON COMMIT DROP is not supported on a global temporary table")));
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("on_commit_delete_rows can only be used on global temporary table")));
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1435,7 +1491,7 @@ RemoveRelations(DropStmt *drop)
 		 * relation persistence cannot be known without its OID.
 		 */
 		if (drop->concurrent &&
-			get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(get_rel_persistence(relOid)))
 		{
 			Assert(list_length(drop->objects) == 1 &&
 				   drop->removeType == OBJECT_INDEX);
@@ -1644,9 +1700,9 @@ ExecuteTruncate(TruncateStmt *stmt)
 		Relation	rel;
 		bool		recurse = rv->inh;
 		Oid			myrelid;
-		LOCKMODE	lockmode = AccessExclusiveLock;
+		LOCKMODE	lockmode;
 
-		myrelid = RangeVarGetRelidExtended(rv, lockmode,
+		myrelid = RangeVarGetRelidExtended(rv, AccessShareLock,
 										   0, RangeVarCallbackForTruncate,
 										   NULL);
 
@@ -1654,9 +1710,21 @@ ExecuteTruncate(TruncateStmt *stmt)
 		if (list_member_oid(relids, myrelid))
 			continue;
 
-		/* open the relation, we already hold a lock on it */
+		/* open the relation, we need hold a low-level lock first */
 		rel = table_open(myrelid, NoLock);
 
+		/*
+		 * Truncate global temp table only cleans up the data in current session,
+		 * only low-level locks are required.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel))
+			lockmode = AccessShareLock;
+		else
+		{
+			lockmode = AccessExclusiveLock;
+			LockRelationOid(myrelid, lockmode);
+		}
+
 		/*
 		 * RangeVarGetRelidExtended() has done most checks with its callback,
 		 * but other checks with the now-opened Relation remain.
@@ -1906,6 +1974,7 @@ ExecuteTruncateGuts(List *explicit_rels,
 	foreach(cell, rels)
 	{
 		Relation	rel = (Relation) lfirst(cell);
+		LOCKMODE	lockmode;
 
 		/* Skip partitioned tables as there is nothing to do */
 		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
@@ -1956,6 +2025,19 @@ ExecuteTruncateGuts(List *explicit_rels,
 			continue;
 		}
 
+		/*
+		 * Skip the global temporary table that is not initialized for storage
+		 * in current session.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel))
+		{
+			lockmode = AccessShareLock;
+			if (!gtt_storage_attached(RelationGetRelid(rel)))
+				continue;
+		}
+		else
+			lockmode = AccessExclusiveLock;
+
 		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
@@ -1967,7 +2049,7 @@ ExecuteTruncateGuts(List *explicit_rels,
 			rel->rd_newRelfilenodeSubid == mySubid)
 		{
 			/* Immediate, non-rollbackable truncation is OK */
-			heap_truncate_one_rel(rel);
+			heap_truncate_one_rel(rel, lockmode);
 		}
 		else
 		{
@@ -2001,7 +2083,7 @@ ExecuteTruncateGuts(List *explicit_rels,
 			if (OidIsValid(toast_relid))
 			{
 				Relation	toastrel = relation_open(toast_relid,
-													 AccessExclusiveLock);
+													 lockmode);
 
 				RelationSetNewRelfilenode(toastrel,
 										  toastrel->rd_rel->relpersistence);
@@ -2012,7 +2094,7 @@ ExecuteTruncateGuts(List *explicit_rels,
 			 * Reconstruct the indexes to match, and we're done.
 			 */
 			reindex_relation(heap_relid, REINDEX_REL_PROCESS_TOAST,
-							 &reindex_params);
+							 &reindex_params, lockmode);
 		}
 
 		pgstat_count_truncate(rel);
@@ -3283,6 +3365,12 @@ CheckRelationTableSpaceMove(Relation rel, Oid newTableSpaceId)
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot move temporary tables of other sessions")));
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot move global temporary table \"%s\"",
+				 		RelationGetRelationName(rel))));
+
 	return true;
 }
 
@@ -4052,6 +4140,10 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temporary table only current session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		CheckGlobalTempTableNotInUse(rel, "ALTER GLOBAL TEMPORARY TABLE");
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -5383,6 +5475,25 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 
 			rel = table_open(tab->relid, NoLock);
 			find_composite_type_dependencies(rel->rd_rel->reltype, rel, NULL);
+
+			if (RELATION_IS_GLOBAL_TEMP(rel) && tab->rewrite > 0)
+			{
+				if (tab->chgPersistence)
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot change global temporary table persistence setting")));
+
+				if(gtt_storage_attached(tab->relid))
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot rewrite global temporary table \"%s\" when it has data in this session",
+								RelationGetRelationName(rel)),
+						 errhint("Please create a new connection and execute ALTER TABLE on the new connection.")));
+
+				/* global temp table has no data in this session, so only change catalog */
+				tab->rewrite = 0;
+			}
+
 			table_close(rel, NoLock);
 		}
 
@@ -5434,6 +5545,9 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot rewrite temporary tables of other sessions")));
 
+			/* Not support rewrite global temp table */
+			Assert(!RELATION_IS_GLOBAL_TEMP(OldHeap));
+
 			/*
 			 * Select destination tablespace (same as original unless user
 			 * requested a change)
@@ -9081,6 +9195,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -13439,7 +13559,9 @@ TryReuseIndex(Oid oldId, IndexStmt *stmt)
 		Relation	irel = index_open(oldId, NoLock);
 
 		/* If it's a partitioned index, there is no storage to share. */
-		if (irel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
+		/* multiple global temp table are not allow use same relfilenode */
+		if (irel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX &&
+			!RELATION_IS_GLOBAL_TEMP(irel))
 		{
 			stmt->oldNode = irel->rd_node.relNode;
 			stmt->oldCreateSubid = irel->rd_createSubid;
@@ -14101,6 +14223,11 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	/* option on_commit_delete_rows is only for global temp table and cannot be set by ALTER TABLE */
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "cannot set \"on_commit_delete_rows\" for relation \"%s\"",
+					RelationGetRelationName(rel));
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -14601,7 +14728,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(RelationGetSmgr(rel), dstrel, MAIN_FORKNUM,
@@ -16202,6 +16329,7 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
 	switch (rel->rd_rel->relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -16643,7 +16771,7 @@ AlterSeqNamespaces(Relation classRel, Relation rel,
  * Register a newly-created relation's ON COMMIT action.
  */
 void
-register_on_commit_action(Oid relid, OnCommitAction action)
+register_on_commit_action(Oid relid, OnCommitAction action, bool is_gloal_temp)
 {
 	OnCommitItem *oc;
 	MemoryContext oldcxt;
@@ -16662,6 +16790,7 @@ register_on_commit_action(Oid relid, OnCommitAction action)
 	oc->oncommit = action;
 	oc->creating_subid = GetCurrentSubTransactionId();
 	oc->deleting_subid = InvalidSubTransactionId;
+	oc->is_global_temp = is_gloal_temp;
 
 	/*
 	 * We use lcons() here so that ON COMMIT actions are processed in reverse
@@ -16707,6 +16836,7 @@ PreCommit_on_commit_actions(void)
 	ListCell   *l;
 	List	   *oids_to_truncate = NIL;
 	List	   *oids_to_drop = NIL;
+	List	   *oids_to_truncate_gtt = NIL;
 
 	foreach(l, on_commits)
 	{
@@ -16730,7 +16860,12 @@ PreCommit_on_commit_actions(void)
 				 * tables, as they must still be empty.
 				 */
 				if ((MyXactFlags & XACT_FLAGS_ACCESSEDTEMPNAMESPACE))
-					oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid);
+				{
+					if (oc->is_global_temp)
+						oids_to_truncate_gtt = lappend_oid(oids_to_truncate_gtt, oc->relid);
+					else
+						oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid);
+				}
 				break;
 			case ONCOMMIT_DROP:
 				oids_to_drop = lappend_oid(oids_to_drop, oc->relid);
@@ -16747,7 +16882,10 @@ PreCommit_on_commit_actions(void)
 	 * exists at truncation time.
 	 */
 	if (oids_to_truncate != NIL)
-		heap_truncate(oids_to_truncate);
+		heap_truncate(oids_to_truncate, false);
+
+	if (oids_to_truncate_gtt != NIL)
+		heap_truncate(oids_to_truncate_gtt, true);
 
 	if (oids_to_drop != NIL)
 	{
@@ -17746,6 +17884,13 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot attach temporary relation of another session as partition")));
 
+	/* If the parent is permanent, so must be all of its partitions. */
+	if (attachrel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach a global temporary relation as partition of permanent relation \"%s\"",
+						RelationGetRelationName(rel))));
+
 	/* Check if there are any columns in attachrel that aren't in the parent */
 	tupleDesc = RelationGetDescr(attachrel);
 	natts = tupleDesc->natts;
@@ -19238,3 +19383,39 @@ GetAttributeCompression(Oid atttypid, char *compression)
 
 	return cmethod;
 }
+
+/*
+ * Parse the on commit clause for the temporary table
+ */
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			bool	res = false;
+			char	*sval = defGetString(def);
+
+			/* It has to be a Boolean value */
+			if (!parse_bool(sval, &res))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
+
+			if (res)
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 283ffaea77d..a1bee3393a0 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -39,7 +39,9 @@
 #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/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/vacuum.h"
 #include "miscadmin.h"
@@ -1323,6 +1325,11 @@ vac_update_relstats(Relation relation,
 	Form_pg_class pgcform;
 	bool		dirty;
 
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+		return vac_update_gtt_relstats(relation, num_pages, num_tuples,
+									   num_all_visible_pages, hasindex,
+									   frozenxid, minmulti, in_outer_xact);
+
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch a copy of the tuple to scribble on */
@@ -1417,7 +1424,6 @@ vac_update_relstats(Relation relation,
 	table_close(rd, RowExclusiveLock);
 }
 
-
 /*
  *	vac_update_datfrozenxid() -- update pg_database.datfrozenxid for our DB
  *
@@ -1509,6 +1515,13 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/*
+		 * The relfrozenxid for a global temporary talble is stored in localhash,
+		 * not pg_class, See list_all_session_gtt_frozenxids()
+		 */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1566,6 +1579,42 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/* If enable global temporary table */
+	if (max_active_gtt > 0)
+	{
+		TransactionId	safe_age;
+		TransactionId	oldest_gtt_frozenxid =
+			gtt_get_oldest_frozenxids_in_current_database(NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid))
+		{
+			safe_age = oldest_gtt_frozenxid + vacuum_gtt_defer_check_age;
+			if (safe_age < FirstNormalTransactionId)
+				safe_age += FirstNormalTransactionId;
+
+			/*
+			 * We tolerate that the minimum age of gtt is less than
+			 * the minimum age of conventional tables, otherwise it will
+			 * throw warning message.
+			 */
+			if (TransactionIdIsNormal(safe_age) &&
+				TransactionIdPrecedes(safe_age, newFrozenXid))
+			{
+				ereport(WARNING,
+						(errmsg("global temporary table oldest relfrozenxid %u is far in the past",
+								oldest_gtt_frozenxid),
+						 errdetail("The oldest relfrozenxid %u in database \"%s\"", newFrozenXid, get_database_name(MyDatabaseId)),
+						 errhint("please consider cleaning up the data in global temporary table to avoid wraparound problems.")));
+			}
+
+			/*
+			 * We need to ensure that the clog required by gtt is not cleand.
+			 */
+			if (TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+				newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1917,6 +1966,19 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	/*
+	 * Skip those global temporary table that are not initialized in
+	 * this session.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel) &&
+		!gtt_storage_attached(RelationGetRelid(rel)))
+	{
+		relation_close(rel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index e183ab097c4..04ae8ff7073 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -527,6 +527,12 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	/* Global temporary table are not sensible. */
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temporary because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 549d9eb6963..370e4d09d1d 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -784,6 +784,10 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		/* Global temp table is one kind of temp table */
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 9df1f81ea89..2207dccf274 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -50,6 +50,7 @@
 #include "access/table.h"
 #include "access/tableam.h"
 #include "access/transam.h"
+#include "catalog/storage_gtt.h"
 #include "executor/executor.h"
 #include "executor/execPartition.h"
 #include "jit/jit.h"
@@ -832,7 +833,7 @@ ExecGetRangeTableRelation(EState *estate, Index rti)
  */
 void
 ExecInitResultRelation(EState *estate, ResultRelInfo *resultRelInfo,
-					   Index rti)
+					   Index rti, CmdType operation)
 {
 	Relation	resultRelationDesc;
 
@@ -843,6 +844,9 @@ ExecInitResultRelation(EState *estate, ResultRelInfo *resultRelInfo,
 					  NULL,
 					  estate->es_instrument);
 
+	/* Check and init global temporary table storage in this session */
+	gtt_init_storage(operation, resultRelationDesc);
+
 	if (estate->es_result_relations == NULL)
 		estate->es_result_relations = (ResultRelInfo **)
 			palloc0(estate->es_range_table_size * sizeof(ResultRelInfo *));
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 5ec699a9bd1..1401d999b40 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -38,6 +38,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -2754,13 +2755,13 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	{
 		mtstate->rootResultRelInfo = makeNode(ResultRelInfo);
 		ExecInitResultRelation(estate, mtstate->rootResultRelInfo,
-							   node->rootRelation);
+							   node->rootRelation, operation);
 	}
 	else
 	{
 		mtstate->rootResultRelInfo = mtstate->resultRelInfo;
 		ExecInitResultRelation(estate, mtstate->resultRelInfo,
-							   linitial_int(node->resultRelations));
+							   linitial_int(node->resultRelations), operation);
 	}
 
 	/* set up epqstate with dummy subplan data for the moment */
@@ -2788,7 +2789,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 		if (resultRelInfo != mtstate->rootResultRelInfo)
 		{
-			ExecInitResultRelation(estate, resultRelInfo, resultRelation);
+			ExecInitResultRelation(estate, resultRelInfo, resultRelation, operation);
 
 			/*
 			 * For child result relations, store the root result relation
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 169b1d53fc8..07a616d626b 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -48,7 +48,7 @@
 #include "partitioning/partprune.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/lsyscache.h"
-
+#include "utils/rel.h"
 
 /* results of subquery_is_pushdown_safe */
 typedef struct pushdown_safety_info
@@ -619,7 +619,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			if (RelpersistenceTsTemp(get_rel_persistence(rte->relid)))
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index bd09f85aea1..a56f0b8ceee 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6071,7 +6071,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
-	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+	if (RELATION_IS_TEMP(heap) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index a5002ad8955..22f64506caa 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -31,6 +31,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
 #include "catalog/pg_statistic_ext_data.h"
+#include "catalog/storage_gtt.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -222,6 +223,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temporary table in this session */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6ac2e9ce237..2d62d0a4036 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2902,7 +2902,7 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
 		 * creation query. It would be hard to refresh data or incrementally
 		 * maintain it if a source disappeared.
 		 */
-		if (isQueryUsingTempRelation(query))
+		if (isQueryUsingTempRelation(query) || isQueryUsingGlobalTempRelation(query))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("materialized views must not use temporary tables or views")));
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index b5966712ce1..388303a2d85 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3434,17 +3434,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; }
@@ -11843,19 +11837,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 cb9e177b5e5..e333f427740 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -82,6 +82,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 isQueryUsingGlobalTempRelation_walker(Node *node, void *context);
 
 
 /*
@@ -3665,3 +3666,52 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * Like function isQueryUsingTempRelation_walker
+ * return true if any relation underlying
+ * the query is a global temporary table.
+ */
+static bool
+isQueryUsingGlobalTempRelation_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 = table_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				table_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 isQueryUsingGlobalTempRelation_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  isQueryUsingGlobalTempRelation_walker,
+								  context);
+}
+
+/* Check if the query uses global temporary table */
+bool
+isQueryUsingGlobalTempRelation(Query *query)
+{
+	return isQueryUsingGlobalTempRelation_walker((Node *) query, NULL);
+}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 0eea214dd89..00233b13658 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -447,6 +447,13 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	/*
+	 * If a sequence is bound to a global temporary table, then the sequence
+	 * must been "global temporary"
+	 */
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
@@ -3326,6 +3333,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		cxt.isforeign = false;
 	}
 	cxt.relation = stmt->relation;
+	/* Set the relpersistence to the context */
+	cxt.relation->relpersistence = RelationGetRelPersistence(rel);
 	cxt.rel = rel;
 	cxt.inhRelations = NIL;
 	cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 681ef91b81e..4ea4c5bfcc7 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2116,6 +2116,14 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/*
+			 * Aotuvacuum cannot vacuum the private data stored in each session
+			 * that belongs to global temporary table, so skip them.
+			 */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2182,7 +2190,7 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		if (RelpersistenceTsTemp(classForm->relpersistence))
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index a2512e750c2..9aefdf48cb9 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -37,6 +37,7 @@
 #include "access/xlogutils.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -2934,7 +2935,15 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
-	if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind))
+	/*
+	 * Returns 0 if this global temporary table is not initialized in this session.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+	else if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind))
 	{
 		/*
 		 * Not every table AM uses BLCKSZ wide fixed size blocks.
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 9f26e41c464..0a25b151e82 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -22,6 +22,7 @@
 #include "access/subtrans.h"
 #include "access/syncscan.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -143,6 +144,7 @@ CalculateShmemSize(int *num_semaphores)
 	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
@@ -246,6 +248,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* For global temporary table shared hashtable */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 3be60402890..dfb78547c0a 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -65,6 +65,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -5161,3 +5162,66 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * Search all active session to get db level oldest frozenxid
+ * for global temporary table.
+ *
+ * Pids and Xids are used to store the session level oldest frozenxid if specified
+ */
+TransactionId
+gtt_get_oldest_frozenxids_in_current_database(List **pids, List **xids)
+{
+	ProcArrayStruct		*arrayP = NULL;
+	TransactionId		result = InvalidTransactionId;
+	int			index = 0;
+	int			i = 0;
+	uint8		flags = 0;
+
+	/* return 0 if feature is disabled */
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	/* Disable in standby node */
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	arrayP = procArray;
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		PGPROC	   *proc = &allProcs[pgprocno];
+		uint8		statusFlags = ProcGlobal->statusFlags[index];
+		TransactionId	gtt_frozenxid = InvalidTransactionId;
+
+		if (statusFlags & flags)
+			continue;
+
+		/* Fetch all session level frozenxid that is belonging to current database */
+		gtt_frozenxid = proc->gtt_frozenxid;
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = gtt_frozenxid;
+			else if (TransactionIdPrecedes(gtt_frozenxid, result))
+				result = gtt_frozenxid;
+
+			/* save backend pid and session level oldest relfrozenxid */
+			if (pids)
+				*pids = lappend_int(*pids, proc->pid);
+
+			if (xids)
+				*xids = lappend_oid(*xids, gtt_frozenxid);
+
+			i++;
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	return result;
+}
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 7b0dea4abec..ab0d2031e86 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -176,7 +176,9 @@ static const char *const BuiltinTrancheNames[] = {
 	/* LWTRANCHE_PARALLEL_APPEND: */
 	"ParallelAppend",
 	/* LWTRANCHE_PER_XACT_PREDICATE_LIST: */
-	"PerXactPredicateList"
+	"PerXactPredicateList",
+	/* LWTRANCHE_GLOBAL_TEMP_TABLE_CTL */
+	"GlobalTempTableControl"
 };
 
 StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index e306b04738d..c9e90714c3b 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -391,6 +391,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
@@ -576,6 +577,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 3a2f2e1f99d..9a6e7cee244 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -982,6 +982,13 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/*
+			 * For global temporary table ,each backend has its own storage,
+			 * also only sees its own storage. Use Backendid to identify them.
+			 */
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 1fbb0b28c3b..637ff7f7e43 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -108,6 +108,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -5117,12 +5118,26 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								/* For global temporary table, get statistic data from localhash */
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -5365,15 +5380,28 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6817,6 +6845,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;
@@ -6834,6 +6863,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6845,6 +6882,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;
@@ -6860,6 +6899,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7778,6 +7825,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))
@@ -7790,6 +7839,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7802,6 +7860,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.
@@ -7821,6 +7881,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index feef9998634..ebaad33a698 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -3113,6 +3114,19 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		/* For global temporary table, get statistic data from localhash */
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 2e760e8a3bd..758a635b75e 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -65,6 +65,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1152,6 +1153,36 @@ retry:
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+				TransactionId	relfrozenxid = InvalidTransactionId;
+				MultiXactId 	relminmxid = InvalidMultiXactId;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+
+				/*
+				 * For global temporary table
+				 * get session level relstats from localhash
+				 * and set it to local relcache
+				 */
+				get_gtt_relstats(RelationGetRelid(relation),
+								 &relpages, &reltuples, &relallvisible,
+								 &relfrozenxid, &relminmxid);
+
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+				if (TransactionIdIsNormal(relfrozenxid))
+					relation->rd_rel->relfrozenxid = relfrozenxid;
+
+				if (MultiXactIdIsValid(relminmxid))
+					relation->rd_rel->relminmxid = relminmxid;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1212,6 +1243,15 @@ retry:
 	else
 		Assert(relation->rd_rel->relam == InvalidOid);
 
+	/*
+	 * For one global temporary table,
+	 * other session may created one index, that triggers relcache reload for this session.
+	 * If table already has data at this session, to avoid rebuilding index,
+	 * accept the structure of the index but set the local state to indisvalid.
+	 */
+	if (relation->rd_rel->relkind == RELKIND_INDEX)
+		gtt_correct_index_session_state(relation);
+
 	/* extract reloptions if any */
 	RelationParseRelOptions(relation, pg_class_tuple);
 
@@ -1335,7 +1375,23 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		/*
+		 * For global temporary table,
+		 * the latest relfilenode is saved in localHash(see RelationSetNewRelfilenode()),
+		 * get it and put it to local relcache.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+			if (OidIsValid(newrelnode) &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2288,6 +2344,14 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		/*
+		 * For one global temporary table,
+		 * other session may created one index, that triggers relcache reload for this session.
+		 * If table already has data at this session, to avoid rebuilding index,
+		 * accept the structure of the index but set the local state to indisvalid.
+		 */
+		gtt_correct_index_session_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3568,6 +3632,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3681,6 +3749,13 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
 
+	/*
+	 * For global temporary table, storage information for each session is
+	 * maintained locally, not in pg_class.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+		return GlobalTempRelationSetNewRelfilenode(relation);
+
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
@@ -3724,7 +3799,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);
 	}
 	else
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 4c94f09c645..b4017794a35 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -44,6 +44,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "commands/prepare.h"
 #include "commands/tablespace.h"
@@ -154,6 +155,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temporary table feature.
+ * table schema are still saved in catalog.
+ *
+ * num > 0 means allows the database to manage multiple active tables at the same time.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2141,6 +2154,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("Sets the amount of time to wait before forcing a "
@@ -2712,6 +2734,16 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"vacuum_gtt_defer_check_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("The defer check age of GTT, used to check expired data after vacuum."),
+			NULL
+		},
+		&vacuum_gtt_defer_check_age,
+		10000, 0, 1000000,
+		NULL, NULL, NULL
+	},
+
 	/*
 	 * See also CheckRequiredParameterValues() if this parameter changes
 	 */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 7c2f1d30447..cb18546ad74 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2538,6 +2538,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
 		dopt->no_unlogged_table_data)
 		return;
 
+	/* Don't dump data in global temporary table/sequence */
+	if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		return;
+
 	/* Check that the data is not explicitly excluded */
 	if (simple_oid_list_member(&tabledata_exclude_oids,
 							   tbinfo->dobj.catId.oid))
@@ -14913,6 +14917,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
 		char	   *foreign = "";
+		char	   *table_type = NULL;
 
 		/*
 		 * Set reltypename, and collect any relkind-specific data that we
@@ -14988,9 +14993,15 @@ dumpTableSchema(Archive *fout, const 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);
 
@@ -15358,6 +15369,15 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 			}
 		}
 
+		/*
+		 * Transaction information for the global temporary table is not stored
+		 * in the pg_class.
+		 */
+		if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			Assert(tbinfo->frozenxid == 0);
+			Assert(tbinfo->minmxid == 0);
+		}
 		/*
 		 * In binary_upgrade mode, arrange to restore the old relfrozenxid and
 		 * relminmxid of all vacuumable relations.  (While vacuum.c processes
@@ -15365,7 +15385,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		 * of other relations; so this "if" lacks RELKIND_TOASTVALUE, and the
 		 * child toast table is handled below.)
 		 */
-		if (dopt->binary_upgrade &&
+		else if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
 			 tbinfo->relkind == RELKIND_MATVIEW))
 		{
@@ -16385,6 +16405,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	PQExpBuffer query = createPQExpBuffer();
 	PQExpBuffer delqry = createPQExpBuffer();
 	char	   *qseqname;
+	bool	   global_temp_seq = false;
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
@@ -16394,9 +16415,12 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
-						  "FROM pg_catalog.pg_sequence "
-						  "WHERE seqrelid = '%u'::oid",
+						  "seqcache, seqcycle, "
+						  "c.relpersistence "
+						  "FROM pg_catalog.pg_sequence s, "
+						  "pg_catalog.pg_class c "
+						  "WHERE seqrelid = '%u'::oid "
+						  "and s.seqrelid = c.oid",
 						  tbinfo->dobj.catId.oid);
 	}
 	else
@@ -16433,6 +16457,9 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
 
+	if (fout->remoteVersion >= 150000)
+		global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
 	if (strcmp(seqtype, "smallint") == 0)
@@ -16510,9 +16537,13 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	}
 	else
 	{
-		appendPQExpBuffer(query,
-						  "CREATE SEQUENCE %s\n",
-						  fmtQualifiedDumpable(tbinfo));
+		appendPQExpBuffer(query, "CREATE ");
+
+		if (global_temp_seq)
+			appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+		appendPQExpBuffer(query, "SEQUENCE %s\n",
+								  fmtQualifiedDumpable(tbinfo));
 
 		if (strcmp(seqtype, "bigint") != 0)
 			appendPQExpBuffer(query, "    AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 3d218c2ad24..b120ff8e71d 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -88,7 +88,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -174,7 +174,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index f7fa0820d69..028e0700e37 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_global_temp);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -271,9 +271,11 @@ report_unmatched_relation(const RelInfo *rel, const DbInfo *db, bool is_new_db)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_global_temp)
 {
 	int			dbnum;
 
@@ -283,7 +285,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_global_temp);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -368,7 +370,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_global_temp)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -411,8 +413,17 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
 			 "         ON c.relnamespace = n.oid "
 			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
+			 CppAsString2(RELKIND_MATVIEW) ") AND ");
+
+	if (skip_global_temp)
+	{
+		/* exclude global temp tables */
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+			"    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND ");
+	}
+
 	/* exclude possible orphaned temp tables */
+	snprintf(query + strlen(query), sizeof(query) - strlen(query),
 			 "    ((n.nspname !~ '^pg_temp_' AND "
 			 "      n.nspname !~ '^pg_toast_temp_' AND "
 			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 07defacd673..438ae344d09 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -411,7 +411,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -649,7 +649,10 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+			/* exclude global temp tables */
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -660,7 +663,10 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+		/* exclude global temp tables */
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index da6770d0f83..2da01c28f37 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -372,7 +372,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_global_temp);
 
 /* option.c */
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 40433e32fa0..d1bbab6029b 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3769,7 +3769,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		 * Show whether a relation is permanent, temporary, or unlogged.
 		 */
 		appendPQExpBuffer(&buf,
-						  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+						  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+						  gettext_noop("session"),
 						  gettext_noop("permanent"),
 						  gettext_noop("temporary"),
 						  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 6bd33a06cb1..d65daa3855b 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1103,6 +1103,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2670,6 +2672,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE FOREIGN TABLE */
 	else if (Matches("CREATE", "FOREIGN", "TABLE", MatchAny))
@@ -2904,6 +2909,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE", "SEQUENCE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index c4757bda2d5..d99454c984f 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -86,9 +86,9 @@ extern Oid	heap_create_with_catalog(const char *relname,
 
 extern void heap_drop_with_catalog(Oid relid);
 
-extern void heap_truncate(List *relids);
+extern void heap_truncate(List *relids, bool is_global_temp);
 
-extern void heap_truncate_one_rel(Relation rel);
+extern void heap_truncate_one_rel(Relation rel, LOCKMODE lockmode);
 
 extern void heap_truncate_check_FKs(List *relations, bool tempTables);
 
@@ -161,5 +161,5 @@ extern void StorePartitionKey(Relation rel,
 extern void RemovePartitionKeyByRelId(Oid relid);
 extern void StorePartitionBound(Relation rel, Relation parent,
 								PartitionBoundSpec *bound);
-
+extern void CheckGlobalTempTableNotInUse(Relation rel, const char *stmt);
 #endif							/* HEAP_H */
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index a1d6e3b645f..441cd440493 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -158,7 +158,7 @@ extern void reindex_index(Oid indexId, bool skip_constraint_checks,
 #define REINDEX_REL_FORCE_INDEXES_UNLOGGED	0x08
 #define REINDEX_REL_FORCE_INDEXES_PERMANENT 0x10
 
-extern bool reindex_relation(Oid relid, int flags, ReindexParams *params);
+extern bool reindex_relation(Oid relid, int flags, ReindexParams *params, LOCKMODE lockmode);
 
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 304e8c18d52..e23cb49c010 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -172,6 +172,7 @@ DECLARE_INDEX(pg_class_tblspc_relfilenode_index, 3455, ClassTblspcRelfilenodeInd
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 0859dc81cac..60d92e2096f 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5729,6 +5729,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '9874',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '9875',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '9876',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '9877',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 9ffc7419131..ca5bcaa3f97 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,7 +22,7 @@
 /* GUC variables */
 extern int	wal_skip_threshold;
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationPreTruncate(Relation rel);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 00000000000..50e0570945a
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,46 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern int		vacuum_gtt_defer_check_age;
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void gtt_update_relstats(Relation relation, BlockNumber relpages, double reltuples,
+									BlockNumber relallvisible, TransactionId relfrozenxid,
+									TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void force_enable_gtt_index(Relation index);
+extern void gtt_correct_index_session_state(Relation index);
+extern void gtt_init_storage(CmdType operation, Relation relation);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+extern void index_update_gtt_relstats(Relation rel, bool hasindex, double reltuples, bool isreindex);
+extern void vac_update_gtt_relstats(Relation relation, BlockNumber num_pages, double num_tuples,
+										BlockNumber num_all_visible_pages, bool hasindex, TransactionId frozenxid,
+										MultiXactId minmulti, bool in_outer_xact);
+extern void GlobalTempRelationSetNewRelfilenode(Relation relation);
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index f7542f2bccb..e8ae0f00531 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_sequence(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 5d4037f26e8..28598b06c1e 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -86,7 +86,7 @@ extern void find_composite_type_dependencies(Oid typeOid,
 
 extern void check_of_type(HeapTuple typetuple);
 
-extern void register_on_commit_action(Oid relid, OnCommitAction action);
+extern void register_on_commit_action(Oid relid, OnCommitAction action, bool is_gloal_temp);
 extern void remove_on_commit_action(Oid relid);
 
 extern void PreCommit_on_commit_actions(void);
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 344399f6a8a..ae7628d692b 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -575,7 +575,7 @@ exec_rt_fetch(Index rti, EState *estate)
 
 extern Relation ExecGetRangeTableRelation(EState *estate, Index rti);
 extern void ExecInitResultRelation(EState *estate, ResultRelInfo *resultRelInfo,
-								   Index rti);
+								   Index rti, CmdType operation);
 
 extern int	executor_errposition(EState *estate, int location);
 
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 06dc27995ba..197e367f4e4 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -119,5 +119,6 @@ extern const NameData *attnumAttName(Relation rd, int attid);
 extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
+extern bool isQueryUsingGlobalTempRelation(Query *query);
 
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 124977cf7e3..9b64389b08a 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -189,6 +189,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_SHARED_TIDBITMAP,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_PER_XACT_PREDICATE_LIST,
+	LWTRANCHE_GLOBAL_TEMP_TABLE_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index a58888f9e90..1bbf31923c0 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -164,6 +164,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId 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 e03692053ee..d948c0383b7 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -94,4 +94,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern TransactionId gtt_get_oldest_frozenxids_in_current_database(List **pids, List **xids);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 6bb81707b09..4927edc2dec 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -288,6 +288,9 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 6da1b220cdc..6455dfb7ae2 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	SMgrRelation rd_smgr;		/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -327,6 +327,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -609,11 +610,13 @@ RelationGetSmgr(Relation rel)
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -621,6 +624,7 @@ RelationGetSmgr(Relation rel)
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -633,6 +637,30 @@ RelationGetSmgr(Relation rel)
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP_ON_CURRENT_SESSION
+ *		Test a rel is either local temp relation of this session
+ *		or global temp relation.
+ */
+#define RELATION_IS_TEMP_ON_CURRENT_SESSION(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is local temporary relation or global temporary relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RelpersistenceTsTemp
+ *		Test a relpersistence is local temp relation or global temporary relation.
+ */
+#define RelpersistenceTsTemp(relpersistence) \
+	(relpersistence == RELPERSISTENCE_TEMP || \
+	 relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -678,6 +706,19 @@ RelationGetSmgr(Relation rel)
 	 (relation)->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&	\
 	 !IsCatalogRelation(relation))
 
+/* For global temporary table */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/* Get on commit clause value only for global temporary table */
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+/* Get relpersistence for relation */
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
-- 
2.32.0 (Apple Git-132)

0004-gtt-v65-regress.patchapplication/octet-stream; name=0004-gtt-v65-regress.patchDownload
diff --git a/src/test/isolation/expected/gtt-sequence.out b/src/test/isolation/expected/gtt-sequence.out
new file mode 100644
index 00000000000..31db2ebd423
--- /dev/null
+++ b/src/test/isolation/expected/gtt-sequence.out
@@ -0,0 +1,48 @@
+unused step name: s1_seq_restart
+Parsed test spec with 2 sessions
+
+starting permutation: s1_seq_next s2_seq_next s1_seq_next
+step s1_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      1
+(1 row)
+
+step s2_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      1
+(1 row)
+
+step s1_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      2
+(1 row)
+
+
+starting permutation: s1_select s2_select s1_insert s2_insert s1_select s2_select
+step s1_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+(0 rows)
+
+step s2_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+(0 rows)
+
+step s1_insert: insert into gtt_with_seq values(1);
+step s2_insert: insert into gtt_with_seq values(10);
+step s1_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+ 1| 3
+(1 row)
+
+step s2_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+10| 1
+(1 row)
+
diff --git a/src/test/isolation/expected/gtt-table.out b/src/test/isolation/expected/gtt-table.out
new file mode 100644
index 00000000000..5825773aa12
--- /dev/null
+++ b/src/test/isolation/expected/gtt-table.out
@@ -0,0 +1,675 @@
+Parsed test spec with 3 sessions
+
+starting permutation: s1_update_d
+step s1_update_d: update gtt_on_commit_delete_row set b = 'update'
+
+starting permutation: s1_select_d s1_insert_d s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_begin s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+1|test1
+(1 row)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_begin s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+1|test1
+(1 row)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_select_d s1_truncate_d s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_begin s1_insert_d s1_select_d s1_truncate_d s1_select_d s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_begin s1_insert_d s1_select_d s1_truncate_d s1_select_d s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_insert_d s1_select_d s1_begin s1_insert_d s1_select_d s1_save_1 s1_truncate_d s1_insert_d s1_select_d s1_save_2 s1_truncate_d s1_insert_d s1_select_d s1_save_3 s1_rollback_to_save_2 s1_select_d s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_save_1: SAVEPOINT save1
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_save_2: SAVEPOINT save2
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+4|test1
+(1 row)
+
+step s1_save_3: SAVEPOINT save3
+step s1_rollback_to_save_2: rollback to savepoint save2
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+5|test1
+(2 rows)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_insert_d s1_select_d s1_begin s1_insert_d s1_select_d s1_save_1 s1_truncate_d s1_insert_d s1_select_d s1_save_2 s1_truncate_d s1_insert_d s1_select_d s1_save_3 s1_rollback_to_save_2 s1_select_d s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_save_1: SAVEPOINT save1
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_save_2: SAVEPOINT save2
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+4|test1
+(1 row)
+
+step s1_save_3: SAVEPOINT save3
+step s1_rollback_to_save_2: rollback to savepoint save2
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+5|test1
+(2 rows)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_update_p
+step s2_update_p: update gtt_on_commit_preserve_row set b = 'update'
+
+starting permutation: s2_select_p s2_insert_p s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_begin s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_begin s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_select_p s2_insert_p s2_select_p s2_truncate_p s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_select_p s2_insert_p s2_begin s2_insert_p s2_select_p s2_truncate_p s2_select_p s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_insert_p s2_begin s2_insert_p s2_select_p s2_truncate_p s2_select_p s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_insert_p s2_select_p s2_begin s2_insert_p s2_select_p s2_save_1 s2_truncate_p s2_insert_p s2_select_p s2_save_2 s2_truncate_p s2_insert_p s2_select_p s2_save_3 s2_rollback_to_save_2 s2_select_p s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_save_1: SAVEPOINT save1
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_save_2: SAVEPOINT save2
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+4|test10
+(1 row)
+
+step s2_save_3: SAVEPOINT save3
+step s2_rollback_to_save_2: rollback to savepoint save2
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+
+starting permutation: s2_insert_p s2_select_p s2_begin s2_insert_p s2_select_p s2_save_1 s2_truncate_p s2_insert_p s2_select_p s2_save_2 s2_truncate_p s2_insert_p s2_select_p s2_save_3 s2_rollback_to_save_2 s2_select_p s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_save_1: SAVEPOINT save1
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_save_2: SAVEPOINT save2
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+4|test10
+(1 row)
+
+step s2_save_3: SAVEPOINT save3
+step s2_rollback_to_save_2: rollback to savepoint save2
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_insert_p s2_insert_p s1_select_p s2_select_p
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_truncate_p s2_truncate_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_begin s1_insert_d s2_insert_d s1_truncate_d s2_insert_d s1_commit
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s2_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s2_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_commit: commit
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_reindex_p s2_reindex_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_reindex_p: reindex table gtt_on_commit_preserve_row
+step s2_reindex_p: reindex table gtt_on_commit_preserve_row
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_reindex_i_p s2_reindex_i_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_reindex_i_p: reindex index gtt_on_commit_preserve_row_pkey
+step s2_reindex_i_p: reindex index gtt_on_commit_preserve_row_pkey
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_insert_c s3_create_c s3_insert_c s1_insert_c s1_analyze_c s2_analyze_c s3_analyze_c s1_select_c s2_select_c s3_select_c
+step s2_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s3_create_c: create unique index idx_temp_table_a on gtt_test_createindex(a)
+step s3_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s1_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s1_analyze_c: analyze gtt_test_createindex
+step s2_analyze_c: analyze gtt_test_createindex
+step s3_analyze_c: analyze gtt_test_createindex
+step s1_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                                               
+---------------------------------------------------------
+Index Scan using idx_temp_table_a on gtt_test_createindex
+  Index Cond: (a = 1)                                    
+(2 rows)
+
+step s2_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                      
+--------------------------------
+Seq Scan on gtt_test_createindex
+  Filter: (a = 1)               
+(2 rows)
+
+step s3_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                                               
+---------------------------------------------------------
+Index Scan using idx_temp_table_a on gtt_test_createindex
+  Index Cond: (a = 1)                                    
+(2 rows)
+
+
+starting permutation: s1_begin s2_begin s1_lock_p s2_lock_p s1_truncate_p s2_truncate_p s1_insert_p s2_insert_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_lock_p: LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE
+step s2_lock_p: LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE
+step s1_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index 99c23b16ffe..f5c8eec533d 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -97,3 +97,5 @@ test: plpgsql-toast
 test: truncate-conflict
 test: serializable-parallel
 test: serializable-parallel-2
+test: gtt-sequence
+test: gtt-table
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 12179f25146..7cf0b20e636 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -80,9 +80,30 @@ disconnect_atexit(void)
 {
 	int			i;
 
-	for (i = 0; i < nconns; i++)
+	for (i = 1; i < nconns; i++)
 		if (conns[i].conn)
 			PQfinish(conns[i].conn);
+
+	if (parseresult.destroy)
+	{
+		PGresult   *res;
+
+		res = PQexec(conns[0].conn, parseresult.destroy);
+		if (PQresultStatus(res) == PGRES_TUPLES_OK)
+		{
+			printResultSet(res);
+		}
+		else if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		{
+			fprintf(stderr, "destroy failed: %s",
+					PQerrorMessage(conns[0].conn));
+			/* don't exit on teardown failure */
+		}
+		PQclear(res);
+	}
+
+	if (conns[0].conn)
+		PQfinish(conns[0].conn);
 }
 
 int
@@ -238,6 +259,24 @@ main(int argc, char **argv)
 	PQclear(res);
 	termPQExpBuffer(&wait_query);
 
+	if (parseresult.initialize)
+	{
+		PGresult   *res;
+
+		res = PQexec(conns[0].conn, parseresult.initialize);
+		if (PQresultStatus(res) == PGRES_TUPLES_OK)
+		{
+			printResultSet(res);
+		}
+		else if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		{
+			fprintf(stderr, "initialize failed: %s",
+					PQerrorMessage(conns[0].conn));
+			/* don't exit on teardown failure */
+		}
+		PQclear(res);
+	}
+
 	/*
 	 * Run the permutations specified in the spec, or all if none were
 	 * explicitly specified.
diff --git a/src/test/isolation/isolationtester.h b/src/test/isolation/isolationtester.h
index e00bc6b816b..e15d3c6d3ea 100644
--- a/src/test/isolation/isolationtester.h
+++ b/src/test/isolation/isolationtester.h
@@ -81,6 +81,8 @@ typedef struct
 	int			nsessions;
 	Permutation **permutations;
 	int			npermutations;
+	char	   *initialize;
+	char	   *destroy;
 } TestSpec;
 
 extern TestSpec parseresult;
diff --git a/src/test/isolation/specparse.y b/src/test/isolation/specparse.y
index eb368184b8b..fb0107eccf2 100644
--- a/src/test/isolation/specparse.y
+++ b/src/test/isolation/specparse.y
@@ -39,7 +39,7 @@ TestSpec		parseresult;			/* result of parsing is left here */
 }
 
 %type <ptr_list> setup_list
-%type <str>  opt_setup opt_teardown
+%type <str>  opt_setup opt_teardown opt_initialize opt_destroy
 %type <str> setup
 %type <ptr_list> step_list session_list permutation_list opt_permutation_list
 %type <ptr_list> permutation_step_list blocker_list
@@ -51,23 +51,27 @@ TestSpec		parseresult;			/* result of parsing is left here */
 
 %token <str> sqlblock identifier
 %token <integer> INTEGER
-%token NOTICES PERMUTATION SESSION SETUP STEP TEARDOWN TEST
+%token NOTICES PERMUTATION SESSION SETUP STEP TEARDOWN TEST INITIALIZE DESTROY
 
 %%
 
 TestSpec:
+			opt_initialize
 			setup_list
 			opt_teardown
+			opt_destroy
 			session_list
 			opt_permutation_list
 			{
-				parseresult.setupsqls = (char **) $1.elements;
-				parseresult.nsetupsqls = $1.nelements;
-				parseresult.teardownsql = $2;
-				parseresult.sessions = (Session **) $3.elements;
-				parseresult.nsessions = $3.nelements;
-				parseresult.permutations = (Permutation **) $4.elements;
-				parseresult.npermutations = $4.nelements;
+				parseresult.setupsqls = (char **) $2.elements;
+				parseresult.nsetupsqls = $2.nelements;
+				parseresult.teardownsql = $3;
+				parseresult.sessions = (Session **) $5.elements;
+				parseresult.nsessions = $5.nelements;
+				parseresult.permutations = (Permutation **) $6.elements;
+				parseresult.npermutations = $6.nelements;
+				parseresult.initialize = $1;
+				parseresult.destroy = $4;
 			}
 		;
 
@@ -100,6 +104,16 @@ opt_teardown:
 			| TEARDOWN sqlblock	{ $$ = $2; }
 		;
 
+opt_initialize:
+			/* EMPTY */			{ $$ = NULL; }
+			| INITIALIZE sqlblock	{ $$ = $2; }
+		;
+
+opt_destroy:
+			/* EMPTY */			{ $$ = NULL; }
+			| DESTROY sqlblock	{ $$ = $2; }
+		;
+
 session_list:
 			session_list session
 			{
diff --git a/src/test/isolation/specs/gtt-sequence.spec b/src/test/isolation/specs/gtt-sequence.spec
new file mode 100644
index 00000000000..88eece45e29
--- /dev/null
+++ b/src/test/isolation/specs/gtt-sequence.spec
@@ -0,0 +1,39 @@
+# Tests for global temporary relations
+
+initialize
+{
+  CREATE GLOBAL TEMPORARY TABLE if not exists gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+}
+
+destroy
+{
+  /* wait other backend exit */
+  select pg_sleep(1);
+
+  DROP TABLE gtt_with_seq;
+}
+
+# Session 1
+session "s1"
+step "s1_seq_next" { select nextval('gtt_with_seq_c2_seq'); }
+step "s1_seq_restart" { alter sequence gtt_with_seq_c2_seq RESTART; }
+step "s1_insert" { insert into gtt_with_seq values(1); }
+step "s1_select" { select * from gtt_with_seq order by c1,c2; }
+teardown
+{
+  TRUNCATE gtt_with_seq;
+}
+
+# Session 2
+session "s2"
+step "s2_seq_next" { select nextval('gtt_with_seq_c2_seq'); }
+step "s2_insert" { insert into gtt_with_seq values(10); }
+step "s2_select" { select * from gtt_with_seq order by c1,c2; }
+teardown
+{
+  TRUNCATE gtt_with_seq RESTART IDENTITY;
+}
+
+permutation "s1_seq_next" "s2_seq_next" "s1_seq_next"
+permutation "s1_select" "s2_select" "s1_insert" "s2_insert" "s1_select" "s2_select"
+
diff --git a/src/test/isolation/specs/gtt-table.spec b/src/test/isolation/specs/gtt-table.spec
new file mode 100644
index 00000000000..e0396b21ef0
--- /dev/null
+++ b/src/test/isolation/specs/gtt-table.spec
@@ -0,0 +1,135 @@
+# Tests for global temporary relations
+
+initialize
+{
+  create global temp table gtt_on_commit_delete_row(a bigserial primary key, b text) on commit delete rows;
+  create global temp table gtt_on_commit_preserve_row(a bigserial primary key, b text) on commit preserve rows;
+  create global temp table gtt_test_createindex(a int, b char(1000)) on commit preserve rows;
+}
+
+destroy
+{
+  /* wait other backend exit */
+  select pg_sleep(1);
+
+  DROP TABLE gtt_on_commit_delete_row;
+  DROP TABLE gtt_on_commit_preserve_row;
+  DROP TABLE gtt_test_createindex;
+}
+
+# Session 1
+session "s1"
+step "s1_begin" {begin}
+step "s1_commit" {commit}
+step "s1_rollback" {rollback}
+step "s1_insert_d" {insert into gtt_on_commit_delete_row (b) values('test1')}
+step "s1_select_d" {select a,b from gtt_on_commit_delete_row order by a,b}
+step "s1_insert_p" {insert into gtt_on_commit_preserve_row (b) values('test20')}
+step "s1_select_p" {select a,b from gtt_on_commit_preserve_row order by a,b}
+step "s1_truncate_d" {truncate gtt_on_commit_delete_row}
+step "s1_truncate_p" {truncate gtt_on_commit_preserve_row}
+step "s1_lock_p" {LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE}
+step "s1_update_d" {update gtt_on_commit_delete_row set b = 'update'}
+step "s1_save_1" {SAVEPOINT save1}
+step "s1_save_2" {SAVEPOINT save2}
+step "s1_save_3" {SAVEPOINT save3}
+step "s1_rollback_to_save_2" {rollback to savepoint save2}
+step "s1_reindex_p" {reindex table gtt_on_commit_preserve_row}
+step "s1_reindex_i_p" {reindex index gtt_on_commit_preserve_row_pkey}
+step "s1_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s1_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s1_analyze_c" {analyze gtt_test_createindex}
+teardown
+{
+  TRUNCATE gtt_on_commit_delete_row RESTART IDENTITY;
+  TRUNCATE gtt_on_commit_preserve_row RESTART IDENTITY;
+}
+
+# Session 2
+session "s2"
+step "s2_begin" {begin}
+step "s2_commit" {commit}
+step "s2_rollback" {rollback}
+step "s2_insert_p" {insert into gtt_on_commit_preserve_row (b) values('test10')}
+step "s2_select_p" {select a,b from gtt_on_commit_preserve_row order by a,b}
+step "s2_insert_d" {insert into gtt_on_commit_delete_row (b) values('test1')}
+step "s2_truncate_p" {truncate gtt_on_commit_preserve_row}
+step "s2_update_p" {update gtt_on_commit_preserve_row set b = 'update'}
+step "s2_lock_p" {LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE}
+step "s2_save_1" {SAVEPOINT save1}
+step "s2_save_2" {SAVEPOINT save2}
+step "s2_save_3" {SAVEPOINT save3}
+step "s2_rollback_to_save_2" {rollback to savepoint save2}
+step "s2_reindex_p" {reindex table gtt_on_commit_preserve_row}
+step "s2_reindex_i_p" {reindex index gtt_on_commit_preserve_row_pkey}
+step "s2_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s2_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s2_analyze_c" {analyze gtt_test_createindex}
+teardown
+{
+  TRUNCATE gtt_on_commit_delete_row RESTART IDENTITY;
+  TRUNCATE gtt_on_commit_preserve_row RESTART IDENTITY;
+}
+
+session "s3"
+step "s3_create_c" {create unique index idx_temp_table_a on gtt_test_createindex(a)}
+step "s3_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s3_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s3_analyze_c" {analyze gtt_test_createindex}
+
+
+#
+# test on commit delete temp table
+#
+
+# test update empty temp table
+permutation "s1_update_d"
+# test insert into temp table
+permutation "s1_select_d" "s1_insert_d" "s1_select_d"
+# test temp table in transaction(commit)
+permutation "s1_select_d" "s1_begin"    "s1_insert_d" "s1_select_d"   "s1_commit"   "s1_select_d" 
+# test temp table in transaction(rollback)
+permutation "s1_select_d" "s1_begin"    "s1_insert_d" "s1_select_d"   "s1_rollback" "s1_select_d" 
+# test truncate
+permutation "s1_select_d" "s1_insert_d" "s1_select_d" "s1_truncate_d" "s1_select_d"
+# test truncate in transaction block
+permutation "s1_select_d" "s1_insert_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_truncate_d" "s1_select_d"   "s1_insert_d" "s1_select_d" "s1_commit"   "s1_select_d" 
+permutation "s1_select_d" "s1_insert_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_truncate_d" "s1_select_d"   "s1_insert_d" "s1_select_d" "s1_rollback" "s1_select_d"
+# test temp table with subtransaction or savepoint
+permutation "s1_insert_d" "s1_select_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_save_1"     "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_2"   "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_3" "s1_rollback_to_save_2" "s1_select_d" "s1_insert_d" "s1_select_d" "s1_commit" "s1_select_d"
+permutation "s1_insert_d" "s1_select_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_save_1"     "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_2"   "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_3" "s1_rollback_to_save_2" "s1_select_d" "s1_insert_d" "s1_select_d" "s1_rollback" "s1_select_d"
+
+#
+# test on commit preserve table
+#
+
+# same as test on commit delete temp table
+permutation "s2_update_p"
+permutation "s2_select_p" "s2_insert_p" "s2_select_p"
+permutation "s2_select_p" "s2_begin"    "s2_insert_p" "s2_select_p"   "s2_commit"   "s2_select_p"
+permutation "s2_select_p" "s2_begin"    "s2_insert_p" "s2_select_p"   "s2_rollback" "s2_select_p"
+permutation "s2_select_p" "s2_insert_p" "s2_select_p" "s2_truncate_p" "s2_select_p"
+permutation "s2_select_p" "s2_insert_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_truncate_p" "s2_select_p"   "s2_insert_p" "s2_select_p" "s2_commit"   "s2_select_p" 
+permutation "s2_select_p" "s2_insert_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_truncate_p" "s2_select_p"   "s2_insert_p" "s2_select_p" "s2_rollback" "s2_select_p" 
+permutation "s2_insert_p" "s2_select_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_save_1"     "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_2"   "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_3" "s2_rollback_to_save_2" "s2_select_p" "s2_insert_p" "s2_select_p" "s2_commit" "s2_select_p"
+permutation "s2_insert_p" "s2_select_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_save_1"     "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_2"   "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_3" "s2_rollback_to_save_2" "s2_select_p" "s2_insert_p" "s2_select_p" "s2_rollback" "s2_select_p"
+
+#
+# test concurrent operation on temp table
+#
+
+#  test concurrent read
+permutation "s1_insert_p" "s2_insert_p" "s1_select_p" "s2_select_p" 
+#  test concurrent truncate
+permutation "s1_begin" "s2_begin"    "s1_insert_p" "s2_insert_p"   "s1_truncate_p" "s2_truncate_p"  "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+permutation "s1_begin" "s1_insert_d" "s2_insert_d" "s1_truncate_d" "s2_insert_d"   "s1_commit" 
+#  test concurrent reindex table
+permutation "s1_begin" "s2_begin" "s1_insert_p" "s2_insert_p" "s1_reindex_p"   "s2_reindex_p"   "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+#  test concurrent reindex index
+permutation "s1_begin" "s2_begin" "s1_insert_p" "s2_insert_p" "s1_reindex_i_p" "s2_reindex_i_p" "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+
+# test create index
+permutation "s2_insert_c" "s3_create_c" "s3_insert_c" "s1_insert_c" "s1_analyze_c" "s2_analyze_c" "s3_analyze_c" "s1_select_c" "s2_select_c" "s3_select_c"
+
+# test lock gtt
+permutation "s1_begin" "s2_begin" "s1_lock_p" "s2_lock_p" "s1_truncate_p" "s2_truncate_p" "s1_insert_p" "s2_insert_p" "s1_commit" "s2_commit" "s1_select_p" "s2_select_p"
diff --git a/src/test/isolation/specscanner.l b/src/test/isolation/specscanner.l
index aa6e89268ef..f782b9b8fcc 100644
--- a/src/test/isolation/specscanner.l
+++ b/src/test/isolation/specscanner.l
@@ -67,6 +67,8 @@ session			{ return SESSION; }
 setup			{ return SETUP; }
 step			{ return STEP; }
 teardown		{ return TEARDOWN; }
+initialize		 { return INITIALIZE; }
+destroy			 { return DESTROY; }
 
  /* Whitespace and comments */
 [\n]			{ yyline++; }
diff --git a/src/test/regress/expected/global_temporary_table.out b/src/test/regress/expected/global_temporary_table.out
new file mode 100644
index 00000000000..0b5e09e27b0
--- /dev/null
+++ b/src/test/regress/expected/global_temporary_table.out
@@ -0,0 +1,577 @@
+--
+-- GLobal emparary table test case 
+--
+CREATE SCHEMA IF NOT EXISTS global_temporary_table;
+set search_path=global_temporary_table,sys;
+--
+--  test create global temp table basic syntax
+--
+create global temp table gtt_on_commit_default(a int primary key, b text);
+create global temp table gtt_on_commit_delete(a int primary key, b text) on commit delete rows;
+create global temp table gtt_on_commit_delete2(n int) with (on_commit_delete_rows='true');
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit PRESERVE rows;
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt_test_createindex(c0 tsvector,c1 varchar(100), c2 int);
+create global temp table gtt_test_alter(b text) with(on_commit_delete_rows=true);
+--
+-- test DML on global temp table
+--
+-- update empty temp table
+update gtt_on_commit_delete set b ='test';
+begin;
+insert into gtt_on_commit_delete values (1);
+update gtt_on_commit_delete set b ='test';
+-- should 1 row
+select * from gtt_on_commit_delete;
+ a |  b   
+---+------
+ 1 | test
+(1 row)
+
+commit;
+-- data delete after transaction commit
+-- should 0 row
+select * from gtt_on_commit_delete;
+ a | b 
+---+---
+(0 rows)
+
+-- update empty temp table
+update gtt_on_commit_preserve set b ='test';
+insert into gtt_on_commit_preserve values (2);
+begin;
+insert into gtt_on_commit_preserve values (3);
+update gtt_on_commit_preserve set b ='test';
+-- should 2 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 2 | test
+ 3 | test
+(2 rows)
+
+delete from gtt_on_commit_preserve where a=2;
+commit;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 3 | test
+(1 row)
+
+begin;
+insert into gtt_on_commit_preserve values (4);
+-- temp table support truncate;
+truncate gtt_on_commit_preserve;
+select * from gtt_on_commit_preserve order by a;
+ a | b 
+---+---
+(0 rows)
+
+rollback;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 3 | test
+(1 row)
+
+--
+-- test unsupported global temp partition table
+--
+-- should fail
+CREATE global temp TABLE global_temp_partition_01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+on commit delete rows;
+ERROR:  global temporary relation can only be a regular table or sequence
+CREATE TABLE regular_partition_01_2019 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+);
+CREATE TABLE regular_partition01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time);
+-- should fail
+CREATE global temp TABLE temp_partition01_2018
+PARTITION OF regular_partition01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+ERROR:  cannot create global temporary inherit table or global temporary partitioned table
+CREATE global temp TABLE global_temp_partition_01_2021 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+)on commit delete rows;
+-- should fail
+ALTER TABLE regular_partition01 ATTACH PARTITION global_temp_partition_01_2021 FOR VALUES FROM ('2021-01-01 00:00:00') TO ('2022-01-01 00:00:00');
+ERROR:  cannot attach a global temporary relation as partition of permanent relation "regular_partition01"
+--
+-- test unsupported inherit table
+--
+create table 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 inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent);
+ERROR:  cannot create global temporary inherit table or global temporary partitioned table
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent_global_temp) on commit delete rows;
+ERROR:  cannot create global temporary inherit table or global temporary partitioned table
+--
+-- test DDL on global temp table
+--
+create index idx_gtt_test_alter_b on gtt_test_alter (b);
+insert into gtt_test_alter values('test');
+alter table gtt_test_alter alter b type varchar;
+create index gtt_idx_1 on gtt_test_createindex using gin (c0);
+create index gtt_idx_2 on gtt_test_createindex using gist (c0);
+create index gtt_idx_3 on gtt_test_createindex using hash (c2);
+alter table gtt_test_rename rename to gtt_test_new;
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+create index CONCURRENTLY idx_b on gtt_on_commit_default (b);
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+insert into gtt_on_commit_default values(1,'test');
+create global temp table gtt_test_alter1 (a int primary key,b text);
+alter table gtt_test_alter1 alter a type varchar;
+-- should fail
+alter table gtt_on_commit_default alter a type varchar;
+ERROR:  cannot rewrite global temporary table "gtt_on_commit_default" when it has data in this session
+HINT:  Please create a new connection and execute ALTER TABLE on the new connection.
+-- should fail
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+ERROR:  cannot cluster global temporary table
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- should fail
+REINDEX (TABLESPACE pg_default) TABLE gtt_test_createindex;
+ERROR:  cannot change tablespace of global temporary table
+-- should fail
+REINDEX (TABLESPACE pg_default) INDEX gtt_idx_1;
+ERROR:  cannot change tablespace of global temporary table
+-- should fail
+alter table gtt_on_commit_default set ( on_commit_delete_rows='true');
+ERROR:  cannot set "on_commit_delete_rows" for relation "gtt_on_commit_default"
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  on_commit_delete_rows can only be used on global temporary table
+-- should fail
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temporary because they do not have storage
+create table foo();
+-- should fail
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  cannot set "on_commit_delete_rows" for relation "foo"
+-- should fail
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit drop;
+ERROR:  specifying ON COMMIT DROP is not supported on a global temporary table
+-- should fail
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  cannot specify both ON COMMIT clause and on_commit_delete_rows
+-- should fail
+CREATE MATERIALIZED VIEW mv_gtt_on_commit_default as select * from gtt_on_commit_default;
+ERROR:  materialized views must not use temporary tables or views
+-- test create table as select
+CREATE GLOBAL TEMPORARY TABLE test_create_table_as AS SELECT 1 AS a;
+-- test copy stmt
+create global temp table gtt_copytest (
+        c1 int,
+        "col with , comma" text,
+        "col with "" quote"  int);
+copy gtt_copytest from stdin csv header;
+select count(*) from gtt_copytest;
+ count 
+-------
+     2
+(1 row)
+
+--
+-- test foreign key dependencies for global temp table
+--
+CREATE global temp TABLE temp_products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+CREATE global temp TABLE temp_orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+)on commit delete rows;
+-- should fail
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+CREATE global temp TABLE temp_orders_2 (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products(product_no),
+    quantity integer
+);
+--should fail
+insert into temp_orders values(1,1,1);
+ERROR:  insert or update on table "temp_orders" violates foreign key constraint "temp_orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "temp_products".
+insert into temp_products values(1,'test',1.0);
+begin;
+insert into temp_orders values(1,1,1);
+commit;
+-- should 1 row
+select count(*) from temp_products;
+ count 
+-------
+     1
+(1 row)
+
+-- should 0 row
+select count(*) from temp_orders;
+ count 
+-------
+     0
+(1 row)
+
+--
+-- test sequence on global temp table
+--
+create global temp table global_temp_with_serial (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+-- support insert data to temp table in read only transaction
+insert into global_temp_with_serial (b) values(1);
+select * from global_temp_with_serial;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_delete(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_preserve(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table temp_table_with_sequence_oncommit_delete add c2 int default nextval('seq_1');
+alter table temp_table_with_sequence_oncommit_preserve add c2 int default nextval('seq_1');
+begin;
+insert into temp_table_with_sequence_oncommit_delete (c1)values(1);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(2);
+insert into temp_table_with_sequence_oncommit_delete (c1)values(3);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(4);
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  3 |  3
+(2 rows)
+
+commit;
+-- should 0 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_preserve order by c1;
+ c1 | c2 
+----+----
+  2 |  2
+  4 |  4
+(2 rows)
+
+--
+-- test statistics on temp table
+--
+create global temp table temp_table_test_statistics(a int);
+insert into temp_table_test_statistics values(generate_series(1,100000));
+create index idx_test_1 on temp_table_test_statistics (a);
+create index idx_test_2 on temp_table_test_statistics((a*10));
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+                   QUERY PLAN                   
+------------------------------------------------
+ Bitmap Heap Scan on temp_table_test_statistics
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_test_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+                   QUERY PLAN                   
+------------------------------------------------
+ Bitmap Heap Scan on temp_table_test_statistics
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_test_2
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+-- test statistic for whole row
+explain (costs off) select count(*) from temp_table_test_statistics group by temp_table_test_statistics;
+                  QUERY PLAN                  
+----------------------------------------------
+ HashAggregate
+   Group Key: temp_table_test_statistics.*
+   ->  Seq Scan on temp_table_test_statistics
+(3 rows)
+
+-- test statistic for system column
+explain (costs off) select count(*) from temp_table_test_statistics group by tableoid;
+                  QUERY PLAN                  
+----------------------------------------------
+ HashAggregate
+   Group Key: tableoid
+   ->  Seq Scan on temp_table_test_statistics
+(3 rows)
+
+analyze temp_table_test_statistics;
+-- indexscan by idx_test_1
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Index Only Scan using idx_test_1 on temp_table_test_statistics
+   Index Cond: (a = 200000)
+(2 rows)
+
+-- indexscan by idx_test_2
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+                        QUERY PLAN                         
+-----------------------------------------------------------
+ Index Scan using idx_test_2 on temp_table_test_statistics
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+explain (costs off) select count(*) from temp_table_test_statistics group by temp_table_test_statistics;
+                  QUERY PLAN                  
+----------------------------------------------
+ HashAggregate
+   Group Key: temp_table_test_statistics.*
+   ->  Seq Scan on temp_table_test_statistics
+(3 rows)
+
+explain (costs off) select count(*) from temp_table_test_statistics group by tableoid;
+                  QUERY PLAN                  
+----------------------------------------------
+ HashAggregate
+   Group Key: tableoid
+   ->  Seq Scan on temp_table_test_statistics
+(3 rows)
+
+--
+-- test temp table with toast table
+--
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |                0 |        483328 |           98304 |                 581632
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            32768 |                  |         32768 |               0 |                  32768
+(3 rows)
+
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |             8192 |        499712 |          106496 |                 606208
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            40960 |                  |         40960 |               0 |                  40960
+(3 rows)
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,10),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',10);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |             8192 |                0 |         16384 |           32768 |                  49152
+ idx_gtt_t_kenyon_1 |            16384 |                  |         16384 |               0 |                  16384
+ idx_gtt_t_kenyon_2 |            16384 |                  |         16384 |               0 |                  16384
+(3 rows)
+
+rollback;
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |             8192 |        499712 |          106496 |                 606208
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            40960 |                  |         40960 |               0 |                  40960
+(3 rows)
+
+-- test analyze/vacuum on global temp table
+ANALYZE gtt_t_kenyon;
+VACUUM gtt_t_kenyon;
+--
+-- test global temp table system view
+--
+create global temp table temp_table_test_systemview(a int primary key, b text) on commit PRESERVE rows;
+-- should empty, storage not initialized
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+ tablename 
+-----------
+(0 rows)
+
+-- should empty, storage not initialized
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+insert into temp_table_test_systemview values(generate_series(1,10000),'test');
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+         tablename          
+----------------------------
+ temp_table_test_systemview
+(1 row)
+
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     2
+(1 row)
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+       schemaname       |            tablename            | relpages | reltuples | relallvisible 
+------------------------+---------------------------------+----------+-----------+---------------
+ global_temporary_table | temp_table_test_systemview      |        0 |         0 |             0
+ global_temporary_table | temp_table_test_systemview_pkey |        1 |         0 |             0
+(2 rows)
+
+-- should empty
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+analyze temp_table_test_systemview;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+       schemaname       |            tablename            | relpages | reltuples | relallvisible 
+------------------------+---------------------------------+----------+-----------+---------------
+ global_temporary_table | temp_table_test_systemview      |       55 |     10000 |             0
+ global_temporary_table | temp_table_test_systemview_pkey |       30 |     10000 |             0
+(2 rows)
+
+-- get data after analyze;
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+       schemaname       |         tablename          | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------------------+----------------------------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ global_temporary_table | temp_table_test_systemview | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ global_temporary_table | temp_table_test_systemview | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+-- get all object info in current schema
+select relname ,relkind, relpersistence, reloptions from pg_class c, pg_namespace n where c.relnamespace = n.oid and n.nspname = 'global_temporary_table' order by relkind,relpersistence,relname;
+                     relname                     | relkind | relpersistence |          reloptions           
+-------------------------------------------------+---------+----------------+-------------------------------
+ global_temp_partition_01_2021_id_seq            | S       | g              | {on_commit_delete_rows=false}
+ global_temp_with_serial_a_seq                   | S       | g              | {on_commit_delete_rows=false}
+ regular_partition01_id_seq                      | S       | p              | 
+ regular_partition_01_2019_id_seq                | S       | p              | 
+ seq_1                                           | S       | p              | 
+ gtt_idx_1                                       | i       | g              | 
+ gtt_idx_2                                       | i       | g              | 
+ gtt_idx_3                                       | i       | g              | 
+ gtt_on_commit_default_pkey                      | i       | g              | 
+ gtt_on_commit_delete_pkey                       | i       | g              | 
+ gtt_on_commit_preserve_pkey                     | i       | g              | 
+ gtt_test_alter1_pkey                            | i       | g              | 
+ gtt_test_rename_pkey                            | i       | g              | 
+ idx_b                                           | i       | g              | 
+ idx_gtt_t_kenyon_1                              | i       | g              | 
+ idx_gtt_t_kenyon_2                              | i       | g              | 
+ idx_gtt_test_alter_b                            | i       | g              | 
+ idx_test_1                                      | i       | g              | 
+ idx_test_2                                      | i       | g              | 
+ products_pkey                                   | i       | g              | 
+ temp_orders_2_pkey                              | i       | g              | 
+ temp_orders_pkey                                | i       | g              | 
+ temp_products_pkey                              | i       | g              | 
+ temp_table_test_systemview_pkey                 | i       | g              | 
+ temp_table_with_sequence_oncommit_delete_pkey   | i       | g              | 
+ temp_table_with_sequence_oncommit_preserve_pkey | i       | g              | 
+ regular_partition01                             | p       | p              | 
+ global_temp_partition_01_2021                   | r       | g              | {on_commit_delete_rows=true}
+ global_temp_with_serial                         | r       | g              | {on_commit_delete_rows=false}
+ gtt_copytest                                    | r       | g              | {on_commit_delete_rows=false}
+ gtt_on_commit_default                           | r       | g              | {on_commit_delete_rows=false}
+ gtt_on_commit_delete                            | r       | g              | {on_commit_delete_rows=true}
+ gtt_on_commit_delete2                           | r       | g              | {on_commit_delete_rows=true}
+ gtt_on_commit_preserve                          | r       | g              | {on_commit_delete_rows=false}
+ gtt_t_kenyon                                    | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_alter                                  | r       | g              | {on_commit_delete_rows=true}
+ gtt_test_alter1                                 | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_createindex                            | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_new                                    | r       | g              | {on_commit_delete_rows=false}
+ inherits_parent_global_temp                     | r       | g              | {on_commit_delete_rows=true}
+ products                                        | r       | g              | {on_commit_delete_rows=false}
+ temp_orders                                     | r       | g              | {on_commit_delete_rows=true}
+ temp_orders_2                                   | r       | g              | {on_commit_delete_rows=false}
+ temp_products                                   | r       | g              | {on_commit_delete_rows=false}
+ temp_table_test_statistics                      | r       | g              | {on_commit_delete_rows=false}
+ temp_table_test_systemview                      | r       | g              | {on_commit_delete_rows=false}
+ temp_table_with_sequence_oncommit_delete        | r       | g              | {on_commit_delete_rows=true}
+ temp_table_with_sequence_oncommit_preserve      | r       | g              | {on_commit_delete_rows=false}
+ test_create_table_as                            | r       | g              | {on_commit_delete_rows=false}
+ foo                                             | r       | p              | 
+ inherits_parent                                 | r       | p              | 
+ regular_partition_01_2019                       | r       | p              | 
+(52 rows)
+
+reset search_path;
+drop schema global_temporary_table cascade;
+NOTICE:  drop cascades to 27 other objects
+DETAIL:  drop cascades to table global_temporary_table.gtt_on_commit_default
+drop cascades to table global_temporary_table.gtt_on_commit_delete
+drop cascades to table global_temporary_table.gtt_on_commit_delete2
+drop cascades to table global_temporary_table.gtt_on_commit_preserve
+drop cascades to table global_temporary_table.gtt_test_new
+drop cascades to table global_temporary_table.gtt_test_createindex
+drop cascades to table global_temporary_table.gtt_test_alter
+drop cascades to table global_temporary_table.regular_partition_01_2019
+drop cascades to table global_temporary_table.regular_partition01
+drop cascades to table global_temporary_table.global_temp_partition_01_2021
+drop cascades to table global_temporary_table.inherits_parent
+drop cascades to table global_temporary_table.inherits_parent_global_temp
+drop cascades to table global_temporary_table.gtt_test_alter1
+drop cascades to table global_temporary_table.foo
+drop cascades to table global_temporary_table.test_create_table_as
+drop cascades to table global_temporary_table.gtt_copytest
+drop cascades to table global_temporary_table.temp_products
+drop cascades to table global_temporary_table.products
+drop cascades to table global_temporary_table.temp_orders
+drop cascades to table global_temporary_table.temp_orders_2
+drop cascades to table global_temporary_table.global_temp_with_serial
+drop cascades to sequence global_temporary_table.seq_1
+drop cascades to table global_temporary_table.temp_table_with_sequence_oncommit_delete
+drop cascades to table global_temporary_table.temp_table_with_sequence_oncommit_preserve
+drop cascades to table global_temporary_table.temp_table_test_statistics
+drop cascades to table global_temporary_table.gtt_t_kenyon
+drop cascades to table global_temporary_table.temp_table_test_systemview
+-- should empty
+select * from pg_list_gtt_relfrozenxids();
+ pid | relfrozenxid 
+-----+--------------
+(0 rows)
+
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index d652f7b5fb4..a1d4219737e 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1359,6 +1359,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 5b0c73d7e37..40f4fc5ca10 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -134,3 +134,6 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: global_temporary_table
diff --git a/src/test/regress/sql/global_temporary_table.sql b/src/test/regress/sql/global_temporary_table.sql
new file mode 100644
index 00000000000..51cd9bbbc0c
--- /dev/null
+++ b/src/test/regress/sql/global_temporary_table.sql
@@ -0,0 +1,315 @@
+--
+-- GLobal emparary table test case 
+--
+
+CREATE SCHEMA IF NOT EXISTS global_temporary_table;
+set search_path=global_temporary_table,sys;
+
+--
+--  test create global temp table basic syntax
+--
+create global temp table gtt_on_commit_default(a int primary key, b text);
+create global temp table gtt_on_commit_delete(a int primary key, b text) on commit delete rows;
+create global temp table gtt_on_commit_delete2(n int) with (on_commit_delete_rows='true');
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit PRESERVE rows;
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt_test_createindex(c0 tsvector,c1 varchar(100), c2 int);
+create global temp table gtt_test_alter(b text) with(on_commit_delete_rows=true);
+--
+-- test DML on global temp table
+--
+
+-- update empty temp table
+update gtt_on_commit_delete set b ='test';
+begin;
+insert into gtt_on_commit_delete values (1);
+update gtt_on_commit_delete set b ='test';
+-- should 1 row
+select * from gtt_on_commit_delete;
+commit;
+-- data delete after transaction commit
+-- should 0 row
+select * from gtt_on_commit_delete;
+
+-- update empty temp table
+update gtt_on_commit_preserve set b ='test';
+insert into gtt_on_commit_preserve values (2);
+begin;
+insert into gtt_on_commit_preserve values (3);
+update gtt_on_commit_preserve set b ='test';
+-- should 2 row
+select * from gtt_on_commit_preserve order by a;
+delete from gtt_on_commit_preserve where a=2;
+commit;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+
+begin;
+insert into gtt_on_commit_preserve values (4);
+-- temp table support truncate;
+truncate gtt_on_commit_preserve;
+select * from gtt_on_commit_preserve order by a;
+rollback;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+
+--
+-- test unsupported global temp partition table
+--
+
+-- should fail
+CREATE global temp TABLE global_temp_partition_01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+on commit delete rows;
+
+CREATE TABLE regular_partition_01_2019 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+);
+
+CREATE TABLE regular_partition01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time);
+
+-- should fail
+CREATE global temp TABLE temp_partition01_2018
+PARTITION OF regular_partition01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE global_temp_partition_01_2021 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+)on commit delete rows;
+
+-- should fail
+ALTER TABLE regular_partition01 ATTACH PARTITION global_temp_partition_01_2021 FOR VALUES FROM ('2021-01-01 00:00:00') TO ('2022-01-01 00:00:00');
+
+--
+-- test unsupported inherit table
+--
+create table 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 inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent);
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent_global_temp) on commit delete rows;
+
+--
+-- test DDL on global temp table
+--
+create index idx_gtt_test_alter_b on gtt_test_alter (b);
+insert into gtt_test_alter values('test');
+alter table gtt_test_alter alter b type varchar;
+create index gtt_idx_1 on gtt_test_createindex using gin (c0);
+create index gtt_idx_2 on gtt_test_createindex using gist (c0);
+create index gtt_idx_3 on gtt_test_createindex using hash (c2);
+alter table gtt_test_rename rename to gtt_test_new;
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+create index CONCURRENTLY idx_b on gtt_on_commit_default (b);
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+insert into gtt_on_commit_default values(1,'test');
+create global temp table gtt_test_alter1 (a int primary key,b text);
+alter table gtt_test_alter1 alter a type varchar;
+-- should fail
+alter table gtt_on_commit_default alter a type varchar;
+-- should fail
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) on commit delete rows;
+-- should fail
+REINDEX (TABLESPACE pg_default) TABLE gtt_test_createindex;
+-- should fail
+REINDEX (TABLESPACE pg_default) INDEX gtt_idx_1;
+-- should fail
+alter table gtt_on_commit_default set ( on_commit_delete_rows='true');
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) with(on_commit_delete_rows=true);
+-- should fail
+create or replace global temp view gtt_v as select 5;
+create table foo();
+-- should fail
+alter table foo set (on_commit_delete_rows='true');
+-- should fail
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit drop;
+-- should fail
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+-- should fail
+CREATE MATERIALIZED VIEW mv_gtt_on_commit_default as select * from gtt_on_commit_default;
+-- test create table as select
+CREATE GLOBAL TEMPORARY TABLE test_create_table_as AS SELECT 1 AS a;
+-- test copy stmt
+create global temp table gtt_copytest (
+        c1 int,
+        "col with , comma" text,
+        "col with "" quote"  int);
+
+copy gtt_copytest from stdin csv header;
+this is just a line full of junk that would error out if parsed
+1,a,1
+2,b,2
+\.
+select count(*) from gtt_copytest;
+
+--
+-- test foreign key dependencies for global temp table
+--
+CREATE global temp TABLE temp_products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+CREATE global temp TABLE temp_orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+)on commit delete rows;
+
+-- should fail
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+);
+
+CREATE global temp TABLE temp_orders_2 (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products(product_no),
+    quantity integer
+);
+
+--should fail
+insert into temp_orders values(1,1,1);
+
+insert into temp_products values(1,'test',1.0);
+begin;
+insert into temp_orders values(1,1,1);
+commit;
+-- should 1 row
+select count(*) from temp_products;
+-- should 0 row
+select count(*) from temp_orders;
+
+--
+-- test sequence on global temp table
+--
+create global temp table global_temp_with_serial (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+-- support insert data to temp table in read only transaction
+insert into global_temp_with_serial (b) values(1);
+select * from global_temp_with_serial;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_delete(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_preserve(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table temp_table_with_sequence_oncommit_delete add c2 int default nextval('seq_1');
+alter table temp_table_with_sequence_oncommit_preserve add c2 int default nextval('seq_1');
+begin;
+insert into temp_table_with_sequence_oncommit_delete (c1)values(1);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(2);
+insert into temp_table_with_sequence_oncommit_delete (c1)values(3);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(4);
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+commit;
+-- should 0 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_preserve order by c1;
+
+--
+-- test statistics on temp table
+--
+create global temp table temp_table_test_statistics(a int);
+insert into temp_table_test_statistics values(generate_series(1,100000));
+create index idx_test_1 on temp_table_test_statistics (a);
+create index idx_test_2 on temp_table_test_statistics((a*10));
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+-- test statistic for whole row
+explain (costs off) select count(*) from temp_table_test_statistics group by temp_table_test_statistics;
+-- test statistic for system column
+explain (costs off) select count(*) from temp_table_test_statistics group by tableoid;
+analyze temp_table_test_statistics;
+-- indexscan by idx_test_1
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+-- indexscan by idx_test_2
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+explain (costs off) select count(*) from temp_table_test_statistics group by temp_table_test_statistics;
+explain (costs off) select count(*) from temp_table_test_statistics group by tableoid;
+
+--
+-- test temp table with toast table
+--
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,10),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',10);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+rollback;
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+
+-- test analyze/vacuum on global temp table
+ANALYZE gtt_t_kenyon;
+VACUUM gtt_t_kenyon;
+
+--
+-- test global temp table system view
+--
+create global temp table temp_table_test_systemview(a int primary key, b text) on commit PRESERVE rows;
+-- should empty, storage not initialized
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+-- should empty, storage not initialized
+select count(*) from pg_list_gtt_relfrozenxids();
+insert into temp_table_test_systemview values(generate_series(1,10000),'test');
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+select count(*) from pg_list_gtt_relfrozenxids();
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+-- should empty
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+analyze temp_table_test_systemview;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+-- get data after analyze;
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+
+-- get all object info in current schema
+select relname ,relkind, relpersistence, reloptions from pg_class c, pg_namespace n where c.relnamespace = n.oid and n.nspname = 'global_temporary_table' order by relkind,relpersistence,relname;
+
+reset search_path;
+drop schema global_temporary_table cascade;
+-- should empty
+select * from pg_list_gtt_relfrozenxids();
+
-- 
2.32.0 (Apple Git-132)

#350Wenjing Zeng
wjzeng2012@gmail.com
In reply to: Wenjing Zeng (#349)
4 attachment(s)
Re: [Proposal] Global temporary tables

Update GTT v66 to fix conflicts with the latest code.

Regards, Wenjing.

Wenjing Zeng <wjzeng2012@gmail.com> 于2022年1月20日周四 17:53写道:

Show quoted text

very glad to see your reply.
Thank you very much for your review of the code and found so many problems.
There was a conflict between the latest code and patch, I have corrected
it and provided a new patch (V65).
Waiting for your feedback.

Regards, Wenjing.

Andrew Bille <andrewbille@gmail.com> 于2022年1月10日周一 17:17写道:

Hi!

I could not detect crashes with your last patch, so I think the patch is
ready for a review.
Please, also consider fixing error messages, as existing ones don't
follow message writing guidelines.
https://www.postgresql.org/docs/14/error-style-guide.html

I corrected the ERROR message of GTT according to the link and the
existing error message.
Some comments and code refactoring were also done.

Regards, Andrew

On Thu, Dec 23, 2021 at 7:36 PM wenjing <wjzeng2012@gmail.com> wrote:

Andrew Bille <andrewbille@gmail.com> 于2021年12月21日周二 14:00写道:

Hi!
Thanks for new patches.
Yet another crash reproduced on master with v63 patches:

CREATE TABLESPACE ts LOCATION '/tmp/ts';
CREATE GLOBAL TEMP TABLE tbl (num1 bigint);
INSERT INTO tbl (num1) values (1);
CREATE INDEX tbl_idx ON tbl (num1);
REINDEX (TABLESPACE ts) TABLE tbl;

This is a feature made in PG14 that supports reindex change tablespaces.
Thank you for pointing that out and I fixed it in v64.
Waiting for your feedback.

Attachments:

0001-gtt-v66-reademe.patchapplication/octet-stream; name=0001-gtt-v66-reademe.patchDownload
diff --git a/README.gtt.txt b/README.gtt.txt
new file mode 100644
index 00000000000..d181df9acd7
--- /dev/null
+++ b/README.gtt.txt
@@ -0,0 +1,172 @@
+Global Temporary Table(GTT)
+=========================================
+
+Feature description
+-----------------------------------------
+
+Previously, temporary tables are defined once and automatically
+exist (starting with empty contents) in every session before using them.
+
+The temporary table implementation in PostgreSQL, known as Local temp tables(LTT),
+did not fully comply with the SQL standard. This version added the support of
+Global Temporary Table .
+
+The metadata of Global Temporary Table is persistent and shared among sessions.
+The data stored in the Global temporary table is independent of sessions. This
+means, when a session creates a Global Temporary Table and writes some data.
+Other sessions cannot see those data, but they have an empty Global Temporary
+Table with same schema.
+
+Like local temporary table, Global Temporary Table supports ON COMMIT PRESERVE ROWS
+or ON COMMIT DELETE ROWS clause, so that data in the temporary table can be
+cleaned up or preserved automatically when a session exits or a transaction COMMITs.
+
+Unlike Local Temporary Table, Global Temporary Table does not support
+ON COMMIT DROP clauses.
+
+In following paragraphs, we use GTT for Global Temporary Table and LTT for
+local temporary table.
+
+Main design ideas
+-----------------------------------------
+In general, GTT and LTT use the same storage and buffer design and
+implementation. The storage files for both types of temporary tables are named
+as t_backendid_relfilenode, and the local buffer is used to cache the data.
+
+The schema of GTTs is shared among sessions while their data are not. We build
+a new mechanisms to manage those non-shared data and their statistics.
+Here is the summary of changes:
+
+1) CATALOG
+GTTs store session-specific data. The storage information of GTTs'data, their
+transaction information, and their statistics are not stored in the catalog.
+
+2) STORAGE INFO & STATISTICS INFO & TRANSACTION INFO
+In order to maintain durability and availability of GTTs'session-specific data,
+their storage information, statistics, and transaction information is managed
+in a local hash table tt_storage_local_hash.
+
+3) DDL
+Currently, GTT supports almost all table'DDL except CLUSTER/VACUUM FULL.
+Part of the DDL behavior is limited by shared definitions and multiple copies of
+local data, and we added some structures to handle this.
+
+A shared hash table active_gtt_shared_hash is added to track the state of the
+GTT in a different session. This information is recorded in the hash table
+during the DDL execution of the GTT.
+
+The data stored in a GTT can only be modified or accessed by owning session.
+The statements that only modify data in a GTT do not need a high level of
+table locking. The operations making those changes include truncate GTT,
+reindex GTT, and lock GTT.
+
+4) MVCC commit log(clog) cleanup
+Each GTT in a session has its own piece of data, and they have their own
+transaction information. We set up data structures to track and maintain
+this information. The cleaning of CLOGs also needs to consider the transaction
+information of GTT.
+
+Detailed design
+-----------------------------------------
+
+1. CATALOG
+1.1 relpersistence
+define RELPERSISTENCEGLOBALTEMP 'g'
+Mark Global Temporary Table in pg_class relpersistence to 'g'. The relpersistence
+of indexes created on the GTT, sequences on GTT and toast tables on GTT are
+also set to 'g'
+
+1.2 on commit clause
+LTT's status associated with on commit DELETE ROWS and on commit PRESERVE ROWS
+is not stored in catalog. Instead, GTTs need a bool value on_commit_delete_rows
+in reloptions which is shared among sessions.
+
+1.3 gram.y
+GTT is already supported in syntax tree. We remove the warning message
+"GLOBAL is deprecated in temporary table creation" and mark
+relpersistence = RELPERSISTENCEGLOBALTEMP.
+
+2. STORAGE INFO & STATISTICS INFO & TRANSACTION INFO
+2.1. gtt_storage_local_hash
+Each backend creates a local hashtable gtt_storage_local_hash to track a GTT's
+storage file information, statistics, and transaction information.
+
+2.2 GTT storage file info track
+1) When one session inserts data into a GTT for the first time, record the
+storage info to gtt_storage_local_hash.
+2) Use beforeshmemexit to ensure that all files of session GTT are deleted when
+the session exits.
+
+2.3 statistics info
+1) relpages reltuples relallvisible relfilenode
+2) The statistics of each column from pg_statistic
+All the above information is stored in gtt_storage_local_hash.
+When doing vacuum or analyze, GTT's statistic is updated, which is used by
+the SQL planner.
+The statistics summarizes only data in the current session.
+
+2.3 transaction info track
+frozenxid minmulti from pg_class is stored to gtt_storage_local_hash.
+
+3 DDL
+3.1. active_gtt_shared_hash
+This is the hash table created in shared memory to trace the GTT files initialized
+in each session. Each hash entry contains a bitmap that records the backendid of
+the initialized GTT file. With this hash table, we know which backend/session
+is using this GTT. Such information is used during GTT's DDL operations.
+
+3.2 DROP GTT
+One GTT is allowed to be deleted when there is only one session using the table
+and the session is the current session. After holding the lock on GTT,
+active_gtt_shared_hash is checked to ensure that.
+
+3.3 ALTER GTT/DROP INDEX ON GTT
+Same as drop GTT.
+
+3.4 CREATE INDEX ON GTT
+1) create index on GTT statements build index based on local data in a session.
+2) After the index is created, record the index metadata to the catalog.
+3) Other sessions can enable or disable the local GTT index.
+
+3.5 TRUNCATE/REINDEX GTT
+The SQL truncate/reindex command open the GTT using AccessShareLock lock,
+not AccessExclusiveLock, because this command only cleans up local data and
+local buffers in current session. This allows these operations to be executed
+concurrently between sessions, unlike normal tables.
+
+3.6 LOCK GTT
+A lock GTT statement does not hold any relation lock.
+
+3.7 CLUSTER GTT/VACUUM FULL GTT
+The current version does not support.
+
+4 MVCC commit log(clog) cleanup
+
+The GTT storage file contains transaction information. Queries for GTT data rely
+on transaction information such as clog. The transaction information required by
+each session may be completely different. We need to ensure that the transaction
+information of the GTT data is not cleaned up during its lifetime and that
+transaction resources are recycled at the instance level.
+
+4.1 The session level GTT oldest frozenxid
+1) To manage all GTT transaction information, add session level oldest frozenxid
+in each session. When one GTT is created or removed, record the session level
+oldest frozenxid and store it in MyProc.
+2) When vacuum advances the database's frozenxid, session level oldest frozenxid
+should be considered. This is acquired by searching all of MyProc. This way,
+we can avoid the clog required by GTTs to be cleaned.
+
+4.2 vacuum GTT
+Users can perform vacuum over a GTT to clean up local data in the GTT.
+
+4.3 autovacuum GTT
+Autovacuum skips all GTTs, because the data in GTTs is only visible in current session.
+
+5 OTHERS
+5.1 Parallel query
+Planner does not produce parallel query plans for SQL related to GTT. Because
+GTT private data cannot be accessed across processes.
+
+5.2 WAL and Logical replication
+Like LTT, the DML on GTT does not record WAL and is not parsed or replay by
+the logical replication.
\ No newline at end of file
-- 
2.30.1 (Apple Git-130)

0002-gtt-v66-doc.patchapplication/octet-stream; name=0002-gtt-v66-doc.patchDownload
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 473a0a4aeb..e510bde8ac 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -169,32 +169,67 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     <listitem>
      <para>
       If specified, the table is created as a temporary table.
-      Temporary tables are automatically dropped at the end of a
-      session, or optionally at the end of the current transaction
-      (see <literal>ON COMMIT</literal> below).  The default
-      search_path includes the temporary schema first and so identically
-      named existing permanent tables are not chosen for new plans
+      Optionally, <literal>GLOBAL</literal> or <literal>LOCAL</literal>
+      can be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
+      They represent two types of temporary tables supported by <productname>PostgreSQL</productname>:
+      global temporary table and local temporary table. Without specified
+      GLOBAL or LOCAL, a local temporary table is created by default.
+     </para>
+
+    <para>
+     Both types of temporary tables’ data are truncated at the
+     end of a session or optionally at the end of the current transaction.
+     (see <literal>ON COMMIT</literal> below). For global temporary table,
+     its schema is reserved and reused by future sessions or transactions.
+     For local temporary table, both its data and its schema are dropped.
+    </para>
+
+    <variablelist>
+     <varlistentry>
+      <term><literal>Global Temporary Table</literal></term>
+      <listitem>
+       <para>
+        Global temporary table are defined just once and automatically exist
+        (starting with empty contents) in every session that needs them.
+        The schema definition of temporary tables is persistent and shared among sessions.
+        However, the data in temporary tables are kept private to sessions themselves,
+        even though they use same name and same schema.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>Local Temporary Table</literal></term>
+     <listitem>
+     <para>
+      Local temporary table are automatically dropped at the end of a
+      session (include schema and data). Future sessions need to create
+      their own temporary tables when they are used.
+     </para>
+     <para>
+      The default search_path includes the temporary schema first and so
+      identically named existing permanent tables are not chosen for new plans
       while the temporary table exists, unless they are referenced
       with schema-qualified names. Any indexes created on a temporary
       table are automatically temporary as well.
      </para>
+     </listitem>
+     </varlistentry>
+    </variablelist>
 
-     <para>
-      The <link linkend="autovacuum">autovacuum daemon</link> cannot
-      access and therefore cannot vacuum or analyze temporary tables.
-      For this reason, appropriate vacuum and analyze operations should be
-      performed via session SQL commands.  For example, if a temporary
-      table is going to be used in complex queries, it is wise to run
-      <command>ANALYZE</command> on the temporary table after it is populated.
-     </para>
+    <para>
+     The <link linkend="autovacuum">autovacuum daemon</link> cannot
+     access and therefore cannot vacuum or analyze temporary tables.
+     For this reason, appropriate vacuum and analyze operations should be
+     performed via session SQL commands.  For example, if a temporary
+     table is going to be used in complex queries, it is wise to run
+     <command>ANALYZE</command> on the temporary table after it is populated.
+    </para>
+    <para>
+     The Temporary table resembles the SQL standard, but has some differences.
+     see <xref linkend="sql-createtable-compatibility"/> below.
+    </para>
 
-     <para>
-      Optionally, <literal>GLOBAL</literal> or <literal>LOCAL</literal>
-      can be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
-      This presently makes no difference in <productname>PostgreSQL</productname>
-      and is deprecated; see
-      <xref linkend="sql-createtable-compatibility"/> below.
-     </para>
     </listitem>
    </varlistentry>
 
@@ -2133,13 +2168,17 @@ CREATE TABLE cities_partdef
    <title>Temporary Tables</title>
 
    <para>
-    Although the syntax of <literal>CREATE TEMPORARY TABLE</literal>
-    resembles that of the SQL standard, the effect is not the same.  In the
-    standard,
-    temporary tables are defined just once and automatically exist (starting
-    with empty contents) in every session that needs them.
-    <productname>PostgreSQL</productname> instead
-    requires each session to issue its own <literal>CREATE TEMPORARY
+    Although the syntax of <literal>CREATE GLOBAL/LOCAL TEMPORARY TABLE</literal>
+    resembles that of the SQL standard, the effect is not the same.
+    The global temporary table follows the SQL standards while local temporary
+    table does not.
+   </para>
+
+   <para>
+    First, in the standard, both global and local temporary tables are defined just
+    once and automatically exist (starting with empty contents) in every session
+    that needs them. For local temporary tables, <productname>PostgreSQL</productname>
+    instead requires each session to issue its own <literal>CREATE LOCAL TEMPORARY
     TABLE</literal> command for each temporary table to be used.  This allows
     different sessions to use the same temporary table name for different
     purposes, whereas the standard's approach constrains all instances of a
@@ -2147,29 +2186,14 @@ CREATE TABLE cities_partdef
    </para>
 
    <para>
-    The standard's definition of the behavior of temporary tables is
-    widely ignored.  <productname>PostgreSQL</productname>'s behavior
-    on this point is similar to that of several other SQL databases.
-   </para>
-
-   <para>
-    The SQL standard also distinguishes between global and local temporary
+    Second, the SQL standard distinguishes between global and local temporary
     tables, where a local temporary table has a separate set of contents for
     each SQL module within each session, though its definition is still shared
-    across sessions.  Since <productname>PostgreSQL</productname> does not
+    across sessions. Since <productname>PostgreSQL</productname> does not
     support SQL modules, this distinction is not relevant in
     <productname>PostgreSQL</productname>.
    </para>
 
-   <para>
-    For compatibility's sake, <productname>PostgreSQL</productname> will
-    accept the <literal>GLOBAL</literal> and <literal>LOCAL</literal> keywords
-    in a temporary table declaration, but they currently have no effect.
-    Use of these keywords is discouraged, since future versions of
-    <productname>PostgreSQL</productname> might adopt a more
-    standard-compliant interpretation of their meaning.
-   </para>
-
    <para>
     The <literal>ON COMMIT</literal> clause for temporary tables
     also resembles the SQL standard, but has some differences.
@@ -2177,7 +2201,8 @@ CREATE TABLE cities_partdef
     default behavior is <literal>ON COMMIT DELETE ROWS</literal>.  However, the
     default behavior in <productname>PostgreSQL</productname> is
     <literal>ON COMMIT PRESERVE ROWS</literal>.  The <literal>ON COMMIT
-    DROP</literal> option does not exist in SQL.
+    DROP</literal> option does not exist in SQL and is not supported by
+    global temporary table.
    </para>
   </refsect2>
 
-- 
2.30.1 (Apple Git-130)

0003-gtt-v66-implementation.patchapplication/octet-stream; name=0003-gtt-v66-implementation.patchDownload
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index f996f9a5727..18f1f7a42c0 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -18,6 +18,7 @@
 #include "access/toast_internals.h"
 #include "access/visibilitymap.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "funcapi.h"
 #include "miscadmin.h"
 #include "storage/bufmgr.h"
@@ -340,6 +341,13 @@ verify_heapam(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(ctx.rel) &&
+		!gtt_storage_attached(RelationGetRelid(ctx.rel)))
+	{
+		relation_close(ctx.rel, AccessShareLock);
+		PG_RETURN_NULL();
+	}
+
 	/* Early exit if the relation is empty */
 	nblocks = RelationGetNumberOfBlocks(ctx.rel);
 	if (!nblocks)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index d592655258a..051fe802824 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -159,6 +159,18 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * For global temporary table save on commit clause info to reloptions.
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temporary table on commit options",
+			RELOPT_KIND_HEAP,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},
 	/* list terminator */
 	{{NULL}}
 };
@@ -1834,6 +1846,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 d4bf0c7563d..b623e958c38 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1024,7 +1024,7 @@ gistproperty(Oid index_oid, int attno,
 XLogRecPtr
 gistGetFakeLSN(Relation rel)
 {
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	if (RELATION_IS_TEMP(rel))
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index a259a301fa8..6a85ec9debe 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -153,7 +153,7 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	if (!RELATION_IS_TEMP(index))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 98230aac49c..99d0926b85b 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -5835,6 +5835,7 @@ heap_abort_speculative(Relation relation, ItemPointer tid)
 	Buffer		buffer;
 	TransactionId prune_xid;
 
+	Assert(TransactionIdIsNormal(relation->rd_rel->relfrozenxid));
 	Assert(ItemPointerIsValid(tid));
 
 	block = ItemPointerGetBlockNumber(tid);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 39ef8a0b77d..0bb308d1c5e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -593,7 +593,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
@@ -645,7 +645,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(RelationGetSmgr(rel), dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 9c88b9bd71a..a817e944cb2 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -333,6 +333,9 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 	TransactionId FreezeLimit;
 	MultiXactId MultiXactCutoff;
 
+	Assert(TransactionIdIsNormal(rel->rd_rel->relfrozenxid));
+        Assert(MultiXactIdIsValid(rel->rd_rel->relminmxid));
+
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (IsAutoVacuumWorkerProcess() &&
 							  params->log_min_duration >= 0));
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 6b5f01e1d07..099f942c936 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -677,6 +678,14 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		/*
+		 * If a global temporary table storage file is not initialized in the
+		 * this session, its index does not have a root page, just returns 0.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index eefebb7bb83..97bec1e0e98 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -44,6 +44,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index dfd5fb669ee..c0fd68652f7 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -504,6 +504,7 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 7e99de88b34..049eb621bdd 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -62,6 +62,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"
@@ -102,6 +103,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -349,8 +351,21 @@ heap_create(const char *relname,
 	if (!RELKIND_HAS_TABLESPACE(relkind))
 		reltablespace = InvalidOid;
 
+	/*
+	 * For create global temporary table, initialization storage information
+	 * and recorded in into pg_class, but not initialization stroage file.
+	 * When data is inserted into a temporary table, its storage file is initialized.
+	 */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		create_storage = false;
+		if (OidIsValid(relfilenode))
+			elog(ERROR, "global temporary table can not reuse an existing relfilenode");
+
+		relfilenode = relid;
+	}
 	/* Don't create storage for relkinds without physical storage. */
-	if (!RELKIND_HAS_STORAGE(relkind))
+	else if (!RELKIND_HAS_STORAGE(relkind))
 		create_storage = false;
 	else
 	{
@@ -403,7 +418,7 @@ heap_create(const char *relname,
 											relpersistence,
 											relfrozenxid, relminmxid);
 		else if (RELKIND_HAS_STORAGE(rel->rd_rel->relkind))
-			RelationCreateStorage(rel->rd_node, relpersistence);
+			RelationCreateStorage(rel->rd_node, relpersistence, rel);
 		else
 			Assert(false);
 	}
@@ -970,6 +985,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -995,8 +1011,21 @@ AddNewRelationTuple(Relation pg_class_desc,
 		new_rel_reltup->reltuples = 1;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/*
+	 * The transaction information of the global temporary table is stored
+	 * in hash table, not in pg_class.
+	 */
+	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;
@@ -1404,6 +1433,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1486,10 +1516,13 @@ 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
+	 * For local temporary table, if there's a special on-commit action, remember it.
+	 * For global temporary table, on-commit action is recorded during initial storage.
+	 * See remember_gtt_storage_info.
 	 */
-	if (oncommit != ONCOMMIT_NOOP)
-		register_on_commit_action(relid, oncommit);
+	if (oncommit != ONCOMMIT_NOOP &&
+		relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+		register_on_commit_action(relid, oncommit, false);
 
 	/*
 	 * ok, the relation has been cataloged, so close our relations and return
@@ -1986,6 +2019,13 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/*
+	 * Only when other sessions are not using this global temporary table,
+	 * is it allowed to drop it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		CheckGlobalTempTableNotInUse(rel, "DROP GLOBAL TEMPORARY TABLE");
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3272,7 +3312,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3284,7 +3324,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3320,31 +3360,49 @@ RelationTruncateIndexes(Relation heapRelation)
  * ON COMMIT truncation of temporary tables, where it doesn't matter.
  */
 void
-heap_truncate(List *relids)
+heap_truncate(List *relids, bool is_global_temp)
 {
 	List	   *relations = NIL;
-	ListCell   *cell;
+	List	   *lock_modes = NIL;
+	ListCell   *cell_rel;
+	ListCell   *cell_lock;
 
 	/* Open relations for processing, and grab exclusive access on each */
-	foreach(cell, relids)
+	foreach(cell_rel, relids)
 	{
-		Oid			rid = lfirst_oid(cell);
+		Oid			rid = lfirst_oid(cell_rel);
 		Relation	rel;
+		LOCKMODE	lockmode;
+
+		/*
+		 * Truncate global temporary table only clears local data,
+		 * so only low-level locks need to be held.
+		 */
+		if (is_global_temp)
+		{
+			lockmode = RowExclusiveLock;
+			if (!gtt_storage_attached(rid))
+				continue;
+		}
+		else
+			lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
+		lock_modes = lappend_int(lock_modes, lockmode);
 	}
 
 	/* Don't allow truncate on tables that are referenced by foreign keys */
 	heap_truncate_check_FKs(relations, true);
 
 	/* OK to do it */
-	foreach(cell, relations)
+	forboth(cell_rel, relations, cell_lock, lock_modes)
 	{
-		Relation	rel = lfirst(cell);
+		Relation	rel = lfirst(cell_rel);
+		LOCKMODE	lockmode = lfirst_int(cell_lock);
 
 		/* Truncate the relation */
-		heap_truncate_one_rel(rel);
+		heap_truncate_one_rel(rel, lockmode);
 
 		/* Close the relation, but keep exclusive lock on it until commit */
 		table_close(rel, NoLock);
@@ -3361,7 +3419,7 @@ heap_truncate(List *relids)
  * checked permissions etc, and must have obtained AccessExclusiveLock.
  */
 void
-heap_truncate_one_rel(Relation rel)
+heap_truncate_one_rel(Relation rel, LOCKMODE lockmode)
 {
 	Oid			toastrelid;
 
@@ -3374,18 +3432,25 @@ heap_truncate_one_rel(Relation rel)
 
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		gtt_update_relstats(rel, 0, 0, 0,
+							RecentXmin,GetOldestMultiXactId());
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		if (RELATION_IS_GLOBAL_TEMP(toastrel))
+			gtt_update_relstats(toastrel, 0, 0, 0,
+								RecentXmin, GetOldestMultiXactId());
+
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
@@ -3856,3 +3921,15 @@ StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
 
 	CacheInvalidateRelcache(parent);
 }
+
+void
+CheckGlobalTempTableNotInUse(Relation rel, const char *stmt)
+{
+	if (RELATION_IS_GLOBAL_TEMP(rel) &&
+		is_other_backend_use_gtt(RelationGetRelid(rel)))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_IN_USE),
+				 errmsg("cannot %s \"%s\" because it is being used by other session",
+						stmt, RelationGetRelationName(rel)),
+				 errdetail("Please try detach all other sessions using this table and try again.")));
+}
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 5e3fc2b35dc..03900de5a00 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -54,6 +54,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"
@@ -125,7 +126,8 @@ static void UpdateIndexRelation(Oid indexoid, Oid heapoid,
 								bool isready);
 static void index_update_stats(Relation rel,
 							   bool hasindex,
-							   double reltuples);
+							   double reltuples,
+							   bool isreindex);
 static void IndexCheckExclusion(Relation heapRelation,
 								Relation indexRelation,
 								IndexInfo *indexInfo);
@@ -737,6 +739,21 @@ index_create(Relation heapRelation,
 	MultiXactId relminmxid;
 	bool		create_storage = !OidIsValid(relFileNode);
 
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* disable create index on global temporary table with concurrent mode */
+		concurrent = false;
+
+		/*
+		 * For the case that some backend is applied relcache message to create
+		 * an index on a global temporary table, if this table in the current
+		 * backend are not initialized, the creation of index storage on the
+		 * table are also skipped.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/* constraint flags can only be set when a constraint is requested */
 	Assert((constr_flags == 0) ||
 		   ((flags & INDEX_CREATE_ADD_CONSTRAINT) != 0));
@@ -1243,7 +1260,8 @@ index_create(Relation heapRelation,
 		 */
 		index_update_stats(heapRelation,
 						   true,
-						   -1.0);
+						   -1.0,
+						   false);
 		/* Make the above update visible */
 		CommandCounterIncrement();
 	}
@@ -2151,7 +2169,7 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 * lock (see comments in RemoveRelations), and a non-concurrent DROP is
 	 * more efficient.
 	 */
-	Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+	Assert(!RelpersistenceTsTemp(get_rel_persistence(indexId)) ||
 		   (!concurrent && !concurrent_lock_mode));
 
 	/*
@@ -2183,6 +2201,14 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/*
+	 * Allow to drop index on global temporary table when only current
+	 * backend use it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation))
+		CheckGlobalTempTableNotInUse(userHeapRelation,
+									 "DROP INDEX ON GLOBAL TEMPORARY TABLE");
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2789,7 +2815,8 @@ FormIndexDatum(IndexInfo *indexInfo,
 static void
 index_update_stats(Relation rel,
 				   bool hasindex,
-				   double reltuples)
+				   double reltuples,
+				   bool isreindex)
 {
 	Oid			relid = RelationGetRelid(rel);
 	Relation	pg_class;
@@ -2797,6 +2824,13 @@ index_update_stats(Relation rel,
 	Form_pg_class rd_rel;
 	bool		dirty;
 
+	/*
+	 * Most of the global Temp table data is updated to the local hash, and reindex
+	 * does not refresh relcache, so call a separate function.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		return index_update_gtt_relstats(rel, hasindex, reltuples, isreindex);
+
 	/*
 	 * We always update the pg_class row using a non-transactional,
 	 * overwrite-in-place update.  There are several reasons for this:
@@ -3016,6 +3050,25 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, progress_index, progress_vals);
 	}
 
+	/* For build index on global temporary table */
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		/*
+		 * If the storage for the index in this session is not initialized,
+		 * it needs to be created.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			/* Before create init storage, fix the local Relcache first */
+			gtt_index_force_enable(indexRelation);
+
+			Assert(gtt_storage_attached(RelationGetRelid(heapRelation)));
+
+			/* Init storage for index */
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3098,11 +3151,13 @@ index_build(Relation heapRelation,
 	 */
 	index_update_stats(heapRelation,
 					   true,
-					   stats->heap_tuples);
+					   stats->heap_tuples,
+					   isreindex);
 
 	index_update_stats(indexRelation,
 					   false,
-					   stats->index_tuples);
+					   stats->index_tuples,
+					   isreindex);
 
 	/* Make the updated catalog row versions visible */
 	CommandCounterIncrement();
@@ -3557,6 +3612,8 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	PGRUsage	ru0;
 	bool		progress = ((params->options & REINDEXOPT_REPORT_PROGRESS) != 0);
 	bool		set_tablespace = false;
+	LOCKMODE	lockmode_on_heap = ShareLock;
+	LOCKMODE	lockmode_on_index = AccessExclusiveLock;
 
 	pg_rusage_init(&ru0);
 
@@ -3570,10 +3627,35 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	if (!OidIsValid(heapId))
 		return;
 
+	/*
+	 * For reindex on global temporary table, If the storage for the index
+	 * in current session is not initialized, nothing is done.
+	 */
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		if (OidIsValid(params->tablespaceOid))
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot change tablespace of global temporary table")));
+
+		if (!gtt_storage_attached(indexId))
+		{
+			/* Suppress use of the target index while rebuilding it */
+			SetReindexProcessing(heapId, indexId);
+			/* Re-allow use of target index */
+			ResetReindexProcessing();
+			return;
+		}
+
+		/* For global temp table reindex handles local data, using low-level locks */
+		lockmode_on_heap = AccessShareLock;
+		lockmode_on_index = AccessShareLock;
+	}
+
 	if ((params->options & REINDEXOPT_MISSING_OK) != 0)
-		heapRelation = try_table_open(heapId, ShareLock);
+		heapRelation = try_table_open(heapId, lockmode_on_heap);
 	else
-		heapRelation = table_open(heapId, ShareLock);
+		heapRelation = table_open(heapId, lockmode_on_heap);
 
 	/* if relation is gone, leave */
 	if (!heapRelation)
@@ -3599,7 +3681,7 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	 * Open the target index relation and get an exclusive lock on it, to
 	 * ensure that no one else is touching this particular index.
 	 */
-	iRel = index_open(indexId, AccessExclusiveLock);
+	iRel = index_open(indexId, lockmode_on_index);
 
 	if (progress)
 		pgstat_progress_update_param(PROGRESS_CREATEIDX_ACCESS_METHOD_OID,
@@ -3841,7 +3923,7 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
  * index rebuild.
  */
 bool
-reindex_relation(Oid relid, int flags, ReindexParams *params)
+reindex_relation(Oid relid, int flags, ReindexParams *params, LOCKMODE lockmode)
 {
 	Relation	rel;
 	Oid			toast_relid;
@@ -3857,9 +3939,9 @@ reindex_relation(Oid relid, int flags, ReindexParams *params)
 	 * should match ReindexTable().
 	 */
 	if ((params->options & REINDEXOPT_MISSING_OK) != 0)
-		rel = try_table_open(relid, ShareLock);
+		rel = try_table_open(relid, lockmode);
 	else
-		rel = table_open(relid, ShareLock);
+		rel = table_open(relid, lockmode);
 
 	/* if relation is gone, leave */
 	if (!rel)
@@ -3966,7 +4048,7 @@ reindex_relation(Oid relid, int flags, ReindexParams *params)
 
 		newparams.options &= ~(REINDEXOPT_MISSING_OK);
 		newparams.tablespaceOid = InvalidOid;
-		result |= reindex_relation(toast_relid, flags, &newparams);
+		result |= reindex_relation(toast_relid, flags, &newparams, lockmode);
 	}
 
 	return result;
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 5dbac9c437a..033fef26fe3 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -656,6 +656,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/* Do not allow create global temporary table in temporary schemas */
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temporary relation in temporary schema")));
+			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 9b8075536a7..733d89c0e8a 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,10 +27,12 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
 #include "utils/hsearch.h"
+#include "utils/inval.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
 
@@ -61,6 +63,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			temprelOid;			/* InvalidOid if not a global temporary rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -115,7 +118,7 @@ AddPendingSync(const RelFileNode *rnode)
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -126,7 +129,12 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/*
+		 * global temporary table and local temporary table use same
+		 * design on storage module.
+		 */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -154,6 +162,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
@@ -165,6 +174,30 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		AddPendingSync(&rnode);
 	}
 
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		Assert(rel && RELATION_IS_GLOBAL_TEMP(rel));
+
+		/*
+		 * Remember the reloid of global temporary table, which is used for
+		 * transaction commit or rollback.
+		 * see smgrDoPendingDeletes.
+		 */
+		pending->temprelOid = RelationGetRelid(rel);
+
+		/* Remember global temporary table storage info to localhash */
+		remember_gtt_storage_info(rnode, rel);
+
+		/* Make cache invalid and set new relnode to local relcache. */
+		CacheInvalidateRelcache(rel);
+
+		/*
+		 * Make the pg_class row change or relation map change visible.  This will
+		 * cause the relcache entry to get updated, too.
+		 */
+		CommandCounterIncrement();
+	}
+
 	return srel;
 }
 
@@ -201,11 +234,20 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/*
+	 * Remember the reloid of global temporary table, which is used for
+	 * transaction commit or rollback.
+	 * see smgrDoPendingDeletes.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->temprelOid = RelationGetRelid(rel);
+
 	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
@@ -618,6 +660,7 @@ smgrDoPendingDeletes(bool isCommit)
 	int			nrels = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -647,14 +690,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->temprelOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -664,12 +711,21 @@ smgrDoPendingDeletes(bool isCommit)
 
 	if (nrels > 0)
 	{
+		int	i;
+
 		smgrdounlinkall(srels, nrels, false);
 
-		for (int i = 0; i < nrels; i++)
+		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* free global temporary table info in localhash */
+			if (gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 00000000000..31ddef288e2
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1651 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  The body implementation of Global temparary table.
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *	  See src/backend/catalog/GTT_README for Global temparary table's
+ *	  requirements and design.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/heapam.h"
+#include "access/multixact.h"
+#include "access/visibilitymap.h"
+#include "catalog/catalog.h"
+#include "catalog/index.h"
+#include "catalog/pg_statistic.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
+#include "commands/tablecmds.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "nodes/pg_list.h"
+#include "storage/bufmgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/sinvaladt.h"
+#include "utils/catcache.h"
+#include "utils/guc.h"
+#include "utils/inval.h"
+#include "utils/syscache.h"
+
+/*
+ * WORDNUM/BITNUM/BITMAPSET_SIZE copy from bitmapset.c, and use BITS_PER_BITMAPWORD
+ * and typedef bitmapword from nodes/bitmapset.h. GTT records the status of global
+ * temporary tables in each session using bitmaps, which are stored in shared memory.
+ */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_info_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+
+int	vacuum_gtt_defer_check_age = 0;
+
+/*
+ * Global temp table or senquence has separate state in each session,
+ * record in a shared memory bitmap.
+ * However, index and toast relation for global temp table is not,
+ * because they depend on tables and don't exist independently.
+ */
+#define GLOBAL_TEMP_RELKIND_STATE_IS_SHARED(relkind) \
+	((relkind) == RELKIND_RELATION || \
+	 (relkind) == RELKIND_SEQUENCE)
+
+/*
+ * The Global temporary table's shared hash table data structure
+ */
+typedef struct gtt_ctl_data
+{
+	LWLock		lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+/* record this global temporary table in which backends are being used */
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+/*
+ * The Global temporary table's local hash table data structure
+ */
+/* Record the storage information and statistical information of the global temporary table */
+typedef struct
+{
+	Oid			relfilenode;	/* relation */
+	Oid			spcnode;		/* tablespace */
+
+	/* pg_class relation statistics */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+
+	/* pg_statistic column statistics */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid, char relkind);
+static void gtt_storage_checkout(Oid relid, bool isCommit, char relkind);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_free_statistics(gtt_relfilenode *rnode);
+static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, Oid spcnode, bool missing_ok);
+static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok);
+static Bitmapset *copy_active_gtt_bitmap(Oid relid);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+/*
+ * Calculate shared hash table entry size for GTT.
+ */
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(GetMaxBackends() + 1);
+
+	/* hash entry header size */
+	hash_entry_size = MAXALIGN(sizeof(gtt_shared_hash_entry));
+
+	/*
+	 * hash entry data size
+	 * this is a bitmap in shared memory, each backend have a bit.
+	 * ensure we have enough words to store the upper bit.
+	 */
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+/*
+ * Calculate shared hash table max size for GTT.
+ */
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	/* shared hash header size */
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	/* hash entry size */
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	/* max size */
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+/*
+ * Initialization shared hash table for GTT.
+ */
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl = ShmemInitStruct("gtt_shared_ctl",
+									 sizeof(gtt_ctl_data),
+									 &found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GLOBAL_TEMP_TABLE_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&gtt_shared_ctl->lock, LWTRANCHE_GLOBAL_TEMP_TABLE_CTL);
+		gtt_shared_ctl->max_entry = max_active_gtt;
+		gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+	}
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(gtt_fnode);
+	info.entrysize = action_gtt_shared_hash_entry_size();
+	active_gtt_shared_hash = ShmemInitHash("active gtt shared hash",
+										   gtt_shared_ctl->max_entry,
+										   gtt_shared_ctl->max_entry,
+										   &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+/*
+ * Record GTT relid to shared hash table, which means that current session is using this GTT.
+ */
+static void
+gtt_storage_checkin(Oid relid, char relkind)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			found;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (!GLOBAL_TEMP_RELKIND_STATE_IS_SHARED(relkind))
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash, (void *)&(fnode), HASH_ENTER_NULL, &found);
+	if (!entry)
+	{
+		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_global_temporary_table.")));
+	}
+
+	if (!found)
+	{
+		int		wordnum;
+
+		/* init bitmap in shared memory */
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(GetMaxBackends() + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	/* record current backendid in shared bitmap */
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+/*
+ * Remove the GTT relid record from the shared hash table which means that current session is
+ * not use this GTT.
+ */
+static void
+gtt_storage_checkout(Oid relid, bool isCommit, char relkind)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (!GLOBAL_TEMP_RELKIND_STATE_IS_SHARED(relkind))
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash, (void *) &(fnode), HASH_FIND, NULL);
+	if (!entry)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		if (isCommit)
+			elog(WARNING, "relid %u not exist in gtt shared hash when drop local storage", relid);
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= GetMaxBackends());
+
+	/* remove current backendid from shared bitmap */
+	bms_del_member(entry->map, MyBackendId);
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return;
+}
+
+/*
+ * Gets usage information for a GTT from shared hash table.
+ * The information is in the form of bitmap.
+ * Quickly copy the entire bitmap from shared memory and return it.
+ * that to avoid holding locks for a long time.
+ */
+static Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	Bitmapset		*map_copy = NULL;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash, (void *) &(fnode), HASH_FIND, NULL);
+	if (entry)
+	{
+		Assert(entry->map);
+		/* copy the entire bitmap */
+		if (!bms_is_empty(entry->map))
+			map_copy = bms_copy(entry->map);
+	}
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return map_copy;
+}
+
+/*
+ * Check if there are other sessions using this GTT besides the current session.
+ */
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			in_use = false;
+	gtt_fnode		fnode;
+	int			num_use = 0;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash, (void *) &(fnode), HASH_FIND, NULL);
+	if (!entry)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		return false;
+	}
+
+	Assert(entry->map);
+	Assert(MyBackendId >= 1 && MyBackendId <= GetMaxBackends());
+
+	/* how many backend are using this GTT */
+	num_use = bms_num_members(entry->map);
+	if (num_use == 0)
+		in_use = false;
+	else if (num_use == 1)
+	{
+		/* check if this is itself */
+		if(bms_is_member(MyBackendId, entry->map))
+			in_use = false;
+		else
+			in_use = true;
+	}
+	else
+		in_use = true;
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return in_use;
+}
+
+/*
+ * Record GTT information to local hash.
+ * They include GTT storage info, transaction info and statistical info.
+ */
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+	gtt_local_hash_entry		*entry;
+	MemoryContext			oldcontext;
+	gtt_relfilenode			*new_node = NULL;
+	Oid				relid = RelationGetRelid(rel);
+	int				natts = 0;
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("Global temporary table is disabled"),
+				 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "index \"%s\" is invalid that cannot create storage", RelationGetRelationName(rel));
+
+	/* First time through: initialize the hash table */
+	if (!gtt_storage_local_hash)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		HASHCTL		ctl;
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_info_context = AllocSetContextCreate(CacheMemoryContext,
+												 "gtt info context",
+												 ALLOCSET_DEFAULT_SIZES);
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		ctl.hcxt = gtt_info_context;
+		gtt_storage_local_hash = hash_create("global temporary table info",
+											 GTT_LOCAL_HASH_SIZE,
+											 &ctl, HASH_ELEM | HASH_BLOBS);
+	}
+
+	Assert(CacheMemoryContext);
+	Assert(gtt_info_context);
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	entry = gtt_search_by_relid(relid, true);
+	if (!entry)
+	{
+		bool	found = false;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash, (void *) &relid, HASH_ENTER, &found);
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			/* record the on commit clause */
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS, true);
+			}
+		}
+
+		gtt_storage_checkin(relid, entry->relkind);
+	}
+
+	/* record storage info relstat columnstats and transaction info to relfilenode list */
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	new_node->natts = 0;
+	new_node->attnum = NULL;
+	new_node->att_stat_tups = NULL;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* init structure for column statistics */
+	natts = RelationGetNumberOfAttributes(rel);
+	new_node->attnum = palloc0(sizeof(int) * natts);
+	new_node->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+	new_node->natts = natts;
+
+	/* remember transaction info */
+	if (RELKIND_HAS_TABLE_AM(entry->relkind))
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+
+	/* Registration callbacks are used to trigger cleanup during process exit */
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+/*
+ * Remove GTT information from local hash when transaction commit/rollback.
+ */
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	gtt_relfilenode		*d_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"global temp relid %u not found in local hash", relid);
+
+		return;
+	}
+
+	d_rnode = gtt_search_relfilenode(entry, rnode.relNode, rnode.spcNode, true);
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"global temp relfilenode %u not found in rel %u", rnode.relNode, relid);
+		else
+		{
+			/* rollback transaction */
+			if (entry->relfilenode_list == NIL)
+			{
+				gtt_storage_checkout(relid, isCommit, entry->relkind);
+
+				hash_search(gtt_storage_local_hash, (void *) &(relid), HASH_REMOVE, NULL);
+			}
+
+			return;
+		}
+	}
+
+	/* Clean up transaction info from Local order list and MyProc */
+	if (RELKIND_HAS_TABLE_AM(entry->relkind))
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+
+		/* this is valid relfrozenxid */
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	/* delete relfilenode from rel entry */
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	gtt_free_statistics(d_rnode);
+
+	if (entry->relfilenode_list == NIL)
+	{
+		/* tell shared hash that current session will no longer use this GTT */
+		gtt_storage_checkout(relid, isCommit, entry->relkind);
+
+		hash_search(gtt_storage_local_hash, (void *) &(relid), HASH_REMOVE, NULL);
+	}
+
+	return;
+}
+
+/*
+ * Check if current session is using this GTT.
+ */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool			found = false;
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (!OidIsValid(relid))
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry)
+		found = true;
+
+	return found;
+}
+
+/*
+ * When backend exit, bulk cleaning all GTT storage and local buffer of this backend.
+ */
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS			status;
+	gtt_local_hash_entry	*entry;
+
+	if (!gtt_storage_local_hash)
+		return;
+
+	/* Need to ensure we have a usable transaction. */
+	AbortOutOfAnyTransaction();
+
+	/* Search all relfilenode for GTT in current session */
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel[1];
+			RelFileNode		rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel[0] = smgropen(rnode, MyBackendId);
+			smgrdounlinkall(srel, 1, false);
+			smgrclose(srel[0]);
+		}
+
+		gtt_storage_checkout(entry->relid, false, entry->relkind);
+
+		hash_search(gtt_storage_local_hash, (void *) &(entry->relid), HASH_REMOVE, NULL);
+	}
+
+	/* set to global area */
+	MyProc->gtt_frozenxid = InvalidTransactionId;
+
+	return;
+}
+
+/*
+ * Update GTT relstats(relpage/reltuple/relallvisible)
+ * to local hash.
+ */
+void
+gtt_update_relstats(Relation relation, BlockNumber relpages, double reltuples,
+						BlockNumber relallvisible, TransactionId relfrozenxid,
+						TransactionId relminmxid)
+{
+	Oid						relid = RelationGetRelid(relation);
+	gtt_relfilenode			*gtt_rnode = NULL;
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (!OidIsValid(relid))
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (relpages >= 0 &&
+		gtt_rnode->relpages != (int32)relpages)
+	{
+		gtt_rnode->relpages = (int32)relpages;
+		relation->rd_rel->relpages = (int32) relpages;
+	}
+
+	if (reltuples >= 0 &&
+		gtt_rnode->reltuples != (float4)reltuples)
+	{
+		gtt_rnode->reltuples = (float4)reltuples;
+		relation->rd_rel->reltuples = (float4)reltuples;
+	}
+
+	if (relallvisible >= 0 &&
+		gtt_rnode->relallvisible != (int32)relallvisible)
+	{
+		gtt_rnode->relallvisible = (int32)relallvisible;
+		relation->rd_rel->relallvisible = (int32)relallvisible;
+	}
+
+	/* only heap contain transaction information and relallvisible */
+	if (RELKIND_HAS_TABLE_AM(entry->relkind))
+	{
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNextTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			/* set to local order list */
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			/* set to global area */
+			set_gtt_session_relfrozenxid();
+			relation->rd_rel->relfrozenxid = relfrozenxid;
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+			relation->rd_rel->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search GTT relstats(relpage/reltuple/relallvisible)
+ * from local has.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update GTT info(definition is same as pg_statistic)
+ * to local hash.
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+	MemoryContext		oldcontext;
+	bool		found = false;
+	int			i = 0;
+
+	/* not support whole row or system column */
+	if (attnum <= 0)
+		return;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return;
+
+	Assert(entry->relid == reloid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	/* switch context to gtt_info_context for store tuple at heap_form_tuple */
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == 0)
+		{
+			Assert(gtt_rnode->att_stat_tups[i] == NULL);
+			gtt_rnode->attnum[i] = attnum;
+			gtt_rnode->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+			found = true;
+			break;
+		}
+		else if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			heap_freetuple(gtt_rnode->att_stat_tups[i]);
+			gtt_rnode->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+			found = true;
+			break;
+		}
+	}
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!found)
+		elog(WARNING, "analyze can not update relid %u column %d statistics after add or drop column, try truncate table first", reloid, attnum);
+
+	return;
+}
+
+/*
+ * Search GTT statistic info(definition is same as pg_statistic)
+ * from local hash.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	int			i = 0;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	/* not support whole row or system column */
+	if (attnum <= 0)
+		return NULL;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return NULL;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return NULL;
+
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			return gtt_rnode->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Insert a RelfrozenXID into the list and keep the list in order.
+ */
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int		i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Remove a RelfrozenXID from order list gtt_session_relfrozenxid_list.
+ */
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+/*
+ * Update of backend Level oldest relfrozenxid to MyProc.
+ * This makes each backend's oldest RelFrozenxID globally visible.
+ */
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list != NIL)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	if (MyProc->gtt_frozenxid != gtt_frozenxid)
+		MyProc->gtt_frozenxid = gtt_frozenxid;
+}
+
+/*
+ * Get GTT column level data statistics.
+ */
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate		*tupstore;
+	HeapTuple		tuple;
+	Relation		rel = NULL;
+	Oid			reloid = PG_GETARG_OID(0);
+	int			attnum = PG_GETARG_INT32(1);
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	if (!RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	/* get data from local hash */
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum		values[Natts_pg_statistic];
+		bool		isnull[Natts_pg_statistic];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, AccessShareLock);
+	relation_close(pg_tatistic, AccessShareLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get GTT table level data statistics.
+ */
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate	*tupstore;
+	TupleDesc	tupdesc;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid			reloid = PG_GETARG_OID(0);
+	Oid			relnode = 0;
+	BlockNumber		relpages = 0;
+	BlockNumber		relallvisible = 0;
+	uint32			relfrozenxid = 0;
+	uint32			relminmxid = 0;
+	double			reltuples = 0;
+	Relation		rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	if (!RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[6];
+		bool	isnull[6];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get a list of backend pids that are currently using this GTT.
+ */
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	PGPROC			*proc = NULL;
+	Bitmapset		*map = NULL;
+	Tuplestorestate		*tupstore;
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	HeapTuple		tuple;
+	Oid			reloid = PG_GETARG_OID(0);
+	Relation		rel = NULL;
+	int				backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	if (!RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	/* get data from share hash */
+	map = copy_active_gtt_bitmap(reloid);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			/* backendid map to process pid */
+			proc = BackendIdGetProc(backendid);
+			if (proc && proc->pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+				pid_t	pid = proc->pid;
+
+				memset(isnull, 0, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, AccessShareLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get backend level oldest relfrozenxid of each backend using GTT in current database.
+ */
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate		*tupstore;
+	TupleDesc		tupdesc;
+	MemoryContext	oldcontext;
+	TransactionId	oldest = InvalidTransactionId;
+	List			*pids = NULL;
+	List			*xids = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	/* Get all session level oldest relfrozenxid that in current database use global temp table */
+	oldest = gtt_get_oldest_frozenxids_in_current_database(&pids, &xids);
+	if (TransactionIdIsValid(oldest))
+	{
+		HeapTuple		tuple;
+		ListCell		*lc1 = NULL;
+		ListCell		*lc2 = NULL;
+
+		Assert(list_length(pids) == list_length(xids));
+		forboth(lc1, pids, lc2, xids)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, 0, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(lfirst_int(lc1));
+			values[1] = UInt32GetDatum(lfirst_oid(lc2));
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	list_free(pids);
+	list_free(xids);
+
+	return (Datum) 0;
+}
+
+/*
+ * In order to build the GTT index, force enable GTT'index.
+ */
+void
+gtt_index_force_enable(Relation index)
+{
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(RelationGetRelid(index)));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+/*
+ * Fix the local state of the GTT's index.
+ */
+void
+gtt_correct_index_session_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid heapOid = index->rd_index->indrelid;
+
+	/* Must be GTT */
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	/*
+	 * If this GTT is not initialized in the current session,
+	 * its index status is temporarily set to invalid(local relcache).
+	 */
+	if (gtt_storage_attached(heapOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+/*
+ * Initialize storage of GTT and build empty index in this session.
+ */
+void
+gtt_init_storage(CmdType operation, Relation relation)
+{
+	Oid			toastrelid;
+	List		*indexoidlist = NIL;
+	ListCell	*l;
+
+	if (!(operation == CMD_INSERT))
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	/* Each GTT is initialized once in each backend */
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	/* init heap storage */
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	indexoidlist = RelationGetIndexList(relation);
+	foreach(l, indexoidlist)
+	{
+		Oid			indexOid = lfirst_oid(l);
+		Relation	index = index_open(indexOid, RowExclusiveLock);
+		IndexInfo	*info = BuildDummyIndexInfo(index);
+
+		index_build(relation, index, info, true, false);
+		/* after build index, index re-enabled */
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+		index_close(index, NoLock);
+	}
+	list_free(indexoidlist);
+
+	/* rebuild index for global temp toast table */
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+
+		/* init index storage */
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid			indexId = lfirst_oid(indlist);
+			Relation	index = index_open(indexId, RowExclusiveLock);
+			IndexInfo	*info = BuildDummyIndexInfo(index);
+
+			/* build empty index */
+			index_build(toastrel, index, info, true, false);
+			Assert(index->rd_index->indisvalid);
+			Assert(index->rd_index->indislive);
+			Assert(index->rd_index->indisready);
+			index_close(index, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+/*
+ * Release the data structure memory used to store GTT storage info.
+ */
+static void
+gtt_free_statistics(gtt_relfilenode *rnode)
+{
+	int i;
+
+	Assert(rnode);
+
+	for (i = 0; i < rnode->natts; i++)
+	{
+		if (rnode->att_stat_tups[i])
+		{
+			heap_freetuple(rnode->att_stat_tups[i]);
+			rnode->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (rnode->attnum)
+		pfree(rnode->attnum);
+
+	if (rnode->att_stat_tups)
+		pfree(rnode->att_stat_tups);
+
+	pfree(rnode);
+
+	return;
+}
+
+/*
+ * Get the current relfilenode of this GTT.
+ */
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
+/*
+ * Get a relfilenode used by this GTT during the transaction life cycle.
+ */
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, Oid spcnode, bool missing_ok)
+{
+	gtt_relfilenode		*rnode = NULL;
+	ListCell		*lc;
+
+	Assert(entry);
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == relfilenode &&
+			gtt_rnode->spcnode == spcnode)
+		{
+			rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (!missing_ok && rnode == NULL)
+		elog(ERROR, "search relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+	return rnode;
+}
+
+/*
+ * Get one GTT info from local hash.
+ */
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (!gtt_storage_local_hash)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash, (void *) &(relid), HASH_FIND, NULL);
+	if (!entry && !missing_ok)
+		elog(ERROR, "relid %u not found in local hash", relid);
+
+	return entry;
+}
+
+/*
+ * update pg_class entry after CREATE INDEX or REINDEX for global temp table
+ */
+void
+index_update_gtt_relstats(Relation rel, bool hasindex, double reltuples, bool isreindex)
+{
+	Oid			relid = RelationGetRelid(rel);
+
+	Assert(RELATION_IS_GLOBAL_TEMP(rel));
+
+	/* see index_update_stats() */
+	if (reltuples == 0 && rel->rd_rel->reltuples < 0)
+		reltuples = -1;
+
+	/* update reltuples relpages relallvisible to localhash */
+	if (reltuples >= 0)
+	{
+		BlockNumber relpages = RelationGetNumberOfBlocks(rel);
+		BlockNumber relallvisible = 0;
+
+		if (rel->rd_rel->relkind != RELKIND_INDEX)
+			visibilitymap_count(rel, &relallvisible, NULL);
+		else
+			relallvisible = 0;
+
+		gtt_update_relstats(rel, relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
+	}
+
+	/* update relhasindex to pg_class */
+	if (hasindex != rel->rd_rel->relhasindex)
+	{
+		Relation		pg_class = table_open(RelationRelationId, RowExclusiveLock);
+		Form_pg_class	rd_rel;
+		HeapTuple		tuple;
+
+		Assert(rel->rd_rel->relkind != RELKIND_INDEX);
+		tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u", relid);
+
+		rd_rel = (Form_pg_class) GETSTRUCT(tuple);
+		rd_rel->relhasindex = hasindex;
+		heap_inplace_update(pg_class, tuple);
+		heap_freetuple(tuple);
+		table_close(pg_class, RowExclusiveLock);
+	}
+	else if (!isreindex)
+	{
+		/*
+		 * For global temp table
+		 * Even if pg_class does not change, relcache needs to be rebuilt
+		 * for flush rd_indexlist list (for a table) at create index process.
+		 *
+		 * Each session index has an independent data and cache(rd_amcache)
+		 * so relcache of the table and index do not need to be refreshed at reindex process.
+		 * This is different from the reindex of a regular table.
+		 */
+		CacheInvalidateRelcache(rel);
+	}
+}
+
+/*
+ * update statistics for one global temp relation
+ */
+void
+vac_update_gtt_relstats(Relation relation,
+					BlockNumber num_pages, double num_tuples,
+					BlockNumber num_all_visible_pages,
+					bool hasindex, TransactionId frozenxid,
+					MultiXactId minmulti, bool in_outer_xact)
+{
+	Oid			relid = RelationGetRelid(relation);
+	Relation	pg_class;
+	HeapTuple	ctup;
+	Form_pg_class pgcform;
+	bool		dirty = false;
+	List		*idxs = NIL;
+
+	Assert(RELATION_IS_GLOBAL_TEMP(relation));
+
+	/* For global temporary table, store relstats and transaction info to the localhash */
+	gtt_update_relstats(relation, num_pages, num_tuples,
+						num_all_visible_pages, frozenxid, minmulti);
+
+	if (relation->rd_rel->relkind == RELKIND_RELATION)
+		idxs = RelationGetIndexList(relation);
+
+	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+
+	/* Fetch a copy of the tuple to scribble on */
+	ctup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(ctup))
+		elog(ERROR, "pg_class entry for relid %u vanished during vacuuming", relid);
+	pgcform = (Form_pg_class) GETSTRUCT(ctup);
+
+	/* Apply DDL updates, but not inside an outer transaction (see above) */
+	if (!in_outer_xact)
+	{
+		/*
+		 * If we didn't find any indexes, reset relhasindex.
+		 *
+		 * Global temporary table may contain indexes that are not valid locally.
+		 * The catalog should not be updated based on local invalid index.
+		 */
+		if (pgcform->relhasindex && !hasindex && idxs == NIL)
+		{
+			pgcform->relhasindex = false;
+			dirty = true;
+		}
+
+		/* We also clear relhasrules and relhastriggers if needed */
+		if (pgcform->relhasrules && relation->rd_rules == NULL)
+		{
+			pgcform->relhasrules = false;
+			dirty = true;
+		}
+		if (pgcform->relhastriggers && relation->trigdesc == NULL)
+		{
+			pgcform->relhastriggers = false;
+			dirty = true;
+		}
+	}
+
+	/* If anything changed, write out the tuple. */
+	if (dirty)
+		heap_inplace_update(pg_class, ctup);
+
+	table_close(pg_class, RowExclusiveLock);
+
+	list_free(idxs);
+}
+
+void
+GlobalTempRelationSetNewRelfilenode(Relation relation)
+{
+	Oid			newrelfilenode;
+	MultiXactId minmulti = InvalidMultiXactId;
+	TransactionId freezeXid = InvalidTransactionId;
+	RelFileNode newrnode;
+
+	Assert(RELATION_IS_GLOBAL_TEMP(relation));
+	Assert(!RelationIsMapped(relation));
+
+	/* Allocate a new relfilenode */
+	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
+									   RELPERSISTENCE_GLOBAL_TEMP);
+
+	/*
+	 * Schedule unlinking of the old storage at transaction commit.
+	 */
+	RelationDropStorage(relation);
+
+	newrnode = relation->rd_node;
+	newrnode.relNode = newrelfilenode;
+
+	if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind))
+	{
+		table_relation_set_new_filenode(relation, &newrnode,
+										RELPERSISTENCE_GLOBAL_TEMP,
+										&freezeXid, &minmulti);
+	}
+	else if (RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+	{
+		/* handle these directly, at least for now */
+		SMgrRelation srel;
+
+		srel = RelationCreateStorage(newrnode, RELPERSISTENCE_GLOBAL_TEMP, relation);
+		smgrclose(srel);
+	}
+	else
+	{
+		/* we shouldn't be called for anything else */
+		elog(ERROR, "relation \"%s\" does not have storage",
+			 RelationGetRelationName(relation));
+	}
+
+	RelationAssumeNewRelfilenode(relation);
+
+	/* The local relcache and hashtable have been updated */
+	Assert(gtt_fetch_current_relfilenode(RelationGetRelid(relation)) == newrelfilenode);
+	Assert(relation->rd_node.relNode == newrelfilenode);
+}
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 3cb69b1f87b..1625ee9345b 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 736479295ad..f08ac47b729 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -105,7 +106,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);
 
@@ -186,6 +187,17 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -602,14 +614,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]));
 		}
 
 		/* Build extended statistics (if there are any). */
@@ -1620,7 +1633,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;
@@ -1722,31 +1735,48 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/*
+		 * For global temporary table,
+		 * Update column statistic to localhash, not pg_statistic.
+		 */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 02a7e94bf9b..cba222ddee0 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/toasting.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
@@ -390,6 +391,22 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+	{
+		if (gtt_storage_attached(RelationGetRelid(OldHeap)))
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("cannot cluster global temporary table")));
+
+		relation_close(OldHeap, AccessExclusiveLock);
+		pgstat_progress_end_command();
+		return;
+	}
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
@@ -585,6 +602,9 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
 	TransactionId frozenXid;
 	MultiXactId cutoffMulti;
 
+	/* not support cluster global temp table yet */
+	Assert(!RELATION_IS_GLOBAL_TEMP(OldHeap));
+
 	/* Mark the correct index as clustered */
 	if (OidIsValid(indexOid))
 		mark_index_clustered(OldHeap, indexOid, true);
@@ -1428,7 +1448,7 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 	pgstat_progress_update_param(PROGRESS_CLUSTER_PHASE,
 								 PROGRESS_CLUSTER_PHASE_REBUILD_INDEX);
 
-	reindex_relation(OIDOldHeap, reindex_flags, &reindex_params);
+	reindex_relation(OIDOldHeap, reindex_flags, &reindex_params, ShareLock);
 
 	/* Report that we are now doing clean up */
 	pgstat_progress_update_param(PROGRESS_CLUSTER_PHASE,
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 7da7105d44b..8351f41a154 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -289,7 +289,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP_ON_CURRENT_SESSION(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, whereClause,
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 7b3f5a84b82..03692b4a73a 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -30,6 +30,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/copyfrom_internal.h"
 #include "commands/progress.h"
@@ -652,7 +653,7 @@ CopyFrom(CopyFromState cstate)
 	 */
 	ExecInitRangeTable(estate, cstate->range_table);
 	resultRelInfo = target_resultRelInfo = makeNode(ResultRelInfo);
-	ExecInitResultRelation(estate, resultRelInfo, 1);
+	ExecInitResultRelation(estate, resultRelInfo, 1, CMD_INSERT);
 
 	/* Verify the named relation is a valid target for INSERT */
 	CheckValidResultRel(resultRelInfo, CMD_INSERT);
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 9abbb6b5552..a944f244496 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -32,6 +32,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/namespace.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/toasting.h"
 #include "commands/createas.h"
 #include "commands/matview.h"
@@ -520,6 +521,12 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 	 */
 	intoRelationDesc = table_open(intoRelationAddr.objectId, AccessExclusiveLock);
 
+	/*
+	 * Try initializing the global Temp table storage file before writing data
+	 * to the table.
+	 */
+	gtt_init_storage(CMD_INSERT, intoRelationDesc);
+
 	/*
 	 * Make sure the constructed table does not have RLS enabled.
 	 *
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 560dcc87a2c..2d0edf1ae4c 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -111,6 +111,7 @@ struct ReindexIndexCallbackState
 {
 	ReindexParams params;		/* options from statement */
 	Oid			locked_table_oid;	/* tracks previously locked table */
+	LOCKMODE	lockmode;
 };
 
 /*
@@ -570,7 +571,7 @@ DefineIndex(Oid relationId,
 	 * is more efficient.  Do this before any use of the concurrent option is
 	 * done.
 	 */
-	if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+	if (stmt->concurrent && !RelpersistenceTsTemp(get_rel_persistence(relationId)))
 		concurrent = true;
 	else
 		concurrent = false;
@@ -2582,24 +2583,46 @@ ReindexIndex(RangeVar *indexRelation, ReindexParams *params, bool isTopLevel)
 	 */
 	state.params = *params;
 	state.locked_table_oid = InvalidOid;
+	state.lockmode = AccessShareLock;
 	indOid = RangeVarGetRelidExtended(indexRelation,
-									  (params->options & REINDEXOPT_CONCURRENTLY) != 0 ?
-									  ShareUpdateExclusiveLock : AccessExclusiveLock,
+									  AccessShareLock,
 									  0,
 									  RangeVarCallbackForReindexIndex,
 									  &state);
 
 	/*
 	 * Obtain the current persistence and kind of the existing index.  We
-	 * already hold a lock on the index.
+	 * already hold a AccessShareLock on the index.
+	 * If this is not a global temp object, apply a larger lock.
 	 */
 	persistence = get_rel_persistence(indOid);
-	relkind = get_rel_relkind(indOid);
+	if (persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		LOCKMODE	table_lockmode;
+		LOCKMODE	index_lockmode;
+
+		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0)
+		{
+			table_lockmode = ShareUpdateExclusiveLock;
+			index_lockmode = ShareUpdateExclusiveLock;
+		}
+		else
+		{
+			table_lockmode = ShareLock;
+			index_lockmode = AccessExclusiveLock;
+		}
 
+		/* lock heap first */
+		Assert(OidIsValid(state.locked_table_oid));
+		LockRelationOid(state.locked_table_oid, table_lockmode);
+		LockRelationOid(indOid, index_lockmode);
+	}
+
+	relkind = get_rel_relkind(indOid);
 	if (relkind == RELKIND_PARTITIONED_INDEX)
 		ReindexPartitions(indOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 persistence != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(persistence))
 		ReindexRelationConcurrently(indOid, params);
 	else
 	{
@@ -2621,15 +2644,7 @@ RangeVarCallbackForReindexIndex(const RangeVar *relation,
 {
 	char		relkind;
 	struct ReindexIndexCallbackState *state = arg;
-	LOCKMODE	table_lockmode;
-
-	/*
-	 * Lock level here should match table lock in reindex_index() for
-	 * non-concurrent case and table locks used by index_concurrently_*() for
-	 * concurrent case.
-	 */
-	table_lockmode = (state->params.options & REINDEXOPT_CONCURRENTLY) != 0 ?
-		ShareUpdateExclusiveLock : ShareLock;
+	LOCKMODE	table_lockmode = state->lockmode;
 
 	/*
 	 * If we previously locked some other index's heap, and the name we're
@@ -2690,6 +2705,8 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 {
 	Oid			heapOid;
 	bool		result;
+	char		relpersistence;
+	LOCKMODE	lockmode;
 
 	/*
 	 * The lock level used here should match reindex_relation().
@@ -2700,15 +2717,27 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 	 * locks on our temporary table.
 	 */
 	heapOid = RangeVarGetRelidExtended(relation,
-									   (params->options & REINDEXOPT_CONCURRENTLY) != 0 ?
-									   ShareUpdateExclusiveLock : ShareLock,
+									   AccessShareLock,
 									   0,
 									   RangeVarCallbackOwnsTable, NULL);
 
+	relpersistence = get_rel_persistence(heapOid);
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		lockmode = AccessShareLock;
+	else
+	{
+		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0)
+			lockmode = ShareUpdateExclusiveLock;
+		else
+			lockmode = ShareLock;
+
+		LockRelationOid(heapOid, lockmode);
+	}
+
 	if (get_rel_relkind(heapOid) == RELKIND_PARTITIONED_TABLE)
 		ReindexPartitions(heapOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(relpersistence))
 	{
 		result = ReindexRelationConcurrently(heapOid, params);
 
@@ -2725,7 +2754,8 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 		result = reindex_relation(heapOid,
 								  REINDEX_REL_PROCESS_TOAST |
 								  REINDEX_REL_CHECK_CONSTRAINTS,
-								  &newparams);
+								  &newparams,
+								  lockmode);
 		if (!result)
 			ereport(NOTICE,
 					(errmsg("table \"%s\" has no indexes to reindex",
@@ -3120,7 +3150,7 @@ ReindexMultipleInternal(List *relids, ReindexParams *params)
 		Assert(!RELKIND_HAS_PARTITIONS(relkind));
 
 		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			relpersistence != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(relpersistence))
 		{
 			ReindexParams newparams = *params;
 
@@ -3142,13 +3172,20 @@ ReindexMultipleInternal(List *relids, ReindexParams *params)
 		{
 			bool		result;
 			ReindexParams newparams = *params;
+			LOCKMODE	lockmode;
+
+			if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+				lockmode = AccessShareLock;
+			else
+				lockmode = ShareLock;
 
 			newparams.options |=
 				REINDEXOPT_REPORT_PROGRESS | REINDEXOPT_MISSING_OK;
 			result = reindex_relation(relid,
 									  REINDEX_REL_PROCESS_TOAST |
 									  REINDEX_REL_CHECK_CONSTRAINTS,
-									  &newparams);
+									  &newparams,
+									  lockmode);
 
 			if (result && (params->options & REINDEXOPT_VERBOSE) != 0)
 				ereport(INFO,
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 4b3f79704f8..c544885f53e 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -51,12 +51,33 @@ LockTableCommand(LockStmt *lockstmt)
 		RangeVar   *rv = (RangeVar *) lfirst(p);
 		bool		recurse = rv->inh;
 		Oid			reloid;
+		LOCKMODE	lockmode = lockstmt->mode;
+		char		relpersistence;
 
-		reloid = RangeVarGetRelidExtended(rv, lockstmt->mode,
-										  lockstmt->nowait ? RVR_NOWAIT : 0,
+		reloid = RangeVarGetRelidExtended(rv, NoLock, 0,
 										  RangeVarCallbackForLockTable,
 										  (void *) &lockstmt->mode);
 
+		relpersistence = get_rel_persistence(reloid);
+		/* lock statement does not hold any lock on global temp table */
+		if (relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			if (!lockstmt->nowait)
+				LockRelationOid(reloid, lockmode);
+			else if (!ConditionalLockRelationOid(reloid, lockmode))
+			{
+				/* try to throw error by name; relation could be deleted... */
+				char	   *relname = get_rel_name(reloid);
+
+				if (!relname)
+					return;		/* child concurrently dropped, just skip it */
+				ereport(ERROR,
+						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						 errmsg("could not obtain lock on relation \"%s\"",
+								relname)));
+			}
+		}
+
 		if (get_rel_relkind(reloid) == RELKIND_VIEW)
 			LockViewRecurse(reloid, lockstmt->mode, lockstmt->nowait, NIL);
 		else if (recurse)
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index ab592ce2f15..1d0e7077020 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -25,11 +25,14 @@
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
 #include "catalog/dependency.h"
+#include "catalog/heap.h"
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -108,6 +111,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -220,9 +224,16 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	rel = table_open(seqoid, AccessExclusiveLock);
 	tupDesc = RelationGetDescr(rel);
 
-	/* now initialize the sequence's data */
-	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	/*
+	 * For global temp sequence, the storage is not initialized
+	 * when it is created, but when it is used.
+	 */
+	if (!RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		/* now initialize the sequence's data */
+		tuple = heap_form_tuple(tupDesc, value, null);
+		fill_seq_with_data(rel, tuple);
+	}
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -275,8 +286,6 @@ ResetSequence(Oid seq_relid)
 	Buffer		buf;
 	HeapTupleData seqdatatuple;
 	HeapTuple	tuple;
-	HeapTuple	pgstuple;
-	Form_pg_sequence pgsform;
 	int64		startv;
 
 	/*
@@ -287,12 +296,7 @@ ResetSequence(Oid seq_relid)
 	init_sequence(seq_relid, &elm, &seq_rel);
 	(void) read_seq_tuple(seq_rel, &buf, &seqdatatuple);
 
-	pgstuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seq_relid));
-	if (!HeapTupleIsValid(pgstuple))
-		elog(ERROR, "cache lookup failed for sequence %u", seq_relid);
-	pgsform = (Form_pg_sequence) GETSTRUCT(pgstuple);
-	startv = pgsform->seqstart;
-	ReleaseSysCache(pgstuple);
+	startv = get_seqence_start_value(seq_relid);
 
 	/*
 	 * Copy the existing sequence tuple.
@@ -457,6 +461,9 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 
 	init_sequence(relid, &elm, &seqrel);
 
+	if (RELATION_IS_GLOBAL_TEMP(seqrel))
+		CheckGlobalTempTableNotInUse(seqrel, "ALTER GLOBAL TEMPORARY SEQUENCE");
+
 	rel = table_open(SequenceRelationId, RowExclusiveLock);
 	seqtuple = SearchSysCacheCopy1(SEQRELID,
 								   ObjectIdGetDatum(relid));
@@ -617,7 +624,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -961,7 +968,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1185,6 +1192,14 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	/* Initializes the storage for sequence which the global temporary table belongs. */
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_sequence(seqrel);
+	}
 }
 
 
@@ -1959,3 +1974,57 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+/*
+ * Get the startValue of the sequence from syscache.
+ */
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+/*
+ * Initialize sequence which global temporary table belongs.
+ */
+void
+gtt_init_sequence(Relation rel)
+{
+	Datum		value[SEQ_COL_LASTCOL];
+	bool		null[SEQ_COL_LASTCOL];
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL - 1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+	null[SEQ_COL_LASTVAL - 1] = false;
+
+	value[SEQ_COL_LOG - 1] = Int64GetDatum((int64)0);
+	null[SEQ_COL_LOG - 1] = false;
+
+	value[SEQ_COL_CALLED - 1] = BoolGetDatum(false);
+	null[SEQ_COL_CALLED - 1] = false;
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple);
+	heap_freetuple(tuple);
+
+	return;
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 3e83f375b55..5b8f083a110 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"
@@ -119,6 +120,7 @@ typedef struct OnCommitItem
 	 */
 	SubTransactionId creating_subid;
 	SubTransactionId deleting_subid;
+	bool			 is_global_temp;
 } OnCommitItem;
 
 static List *on_commits = NIL;
@@ -625,7 +627,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
 static char GetAttributeCompression(Oid atttypid, char *compression);
-
+static OnCommitAction gtt_oncommit_option(List *options);
 
 /* ----------------------------------------------------------------
  *		DefineRelation
@@ -670,6 +672,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -681,7 +684,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * Check consistency of arguments
 	 */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !RelpersistenceTsTemp(stmt->relation->relpersistence))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -711,7 +714,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	if (RelpersistenceTsTemp(stmt->relation->relpersistence)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -812,6 +815,59 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* For global temporary table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		if (!(relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("global temporary relation can only be a regular table or sequence")));
+
+		if (inheritOids)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot create global temporary inherit table or global temporary partitioned table")));
+
+		/* Check oncommit clause and save to reloptions */
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot specify both ON COMMIT clause and on_commit_delete_rows")));
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_DROP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("specifying ON COMMIT DROP is not supported on a global temporary table")));
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("on_commit_delete_rows can only be used on global temporary table")));
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1436,7 +1492,7 @@ RemoveRelations(DropStmt *drop)
 		 * relation persistence cannot be known without its OID.
 		 */
 		if (drop->concurrent &&
-			get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(get_rel_persistence(relOid)))
 		{
 			Assert(list_length(drop->objects) == 1 &&
 				   drop->removeType == OBJECT_INDEX);
@@ -1645,9 +1701,9 @@ ExecuteTruncate(TruncateStmt *stmt)
 		Relation	rel;
 		bool		recurse = rv->inh;
 		Oid			myrelid;
-		LOCKMODE	lockmode = AccessExclusiveLock;
+		LOCKMODE	lockmode;
 
-		myrelid = RangeVarGetRelidExtended(rv, lockmode,
+		myrelid = RangeVarGetRelidExtended(rv, AccessShareLock,
 										   0, RangeVarCallbackForTruncate,
 										   NULL);
 
@@ -1655,9 +1711,21 @@ ExecuteTruncate(TruncateStmt *stmt)
 		if (list_member_oid(relids, myrelid))
 			continue;
 
-		/* open the relation, we already hold a lock on it */
+		/* open the relation, we need hold a low-level lock first */
 		rel = table_open(myrelid, NoLock);
 
+		/*
+		 * Truncate global temp table only cleans up the data in current session,
+		 * only low-level locks are required.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel))
+			lockmode = AccessShareLock;
+		else
+		{
+			lockmode = AccessExclusiveLock;
+			LockRelationOid(myrelid, lockmode);
+		}
+
 		/*
 		 * RangeVarGetRelidExtended() has done most checks with its callback,
 		 * but other checks with the now-opened Relation remain.
@@ -1907,6 +1975,7 @@ ExecuteTruncateGuts(List *explicit_rels,
 	foreach(cell, rels)
 	{
 		Relation	rel = (Relation) lfirst(cell);
+		LOCKMODE	lockmode;
 
 		/* Skip partitioned tables as there is nothing to do */
 		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
@@ -1957,6 +2026,19 @@ ExecuteTruncateGuts(List *explicit_rels,
 			continue;
 		}
 
+		/*
+		 * Skip the global temporary table that is not initialized for storage
+		 * in current session.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel))
+		{
+			lockmode = AccessShareLock;
+			if (!gtt_storage_attached(RelationGetRelid(rel)))
+				continue;
+		}
+		else
+			lockmode = AccessExclusiveLock;
+
 		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
@@ -1968,7 +2050,7 @@ ExecuteTruncateGuts(List *explicit_rels,
 			rel->rd_newRelfilenodeSubid == mySubid)
 		{
 			/* Immediate, non-rollbackable truncation is OK */
-			heap_truncate_one_rel(rel);
+			heap_truncate_one_rel(rel, lockmode);
 		}
 		else
 		{
@@ -2002,7 +2084,7 @@ ExecuteTruncateGuts(List *explicit_rels,
 			if (OidIsValid(toast_relid))
 			{
 				Relation	toastrel = relation_open(toast_relid,
-													 AccessExclusiveLock);
+													 lockmode);
 
 				RelationSetNewRelfilenode(toastrel,
 										  toastrel->rd_rel->relpersistence);
@@ -2013,7 +2095,7 @@ ExecuteTruncateGuts(List *explicit_rels,
 			 * Reconstruct the indexes to match, and we're done.
 			 */
 			reindex_relation(heap_relid, REINDEX_REL_PROCESS_TOAST,
-							 &reindex_params);
+							 &reindex_params, lockmode);
 		}
 
 		pgstat_count_truncate(rel);
@@ -3284,6 +3366,12 @@ CheckRelationTableSpaceMove(Relation rel, Oid newTableSpaceId)
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot move temporary tables of other sessions")));
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot move global temporary table \"%s\"",
+				 		RelationGetRelationName(rel))));
+
 	return true;
 }
 
@@ -4053,6 +4141,10 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temporary table only current session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		CheckGlobalTempTableNotInUse(rel, "ALTER GLOBAL TEMPORARY TABLE");
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -5384,6 +5476,25 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 
 			rel = table_open(tab->relid, NoLock);
 			find_composite_type_dependencies(rel->rd_rel->reltype, rel, NULL);
+
+			if (RELATION_IS_GLOBAL_TEMP(rel) && tab->rewrite > 0)
+			{
+				if (tab->chgPersistence)
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot change global temporary table persistence setting")));
+
+				if(gtt_storage_attached(tab->relid))
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot rewrite global temporary table \"%s\" when it has data in this session",
+								RelationGetRelationName(rel)),
+						 errhint("Please create a new connection and execute ALTER TABLE on the new connection.")));
+
+				/* global temp table has no data in this session, so only change catalog */
+				tab->rewrite = 0;
+			}
+
 			table_close(rel, NoLock);
 		}
 
@@ -5435,6 +5546,9 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot rewrite temporary tables of other sessions")));
 
+			/* Not support rewrite global temp table */
+			Assert(!RELATION_IS_GLOBAL_TEMP(OldHeap));
+
 			/*
 			 * Select destination tablespace (same as original unless user
 			 * requested a change)
@@ -9082,6 +9196,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -13440,7 +13560,9 @@ TryReuseIndex(Oid oldId, IndexStmt *stmt)
 		Relation	irel = index_open(oldId, NoLock);
 
 		/* If it's a partitioned index, there is no storage to share. */
-		if (irel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
+		/* multiple global temp table are not allow use same relfilenode */
+		if (irel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX &&
+			!RELATION_IS_GLOBAL_TEMP(irel))
 		{
 			stmt->oldNode = irel->rd_node.relNode;
 			stmt->oldCreateSubid = irel->rd_createSubid;
@@ -14102,6 +14224,11 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	/* option on_commit_delete_rows is only for global temp table and cannot be set by ALTER TABLE */
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "cannot set \"on_commit_delete_rows\" for relation \"%s\"",
+					RelationGetRelationName(rel));
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -14602,7 +14729,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(RelationGetSmgr(rel), dstrel, MAIN_FORKNUM,
@@ -16203,6 +16330,7 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
 	switch (rel->rd_rel->relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -16644,7 +16772,7 @@ AlterSeqNamespaces(Relation classRel, Relation rel,
  * Register a newly-created relation's ON COMMIT action.
  */
 void
-register_on_commit_action(Oid relid, OnCommitAction action)
+register_on_commit_action(Oid relid, OnCommitAction action, bool is_gloal_temp)
 {
 	OnCommitItem *oc;
 	MemoryContext oldcxt;
@@ -16663,6 +16791,7 @@ register_on_commit_action(Oid relid, OnCommitAction action)
 	oc->oncommit = action;
 	oc->creating_subid = GetCurrentSubTransactionId();
 	oc->deleting_subid = InvalidSubTransactionId;
+	oc->is_global_temp = is_gloal_temp;
 
 	/*
 	 * We use lcons() here so that ON COMMIT actions are processed in reverse
@@ -16708,6 +16837,7 @@ PreCommit_on_commit_actions(void)
 	ListCell   *l;
 	List	   *oids_to_truncate = NIL;
 	List	   *oids_to_drop = NIL;
+	List	   *oids_to_truncate_gtt = NIL;
 
 	foreach(l, on_commits)
 	{
@@ -16731,7 +16861,12 @@ PreCommit_on_commit_actions(void)
 				 * tables, as they must still be empty.
 				 */
 				if ((MyXactFlags & XACT_FLAGS_ACCESSEDTEMPNAMESPACE))
-					oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid);
+				{
+					if (oc->is_global_temp)
+						oids_to_truncate_gtt = lappend_oid(oids_to_truncate_gtt, oc->relid);
+					else
+						oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid);
+				}
 				break;
 			case ONCOMMIT_DROP:
 				oids_to_drop = lappend_oid(oids_to_drop, oc->relid);
@@ -16748,7 +16883,10 @@ PreCommit_on_commit_actions(void)
 	 * exists at truncation time.
 	 */
 	if (oids_to_truncate != NIL)
-		heap_truncate(oids_to_truncate);
+		heap_truncate(oids_to_truncate, false);
+
+	if (oids_to_truncate_gtt != NIL)
+		heap_truncate(oids_to_truncate_gtt, true);
 
 	if (oids_to_drop != NIL)
 	{
@@ -17747,6 +17885,13 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot attach temporary relation of another session as partition")));
 
+	/* If the parent is permanent, so must be all of its partitions. */
+	if (attachrel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach a global temporary relation as partition of permanent relation \"%s\"",
+						RelationGetRelationName(rel))));
+
 	/* Check if there are any columns in attachrel that aren't in the parent */
 	tupleDesc = RelationGetDescr(attachrel);
 	natts = tupleDesc->natts;
@@ -19239,3 +19384,39 @@ GetAttributeCompression(Oid atttypid, char *compression)
 
 	return cmethod;
 }
+
+/*
+ * Parse the on commit clause for the temporary table
+ */
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			bool	res = false;
+			char	*sval = defGetString(def);
+
+			/* It has to be a Boolean value */
+			if (!parse_bool(sval, &res))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
+
+			if (res)
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index b6767a5ff8c..630f7db5a8b 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -39,7 +39,9 @@
 #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/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/vacuum.h"
 #include "miscadmin.h"
@@ -1313,6 +1315,11 @@ vac_update_relstats(Relation relation,
 	Form_pg_class pgcform;
 	bool		dirty;
 
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+		return vac_update_gtt_relstats(relation, num_pages, num_tuples,
+									   num_all_visible_pages, hasindex,
+									   frozenxid, minmulti, in_outer_xact);
+
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch a copy of the tuple to scribble on */
@@ -1415,7 +1422,6 @@ vac_update_relstats(Relation relation,
 	table_close(rd, RowExclusiveLock);
 }
 
-
 /*
  *	vac_update_datfrozenxid() -- update pg_database.datfrozenxid for our DB
  *
@@ -1507,6 +1513,13 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/*
+		 * The relfrozenxid for a global temporary talble is stored in localhash,
+		 * not pg_class, See list_all_session_gtt_frozenxids()
+		 */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1564,6 +1577,42 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/* If enable global temporary table */
+	if (max_active_gtt > 0)
+	{
+		TransactionId	safe_age;
+		TransactionId	oldest_gtt_frozenxid =
+			gtt_get_oldest_frozenxids_in_current_database(NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid))
+		{
+			safe_age = oldest_gtt_frozenxid + vacuum_gtt_defer_check_age;
+			if (safe_age < FirstNormalTransactionId)
+				safe_age += FirstNormalTransactionId;
+
+			/*
+			 * We tolerate that the minimum age of gtt is less than
+			 * the minimum age of conventional tables, otherwise it will
+			 * throw warning message.
+			 */
+			if (TransactionIdIsNormal(safe_age) &&
+				TransactionIdPrecedes(safe_age, newFrozenXid))
+			{
+				ereport(WARNING,
+						(errmsg("global temporary table oldest relfrozenxid %u is far in the past",
+								oldest_gtt_frozenxid),
+						 errdetail("The oldest relfrozenxid %u in database \"%s\"", newFrozenXid, get_database_name(MyDatabaseId)),
+						 errhint("please consider cleaning up the data in global temporary table to avoid wraparound problems.")));
+			}
+
+			/*
+			 * We need to ensure that the clog required by gtt is not cleand.
+			 */
+			if (TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+				newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1915,6 +1964,19 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	/*
+	 * Skip those global temporary table that are not initialized in
+	 * this session.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel) &&
+		!gtt_storage_attached(RelationGetRelid(rel)))
+	{
+		relation_close(rel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index e183ab097c4..04ae8ff7073 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -527,6 +527,12 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	/* Global temporary table are not sensible. */
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temporary because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 549d9eb6963..370e4d09d1d 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -784,6 +784,10 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		/* Global temp table is one kind of temp table */
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 9df1f81ea89..2207dccf274 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -50,6 +50,7 @@
 #include "access/table.h"
 #include "access/tableam.h"
 #include "access/transam.h"
+#include "catalog/storage_gtt.h"
 #include "executor/executor.h"
 #include "executor/execPartition.h"
 #include "jit/jit.h"
@@ -832,7 +833,7 @@ ExecGetRangeTableRelation(EState *estate, Index rti)
  */
 void
 ExecInitResultRelation(EState *estate, ResultRelInfo *resultRelInfo,
-					   Index rti)
+					   Index rti, CmdType operation)
 {
 	Relation	resultRelationDesc;
 
@@ -843,6 +844,9 @@ ExecInitResultRelation(EState *estate, ResultRelInfo *resultRelInfo,
 					  NULL,
 					  estate->es_instrument);
 
+	/* Check and init global temporary table storage in this session */
+	gtt_init_storage(operation, resultRelationDesc);
+
 	if (estate->es_result_relations == NULL)
 		estate->es_result_relations = (ResultRelInfo **)
 			palloc0(estate->es_range_table_size * sizeof(ResultRelInfo *));
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 5ec699a9bd1..1401d999b40 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -38,6 +38,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -2754,13 +2755,13 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	{
 		mtstate->rootResultRelInfo = makeNode(ResultRelInfo);
 		ExecInitResultRelation(estate, mtstate->rootResultRelInfo,
-							   node->rootRelation);
+							   node->rootRelation, operation);
 	}
 	else
 	{
 		mtstate->rootResultRelInfo = mtstate->resultRelInfo;
 		ExecInitResultRelation(estate, mtstate->resultRelInfo,
-							   linitial_int(node->resultRelations));
+							   linitial_int(node->resultRelations), operation);
 	}
 
 	/* set up epqstate with dummy subplan data for the moment */
@@ -2788,7 +2789,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 		if (resultRelInfo != mtstate->rootResultRelInfo)
 		{
-			ExecInitResultRelation(estate, resultRelInfo, resultRelation);
+			ExecInitResultRelation(estate, resultRelInfo, resultRelation, operation);
 
 			/*
 			 * For child result relations, store the root result relation
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 169b1d53fc8..07a616d626b 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -48,7 +48,7 @@
 #include "partitioning/partprune.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/lsyscache.h"
-
+#include "utils/rel.h"
 
 /* results of subquery_is_pushdown_safe */
 typedef struct pushdown_safety_info
@@ -619,7 +619,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			if (RelpersistenceTsTemp(get_rel_persistence(rte->relid)))
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index bd09f85aea1..a56f0b8ceee 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6071,7 +6071,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
-	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+	if (RELATION_IS_TEMP(heap) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index a5002ad8955..22f64506caa 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -31,6 +31,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
 #include "catalog/pg_statistic_ext_data.h"
+#include "catalog/storage_gtt.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -222,6 +223,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temporary table in this session */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6ac2e9ce237..2d62d0a4036 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2902,7 +2902,7 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
 		 * creation query. It would be hard to refresh data or incrementally
 		 * maintain it if a source disappeared.
 		 */
-		if (isQueryUsingTempRelation(query))
+		if (isQueryUsingTempRelation(query) || isQueryUsingGlobalTempRelation(query))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("materialized views must not use temporary tables or views")));
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c4f32425060..55453690a50 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3435,17 +3435,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; }
@@ -11854,19 +11848,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 cb9e177b5e5..e333f427740 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -82,6 +82,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 isQueryUsingGlobalTempRelation_walker(Node *node, void *context);
 
 
 /*
@@ -3665,3 +3666,52 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * Like function isQueryUsingTempRelation_walker
+ * return true if any relation underlying
+ * the query is a global temporary table.
+ */
+static bool
+isQueryUsingGlobalTempRelation_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 = table_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				table_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 isQueryUsingGlobalTempRelation_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  isQueryUsingGlobalTempRelation_walker,
+								  context);
+}
+
+/* Check if the query uses global temporary table */
+bool
+isQueryUsingGlobalTempRelation(Query *query)
+{
+	return isQueryUsingGlobalTempRelation_walker((Node *) query, NULL);
+}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 99efa26ce4a..08f36df50be 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -447,6 +447,13 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	/*
+	 * If a sequence is bound to a global temporary table, then the sequence
+	 * must been "global temporary"
+	 */
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
@@ -3329,6 +3336,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		cxt.isforeign = false;
 	}
 	cxt.relation = stmt->relation;
+	/* Set the relpersistence to the context */
+	cxt.relation->relpersistence = RelationGetRelPersistence(rel);
 	cxt.rel = rel;
 	cxt.inhRelations = NIL;
 	cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 681ef91b81e..4ea4c5bfcc7 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2116,6 +2116,14 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/*
+			 * Aotuvacuum cannot vacuum the private data stored in each session
+			 * that belongs to global temporary table, so skip them.
+			 */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2182,7 +2190,7 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		if (RelpersistenceTsTemp(classForm->relpersistence))
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index f5459c68f89..39b82a0b389 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -38,6 +38,7 @@
 #include "access/xlogutils.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -2935,7 +2936,15 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
-	if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind))
+	/*
+	 * Returns 0 if this global temporary table is not initialized in this session.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+	else if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind))
 	{
 		/*
 		 * Not every table AM uses BLCKSZ wide fixed size blocks.
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 9f26e41c464..0a25b151e82 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -22,6 +22,7 @@
 #include "access/subtrans.h"
 #include "access/syncscan.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -143,6 +144,7 @@ CalculateShmemSize(int *num_semaphores)
 	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
@@ -246,6 +248,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* For global temporary table shared hashtable */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 13d192ec2b4..c5fd02c9c7f 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -65,6 +65,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -5175,3 +5176,66 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * Search all active session to get db level oldest frozenxid
+ * for global temporary table.
+ *
+ * Pids and Xids are used to store the session level oldest frozenxid if specified
+ */
+TransactionId
+gtt_get_oldest_frozenxids_in_current_database(List **pids, List **xids)
+{
+	ProcArrayStruct		*arrayP = NULL;
+	TransactionId		result = InvalidTransactionId;
+	int			index = 0;
+	int			i = 0;
+	uint8		flags = 0;
+
+	/* return 0 if feature is disabled */
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	/* Disable in standby node */
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	arrayP = procArray;
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		PGPROC	   *proc = &allProcs[pgprocno];
+		uint8		statusFlags = ProcGlobal->statusFlags[index];
+		TransactionId	gtt_frozenxid = InvalidTransactionId;
+
+		if (statusFlags & flags)
+			continue;
+
+		/* Fetch all session level frozenxid that is belonging to current database */
+		gtt_frozenxid = proc->gtt_frozenxid;
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = gtt_frozenxid;
+			else if (TransactionIdPrecedes(gtt_frozenxid, result))
+				result = gtt_frozenxid;
+
+			/* save backend pid and session level oldest relfrozenxid */
+			if (pids)
+				*pids = lappend_int(*pids, proc->pid);
+
+			if (xids)
+				*xids = lappend_oid(*xids, gtt_frozenxid);
+
+			i++;
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	return result;
+}
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 7b0dea4abec..ab0d2031e86 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -176,7 +176,9 @@ static const char *const BuiltinTrancheNames[] = {
 	/* LWTRANCHE_PARALLEL_APPEND: */
 	"ParallelAppend",
 	/* LWTRANCHE_PER_XACT_PREDICATE_LIST: */
-	"PerXactPredicateList"
+	"PerXactPredicateList",
+	/* LWTRANCHE_GLOBAL_TEMP_TABLE_CTL */
+	"GlobalTempTableControl"
 };
 
 StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 37f032e7b95..d1ec2d9bcd5 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -392,6 +392,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 3a2f2e1f99d..9a6e7cee244 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -982,6 +982,13 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/*
+			 * For global temporary table ,each backend has its own storage,
+			 * also only sees its own storage. Use Backendid to identify them.
+			 */
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 1fbb0b28c3b..637ff7f7e43 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -108,6 +108,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -5117,12 +5118,26 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								/* For global temporary table, get statistic data from localhash */
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -5365,15 +5380,28 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6817,6 +6845,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;
@@ -6834,6 +6863,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6845,6 +6882,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;
@@ -6860,6 +6899,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7778,6 +7825,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))
@@ -7790,6 +7839,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7802,6 +7860,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.
@@ -7821,6 +7881,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index feef9998634..ebaad33a698 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -3113,6 +3114,19 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		/* For global temporary table, get statistic data from localhash */
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 2707fed12f4..b26b6a0adbf 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -65,6 +65,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
@@ -1152,6 +1153,36 @@ retry:
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+				TransactionId	relfrozenxid = InvalidTransactionId;
+				MultiXactId 	relminmxid = InvalidMultiXactId;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+
+				/*
+				 * For global temporary table
+				 * get session level relstats from localhash
+				 * and set it to local relcache
+				 */
+				get_gtt_relstats(RelationGetRelid(relation),
+								 &relpages, &reltuples, &relallvisible,
+								 &relfrozenxid, &relminmxid);
+
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+				if (TransactionIdIsNormal(relfrozenxid))
+					relation->rd_rel->relfrozenxid = relfrozenxid;
+
+				if (MultiXactIdIsValid(relminmxid))
+					relation->rd_rel->relminmxid = relminmxid;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1212,6 +1243,15 @@ retry:
 	else
 		Assert(relation->rd_rel->relam == InvalidOid);
 
+	/*
+	 * For one global temporary table,
+	 * other session may created one index, that triggers relcache reload for this session.
+	 * If table already has data at this session, to avoid rebuilding index,
+	 * accept the structure of the index but set the local state to indisvalid.
+	 */
+	if (relation->rd_rel->relkind == RELKIND_INDEX)
+		gtt_correct_index_session_state(relation);
+
 	/* extract reloptions if any */
 	RelationParseRelOptions(relation, pg_class_tuple);
 
@@ -1335,7 +1375,23 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		/*
+		 * For global temporary table,
+		 * the latest relfilenode is saved in localHash(see RelationSetNewRelfilenode()),
+		 * get it and put it to local relcache.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+			if (OidIsValid(newrelnode) &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2289,6 +2345,14 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		/*
+		 * For one global temporary table,
+		 * other session may created one index, that triggers relcache reload for this session.
+		 * If table already has data at this session, to avoid rebuilding index,
+		 * accept the structure of the index but set the local state to indisvalid.
+		 */
+		gtt_correct_index_session_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3569,6 +3633,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3682,6 +3750,13 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
 
+	/*
+	 * For global temporary table, storage information for each session is
+	 * maintained locally, not in pg_class.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+		return GlobalTempRelationSetNewRelfilenode(relation);
+
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
@@ -3725,7 +3800,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);
 	}
 	else
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index f505413a7f9..884a5c2c890 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -44,6 +44,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "commands/prepare.h"
 #include "commands/tablespace.h"
@@ -154,6 +155,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temporary table feature.
+ * table schema are still saved in catalog.
+ *
+ * num > 0 means allows the database to manage multiple active tables at the same time.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2141,6 +2154,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("Sets the amount of time to wait before forcing a "
@@ -2712,6 +2734,16 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"vacuum_gtt_defer_check_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("The defer check age of GTT, used to check expired data after vacuum."),
+			NULL
+		},
+		&vacuum_gtt_defer_check_age,
+		10000, 0, 1000000,
+		NULL, NULL, NULL
+	},
+
 	/*
 	 * See also CheckRequiredParameterValues() if this parameter changes
 	 */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 3b4b63d8971..ef1e78aa3c9 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2538,6 +2538,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
 		dopt->no_unlogged_table_data)
 		return;
 
+	/* Don't dump data in global temporary table/sequence */
+	if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		return;
+
 	/* Check that the data is not explicitly excluded */
 	if (simple_oid_list_member(&tabledata_exclude_oids,
 							   tbinfo->dobj.catId.oid))
@@ -14930,6 +14934,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
 		char	   *foreign = "";
+		char	   *table_type = NULL;
 
 		/*
 		 * Set reltypename, and collect any relkind-specific data that we
@@ -15005,9 +15010,15 @@ dumpTableSchema(Archive *fout, const 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);
 
@@ -15375,6 +15386,15 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 			}
 		}
 
+		/*
+		 * Transaction information for the global temporary table is not stored
+		 * in the pg_class.
+		 */
+		if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			Assert(tbinfo->frozenxid == 0);
+			Assert(tbinfo->minmxid == 0);
+		}
 		/*
 		 * In binary_upgrade mode, arrange to restore the old relfrozenxid and
 		 * relminmxid of all vacuumable relations.  (While vacuum.c processes
@@ -15382,7 +15402,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		 * of other relations; so this "if" lacks RELKIND_TOASTVALUE, and the
 		 * child toast table is handled below.)
 		 */
-		if (dopt->binary_upgrade &&
+		else if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
 			 tbinfo->relkind == RELKIND_MATVIEW))
 		{
@@ -16405,6 +16425,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	PQExpBuffer query = createPQExpBuffer();
 	PQExpBuffer delqry = createPQExpBuffer();
 	char	   *qseqname;
+	bool	   global_temp_seq = false;
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
@@ -16414,9 +16435,12 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
-						  "FROM pg_catalog.pg_sequence "
-						  "WHERE seqrelid = '%u'::oid",
+						  "seqcache, seqcycle, "
+						  "c.relpersistence "
+						  "FROM pg_catalog.pg_sequence s, "
+						  "pg_catalog.pg_class c "
+						  "WHERE seqrelid = '%u'::oid "
+						  "and s.seqrelid = c.oid",
 						  tbinfo->dobj.catId.oid);
 	}
 	else
@@ -16453,6 +16477,9 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
 
+	if (fout->remoteVersion >= 150000)
+		global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
 	if (strcmp(seqtype, "smallint") == 0)
@@ -16530,9 +16557,13 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	}
 	else
 	{
-		appendPQExpBuffer(query,
-						  "CREATE SEQUENCE %s\n",
-						  fmtQualifiedDumpable(tbinfo));
+		appendPQExpBuffer(query, "CREATE ");
+
+		if (global_temp_seq)
+			appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+		appendPQExpBuffer(query, "SEQUENCE %s\n",
+								  fmtQualifiedDumpable(tbinfo));
 
 		if (strcmp(seqtype, "bigint") != 0)
 			appendPQExpBuffer(query, "    AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 019bcb6c7b7..df30fa0dd7e 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -88,7 +88,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -174,7 +174,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 69ef23119f5..14e0fc5ef75 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_global_temp);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -269,9 +269,11 @@ report_unmatched_relation(const RelInfo *rel, const DbInfo *db, bool is_new_db)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_global_temp)
 {
 	int			dbnum;
 
@@ -281,7 +283,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_global_temp);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -365,7 +367,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_global_temp)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -408,8 +410,17 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
 			 "         ON c.relnamespace = n.oid "
 			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
+			 CppAsString2(RELKIND_MATVIEW) ") AND ");
+
+	if (skip_global_temp)
+	{
+		/* exclude global temp tables */
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+			"    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND ");
+	}
+
 	/* exclude possible orphaned temp tables */
+	snprintf(query + strlen(query), sizeof(query) - strlen(query),
 			 "    ((n.nspname !~ '^pg_temp_' AND "
 			 "      n.nspname !~ '^pg_toast_temp_' AND "
 			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 77beb116398..2cdb7b138d4 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -476,7 +476,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -714,7 +714,10 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+			/* exclude global temp tables */
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -725,7 +728,10 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+		/* exclude global temp tables */
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 0aca0a77aae..b99df4f6a42 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -383,7 +383,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_global_temp);
 
 /* option.c */
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 654ef2d7c30..3dd8cd21317 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3780,7 +3780,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		 * Show whether a relation is permanent, temporary, or unlogged.
 		 */
 		appendPQExpBuffer(&buf,
-						  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+						  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+						  gettext_noop("session"),
 						  gettext_noop("permanent"),
 						  gettext_noop("temporary"),
 						  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 98882272130..adc6dd20b72 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1181,6 +1181,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2761,6 +2763,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE FOREIGN TABLE */
 	else if (Matches("CREATE", "FOREIGN", "TABLE", MatchAny))
@@ -2994,6 +2999,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE", "SEQUENCE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index c4757bda2d5..d99454c984f 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -86,9 +86,9 @@ extern Oid	heap_create_with_catalog(const char *relname,
 
 extern void heap_drop_with_catalog(Oid relid);
 
-extern void heap_truncate(List *relids);
+extern void heap_truncate(List *relids, bool is_global_temp);
 
-extern void heap_truncate_one_rel(Relation rel);
+extern void heap_truncate_one_rel(Relation rel, LOCKMODE lockmode);
 
 extern void heap_truncate_check_FKs(List *relations, bool tempTables);
 
@@ -161,5 +161,5 @@ extern void StorePartitionKey(Relation rel,
 extern void RemovePartitionKeyByRelId(Oid relid);
 extern void StorePartitionBound(Relation rel, Relation parent,
 								PartitionBoundSpec *bound);
-
+extern void CheckGlobalTempTableNotInUse(Relation rel, const char *stmt);
 #endif							/* HEAP_H */
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index a1d6e3b645f..441cd440493 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -158,7 +158,7 @@ extern void reindex_index(Oid indexId, bool skip_constraint_checks,
 #define REINDEX_REL_FORCE_INDEXES_UNLOGGED	0x08
 #define REINDEX_REL_FORCE_INDEXES_PERMANENT 0x10
 
-extern bool reindex_relation(Oid relid, int flags, ReindexParams *params);
+extern bool reindex_relation(Oid relid, int flags, ReindexParams *params, LOCKMODE lockmode);
 
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 304e8c18d52..e23cb49c010 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -172,6 +172,7 @@ DECLARE_INDEX(pg_class_tblspc_relfilenode_index, 3455, ClassTblspcRelfilenodeInd
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 62f36daa981..66d12b7b65f 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5735,6 +5735,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '9874',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '9875',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '9876',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '9877',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 9ffc7419131..ca5bcaa3f97 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,7 +22,7 @@
 /* GUC variables */
 extern int	wal_skip_threshold;
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationPreTruncate(Relation rel);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 00000000000..1b6d4ef2a8d
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,46 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern int		vacuum_gtt_defer_check_age;
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void gtt_update_relstats(Relation relation, BlockNumber relpages, double reltuples,
+									BlockNumber relallvisible, TransactionId relfrozenxid,
+									TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void gtt_index_force_enable(Relation index);
+extern void gtt_correct_index_session_state(Relation index);
+extern void gtt_init_storage(CmdType operation, Relation relation);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+extern void index_update_gtt_relstats(Relation rel, bool hasindex, double reltuples, bool isreindex);
+extern void vac_update_gtt_relstats(Relation relation, BlockNumber num_pages, double num_tuples,
+										BlockNumber num_all_visible_pages, bool hasindex, TransactionId frozenxid,
+										MultiXactId minmulti, bool in_outer_xact);
+extern void GlobalTempRelationSetNewRelfilenode(Relation relation);
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index 9fecc41954e..c516851d09c 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -66,5 +66,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_sequence(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 5d4037f26e8..28598b06c1e 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -86,7 +86,7 @@ extern void find_composite_type_dependencies(Oid typeOid,
 
 extern void check_of_type(HeapTuple typetuple);
 
-extern void register_on_commit_action(Oid relid, OnCommitAction action);
+extern void register_on_commit_action(Oid relid, OnCommitAction action, bool is_gloal_temp);
 extern void remove_on_commit_action(Oid relid);
 
 extern void PreCommit_on_commit_actions(void);
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 344399f6a8a..ae7628d692b 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -575,7 +575,7 @@ exec_rt_fetch(Index rti, EState *estate)
 
 extern Relation ExecGetRangeTableRelation(EState *estate, Index rti);
 extern void ExecInitResultRelation(EState *estate, ResultRelInfo *resultRelInfo,
-								   Index rti);
+								   Index rti, CmdType operation);
 
 extern int	executor_errposition(EState *estate, int location);
 
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 06dc27995ba..197e367f4e4 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -119,5 +119,6 @@ extern const NameData *attnumAttName(Relation rd, int attid);
 extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
+extern bool isQueryUsingGlobalTempRelation(Query *query);
 
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 124977cf7e3..9b64389b08a 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -189,6 +189,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_SHARED_TIDBITMAP,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_PER_XACT_PREDICATE_LIST,
+	LWTRANCHE_GLOBAL_TEMP_TABLE_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index a58888f9e90..1bbf31923c0 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -164,6 +164,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId 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 e03692053ee..d948c0383b7 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -94,4 +94,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern TransactionId gtt_get_oldest_frozenxids_in_current_database(List **pids, List **xids);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 6bb81707b09..4927edc2dec 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -288,6 +288,9 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 6da1b220cdc..6455dfb7ae2 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	SMgrRelation rd_smgr;		/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -327,6 +327,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -609,11 +610,13 @@ RelationGetSmgr(Relation rel)
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -621,6 +624,7 @@ RelationGetSmgr(Relation rel)
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -633,6 +637,30 @@ RelationGetSmgr(Relation rel)
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP_ON_CURRENT_SESSION
+ *		Test a rel is either local temp relation of this session
+ *		or global temp relation.
+ */
+#define RELATION_IS_TEMP_ON_CURRENT_SESSION(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is local temporary relation or global temporary relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RelpersistenceTsTemp
+ *		Test a relpersistence is local temp relation or global temporary relation.
+ */
+#define RelpersistenceTsTemp(relpersistence) \
+	(relpersistence == RELPERSISTENCE_TEMP || \
+	 relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -678,6 +706,19 @@ RelationGetSmgr(Relation rel)
 	 (relation)->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&	\
 	 !IsCatalogRelation(relation))
 
+/* For global temporary table */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/* Get on commit clause value only for global temporary table */
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+/* Get relpersistence for relation */
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
-- 
2.32.0 (Apple Git-132)

0004-gtt-v66-regress.patchapplication/octet-stream; name=0004-gtt-v66-regress.patchDownload
diff --git a/src/test/isolation/expected/gtt-sequence.out b/src/test/isolation/expected/gtt-sequence.out
new file mode 100644
index 00000000000..31db2ebd423
--- /dev/null
+++ b/src/test/isolation/expected/gtt-sequence.out
@@ -0,0 +1,48 @@
+unused step name: s1_seq_restart
+Parsed test spec with 2 sessions
+
+starting permutation: s1_seq_next s2_seq_next s1_seq_next
+step s1_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      1
+(1 row)
+
+step s2_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      1
+(1 row)
+
+step s1_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      2
+(1 row)
+
+
+starting permutation: s1_select s2_select s1_insert s2_insert s1_select s2_select
+step s1_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+(0 rows)
+
+step s2_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+(0 rows)
+
+step s1_insert: insert into gtt_with_seq values(1);
+step s2_insert: insert into gtt_with_seq values(10);
+step s1_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+ 1| 3
+(1 row)
+
+step s2_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+10| 1
+(1 row)
+
diff --git a/src/test/isolation/expected/gtt-table.out b/src/test/isolation/expected/gtt-table.out
new file mode 100644
index 00000000000..5825773aa12
--- /dev/null
+++ b/src/test/isolation/expected/gtt-table.out
@@ -0,0 +1,675 @@
+Parsed test spec with 3 sessions
+
+starting permutation: s1_update_d
+step s1_update_d: update gtt_on_commit_delete_row set b = 'update'
+
+starting permutation: s1_select_d s1_insert_d s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_begin s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+1|test1
+(1 row)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_begin s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+1|test1
+(1 row)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_select_d s1_truncate_d s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_begin s1_insert_d s1_select_d s1_truncate_d s1_select_d s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_begin s1_insert_d s1_select_d s1_truncate_d s1_select_d s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_insert_d s1_select_d s1_begin s1_insert_d s1_select_d s1_save_1 s1_truncate_d s1_insert_d s1_select_d s1_save_2 s1_truncate_d s1_insert_d s1_select_d s1_save_3 s1_rollback_to_save_2 s1_select_d s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_save_1: SAVEPOINT save1
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_save_2: SAVEPOINT save2
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+4|test1
+(1 row)
+
+step s1_save_3: SAVEPOINT save3
+step s1_rollback_to_save_2: rollback to savepoint save2
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+5|test1
+(2 rows)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_insert_d s1_select_d s1_begin s1_insert_d s1_select_d s1_save_1 s1_truncate_d s1_insert_d s1_select_d s1_save_2 s1_truncate_d s1_insert_d s1_select_d s1_save_3 s1_rollback_to_save_2 s1_select_d s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_save_1: SAVEPOINT save1
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_save_2: SAVEPOINT save2
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+4|test1
+(1 row)
+
+step s1_save_3: SAVEPOINT save3
+step s1_rollback_to_save_2: rollback to savepoint save2
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+5|test1
+(2 rows)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_update_p
+step s2_update_p: update gtt_on_commit_preserve_row set b = 'update'
+
+starting permutation: s2_select_p s2_insert_p s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_begin s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_begin s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_select_p s2_insert_p s2_select_p s2_truncate_p s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_select_p s2_insert_p s2_begin s2_insert_p s2_select_p s2_truncate_p s2_select_p s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_insert_p s2_begin s2_insert_p s2_select_p s2_truncate_p s2_select_p s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_insert_p s2_select_p s2_begin s2_insert_p s2_select_p s2_save_1 s2_truncate_p s2_insert_p s2_select_p s2_save_2 s2_truncate_p s2_insert_p s2_select_p s2_save_3 s2_rollback_to_save_2 s2_select_p s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_save_1: SAVEPOINT save1
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_save_2: SAVEPOINT save2
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+4|test10
+(1 row)
+
+step s2_save_3: SAVEPOINT save3
+step s2_rollback_to_save_2: rollback to savepoint save2
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+
+starting permutation: s2_insert_p s2_select_p s2_begin s2_insert_p s2_select_p s2_save_1 s2_truncate_p s2_insert_p s2_select_p s2_save_2 s2_truncate_p s2_insert_p s2_select_p s2_save_3 s2_rollback_to_save_2 s2_select_p s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_save_1: SAVEPOINT save1
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_save_2: SAVEPOINT save2
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+4|test10
+(1 row)
+
+step s2_save_3: SAVEPOINT save3
+step s2_rollback_to_save_2: rollback to savepoint save2
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_insert_p s2_insert_p s1_select_p s2_select_p
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_truncate_p s2_truncate_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_begin s1_insert_d s2_insert_d s1_truncate_d s2_insert_d s1_commit
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s2_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s2_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_commit: commit
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_reindex_p s2_reindex_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_reindex_p: reindex table gtt_on_commit_preserve_row
+step s2_reindex_p: reindex table gtt_on_commit_preserve_row
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_reindex_i_p s2_reindex_i_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_reindex_i_p: reindex index gtt_on_commit_preserve_row_pkey
+step s2_reindex_i_p: reindex index gtt_on_commit_preserve_row_pkey
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_insert_c s3_create_c s3_insert_c s1_insert_c s1_analyze_c s2_analyze_c s3_analyze_c s1_select_c s2_select_c s3_select_c
+step s2_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s3_create_c: create unique index idx_temp_table_a on gtt_test_createindex(a)
+step s3_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s1_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s1_analyze_c: analyze gtt_test_createindex
+step s2_analyze_c: analyze gtt_test_createindex
+step s3_analyze_c: analyze gtt_test_createindex
+step s1_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                                               
+---------------------------------------------------------
+Index Scan using idx_temp_table_a on gtt_test_createindex
+  Index Cond: (a = 1)                                    
+(2 rows)
+
+step s2_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                      
+--------------------------------
+Seq Scan on gtt_test_createindex
+  Filter: (a = 1)               
+(2 rows)
+
+step s3_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                                               
+---------------------------------------------------------
+Index Scan using idx_temp_table_a on gtt_test_createindex
+  Index Cond: (a = 1)                                    
+(2 rows)
+
+
+starting permutation: s1_begin s2_begin s1_lock_p s2_lock_p s1_truncate_p s2_truncate_p s1_insert_p s2_insert_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_lock_p: LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE
+step s2_lock_p: LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE
+step s1_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index 99c23b16ffe..f5c8eec533d 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -97,3 +97,5 @@ test: plpgsql-toast
 test: truncate-conflict
 test: serializable-parallel
 test: serializable-parallel-2
+test: gtt-sequence
+test: gtt-table
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 12179f25146..7cf0b20e636 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -80,9 +80,30 @@ disconnect_atexit(void)
 {
 	int			i;
 
-	for (i = 0; i < nconns; i++)
+	for (i = 1; i < nconns; i++)
 		if (conns[i].conn)
 			PQfinish(conns[i].conn);
+
+	if (parseresult.destroy)
+	{
+		PGresult   *res;
+
+		res = PQexec(conns[0].conn, parseresult.destroy);
+		if (PQresultStatus(res) == PGRES_TUPLES_OK)
+		{
+			printResultSet(res);
+		}
+		else if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		{
+			fprintf(stderr, "destroy failed: %s",
+					PQerrorMessage(conns[0].conn));
+			/* don't exit on teardown failure */
+		}
+		PQclear(res);
+	}
+
+	if (conns[0].conn)
+		PQfinish(conns[0].conn);
 }
 
 int
@@ -238,6 +259,24 @@ main(int argc, char **argv)
 	PQclear(res);
 	termPQExpBuffer(&wait_query);
 
+	if (parseresult.initialize)
+	{
+		PGresult   *res;
+
+		res = PQexec(conns[0].conn, parseresult.initialize);
+		if (PQresultStatus(res) == PGRES_TUPLES_OK)
+		{
+			printResultSet(res);
+		}
+		else if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		{
+			fprintf(stderr, "initialize failed: %s",
+					PQerrorMessage(conns[0].conn));
+			/* don't exit on teardown failure */
+		}
+		PQclear(res);
+	}
+
 	/*
 	 * Run the permutations specified in the spec, or all if none were
 	 * explicitly specified.
diff --git a/src/test/isolation/isolationtester.h b/src/test/isolation/isolationtester.h
index e00bc6b816b..e15d3c6d3ea 100644
--- a/src/test/isolation/isolationtester.h
+++ b/src/test/isolation/isolationtester.h
@@ -81,6 +81,8 @@ typedef struct
 	int			nsessions;
 	Permutation **permutations;
 	int			npermutations;
+	char	   *initialize;
+	char	   *destroy;
 } TestSpec;
 
 extern TestSpec parseresult;
diff --git a/src/test/isolation/specparse.y b/src/test/isolation/specparse.y
index eb368184b8b..fb0107eccf2 100644
--- a/src/test/isolation/specparse.y
+++ b/src/test/isolation/specparse.y
@@ -39,7 +39,7 @@ TestSpec		parseresult;			/* result of parsing is left here */
 }
 
 %type <ptr_list> setup_list
-%type <str>  opt_setup opt_teardown
+%type <str>  opt_setup opt_teardown opt_initialize opt_destroy
 %type <str> setup
 %type <ptr_list> step_list session_list permutation_list opt_permutation_list
 %type <ptr_list> permutation_step_list blocker_list
@@ -51,23 +51,27 @@ TestSpec		parseresult;			/* result of parsing is left here */
 
 %token <str> sqlblock identifier
 %token <integer> INTEGER
-%token NOTICES PERMUTATION SESSION SETUP STEP TEARDOWN TEST
+%token NOTICES PERMUTATION SESSION SETUP STEP TEARDOWN TEST INITIALIZE DESTROY
 
 %%
 
 TestSpec:
+			opt_initialize
 			setup_list
 			opt_teardown
+			opt_destroy
 			session_list
 			opt_permutation_list
 			{
-				parseresult.setupsqls = (char **) $1.elements;
-				parseresult.nsetupsqls = $1.nelements;
-				parseresult.teardownsql = $2;
-				parseresult.sessions = (Session **) $3.elements;
-				parseresult.nsessions = $3.nelements;
-				parseresult.permutations = (Permutation **) $4.elements;
-				parseresult.npermutations = $4.nelements;
+				parseresult.setupsqls = (char **) $2.elements;
+				parseresult.nsetupsqls = $2.nelements;
+				parseresult.teardownsql = $3;
+				parseresult.sessions = (Session **) $5.elements;
+				parseresult.nsessions = $5.nelements;
+				parseresult.permutations = (Permutation **) $6.elements;
+				parseresult.npermutations = $6.nelements;
+				parseresult.initialize = $1;
+				parseresult.destroy = $4;
 			}
 		;
 
@@ -100,6 +104,16 @@ opt_teardown:
 			| TEARDOWN sqlblock	{ $$ = $2; }
 		;
 
+opt_initialize:
+			/* EMPTY */			{ $$ = NULL; }
+			| INITIALIZE sqlblock	{ $$ = $2; }
+		;
+
+opt_destroy:
+			/* EMPTY */			{ $$ = NULL; }
+			| DESTROY sqlblock	{ $$ = $2; }
+		;
+
 session_list:
 			session_list session
 			{
diff --git a/src/test/isolation/specs/gtt-sequence.spec b/src/test/isolation/specs/gtt-sequence.spec
new file mode 100644
index 00000000000..88eece45e29
--- /dev/null
+++ b/src/test/isolation/specs/gtt-sequence.spec
@@ -0,0 +1,39 @@
+# Tests for global temporary relations
+
+initialize
+{
+  CREATE GLOBAL TEMPORARY TABLE if not exists gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+}
+
+destroy
+{
+  /* wait other backend exit */
+  select pg_sleep(1);
+
+  DROP TABLE gtt_with_seq;
+}
+
+# Session 1
+session "s1"
+step "s1_seq_next" { select nextval('gtt_with_seq_c2_seq'); }
+step "s1_seq_restart" { alter sequence gtt_with_seq_c2_seq RESTART; }
+step "s1_insert" { insert into gtt_with_seq values(1); }
+step "s1_select" { select * from gtt_with_seq order by c1,c2; }
+teardown
+{
+  TRUNCATE gtt_with_seq;
+}
+
+# Session 2
+session "s2"
+step "s2_seq_next" { select nextval('gtt_with_seq_c2_seq'); }
+step "s2_insert" { insert into gtt_with_seq values(10); }
+step "s2_select" { select * from gtt_with_seq order by c1,c2; }
+teardown
+{
+  TRUNCATE gtt_with_seq RESTART IDENTITY;
+}
+
+permutation "s1_seq_next" "s2_seq_next" "s1_seq_next"
+permutation "s1_select" "s2_select" "s1_insert" "s2_insert" "s1_select" "s2_select"
+
diff --git a/src/test/isolation/specs/gtt-table.spec b/src/test/isolation/specs/gtt-table.spec
new file mode 100644
index 00000000000..e0396b21ef0
--- /dev/null
+++ b/src/test/isolation/specs/gtt-table.spec
@@ -0,0 +1,135 @@
+# Tests for global temporary relations
+
+initialize
+{
+  create global temp table gtt_on_commit_delete_row(a bigserial primary key, b text) on commit delete rows;
+  create global temp table gtt_on_commit_preserve_row(a bigserial primary key, b text) on commit preserve rows;
+  create global temp table gtt_test_createindex(a int, b char(1000)) on commit preserve rows;
+}
+
+destroy
+{
+  /* wait other backend exit */
+  select pg_sleep(1);
+
+  DROP TABLE gtt_on_commit_delete_row;
+  DROP TABLE gtt_on_commit_preserve_row;
+  DROP TABLE gtt_test_createindex;
+}
+
+# Session 1
+session "s1"
+step "s1_begin" {begin}
+step "s1_commit" {commit}
+step "s1_rollback" {rollback}
+step "s1_insert_d" {insert into gtt_on_commit_delete_row (b) values('test1')}
+step "s1_select_d" {select a,b from gtt_on_commit_delete_row order by a,b}
+step "s1_insert_p" {insert into gtt_on_commit_preserve_row (b) values('test20')}
+step "s1_select_p" {select a,b from gtt_on_commit_preserve_row order by a,b}
+step "s1_truncate_d" {truncate gtt_on_commit_delete_row}
+step "s1_truncate_p" {truncate gtt_on_commit_preserve_row}
+step "s1_lock_p" {LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE}
+step "s1_update_d" {update gtt_on_commit_delete_row set b = 'update'}
+step "s1_save_1" {SAVEPOINT save1}
+step "s1_save_2" {SAVEPOINT save2}
+step "s1_save_3" {SAVEPOINT save3}
+step "s1_rollback_to_save_2" {rollback to savepoint save2}
+step "s1_reindex_p" {reindex table gtt_on_commit_preserve_row}
+step "s1_reindex_i_p" {reindex index gtt_on_commit_preserve_row_pkey}
+step "s1_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s1_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s1_analyze_c" {analyze gtt_test_createindex}
+teardown
+{
+  TRUNCATE gtt_on_commit_delete_row RESTART IDENTITY;
+  TRUNCATE gtt_on_commit_preserve_row RESTART IDENTITY;
+}
+
+# Session 2
+session "s2"
+step "s2_begin" {begin}
+step "s2_commit" {commit}
+step "s2_rollback" {rollback}
+step "s2_insert_p" {insert into gtt_on_commit_preserve_row (b) values('test10')}
+step "s2_select_p" {select a,b from gtt_on_commit_preserve_row order by a,b}
+step "s2_insert_d" {insert into gtt_on_commit_delete_row (b) values('test1')}
+step "s2_truncate_p" {truncate gtt_on_commit_preserve_row}
+step "s2_update_p" {update gtt_on_commit_preserve_row set b = 'update'}
+step "s2_lock_p" {LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE}
+step "s2_save_1" {SAVEPOINT save1}
+step "s2_save_2" {SAVEPOINT save2}
+step "s2_save_3" {SAVEPOINT save3}
+step "s2_rollback_to_save_2" {rollback to savepoint save2}
+step "s2_reindex_p" {reindex table gtt_on_commit_preserve_row}
+step "s2_reindex_i_p" {reindex index gtt_on_commit_preserve_row_pkey}
+step "s2_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s2_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s2_analyze_c" {analyze gtt_test_createindex}
+teardown
+{
+  TRUNCATE gtt_on_commit_delete_row RESTART IDENTITY;
+  TRUNCATE gtt_on_commit_preserve_row RESTART IDENTITY;
+}
+
+session "s3"
+step "s3_create_c" {create unique index idx_temp_table_a on gtt_test_createindex(a)}
+step "s3_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s3_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s3_analyze_c" {analyze gtt_test_createindex}
+
+
+#
+# test on commit delete temp table
+#
+
+# test update empty temp table
+permutation "s1_update_d"
+# test insert into temp table
+permutation "s1_select_d" "s1_insert_d" "s1_select_d"
+# test temp table in transaction(commit)
+permutation "s1_select_d" "s1_begin"    "s1_insert_d" "s1_select_d"   "s1_commit"   "s1_select_d" 
+# test temp table in transaction(rollback)
+permutation "s1_select_d" "s1_begin"    "s1_insert_d" "s1_select_d"   "s1_rollback" "s1_select_d" 
+# test truncate
+permutation "s1_select_d" "s1_insert_d" "s1_select_d" "s1_truncate_d" "s1_select_d"
+# test truncate in transaction block
+permutation "s1_select_d" "s1_insert_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_truncate_d" "s1_select_d"   "s1_insert_d" "s1_select_d" "s1_commit"   "s1_select_d" 
+permutation "s1_select_d" "s1_insert_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_truncate_d" "s1_select_d"   "s1_insert_d" "s1_select_d" "s1_rollback" "s1_select_d"
+# test temp table with subtransaction or savepoint
+permutation "s1_insert_d" "s1_select_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_save_1"     "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_2"   "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_3" "s1_rollback_to_save_2" "s1_select_d" "s1_insert_d" "s1_select_d" "s1_commit" "s1_select_d"
+permutation "s1_insert_d" "s1_select_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_save_1"     "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_2"   "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_3" "s1_rollback_to_save_2" "s1_select_d" "s1_insert_d" "s1_select_d" "s1_rollback" "s1_select_d"
+
+#
+# test on commit preserve table
+#
+
+# same as test on commit delete temp table
+permutation "s2_update_p"
+permutation "s2_select_p" "s2_insert_p" "s2_select_p"
+permutation "s2_select_p" "s2_begin"    "s2_insert_p" "s2_select_p"   "s2_commit"   "s2_select_p"
+permutation "s2_select_p" "s2_begin"    "s2_insert_p" "s2_select_p"   "s2_rollback" "s2_select_p"
+permutation "s2_select_p" "s2_insert_p" "s2_select_p" "s2_truncate_p" "s2_select_p"
+permutation "s2_select_p" "s2_insert_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_truncate_p" "s2_select_p"   "s2_insert_p" "s2_select_p" "s2_commit"   "s2_select_p" 
+permutation "s2_select_p" "s2_insert_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_truncate_p" "s2_select_p"   "s2_insert_p" "s2_select_p" "s2_rollback" "s2_select_p" 
+permutation "s2_insert_p" "s2_select_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_save_1"     "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_2"   "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_3" "s2_rollback_to_save_2" "s2_select_p" "s2_insert_p" "s2_select_p" "s2_commit" "s2_select_p"
+permutation "s2_insert_p" "s2_select_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_save_1"     "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_2"   "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_3" "s2_rollback_to_save_2" "s2_select_p" "s2_insert_p" "s2_select_p" "s2_rollback" "s2_select_p"
+
+#
+# test concurrent operation on temp table
+#
+
+#  test concurrent read
+permutation "s1_insert_p" "s2_insert_p" "s1_select_p" "s2_select_p" 
+#  test concurrent truncate
+permutation "s1_begin" "s2_begin"    "s1_insert_p" "s2_insert_p"   "s1_truncate_p" "s2_truncate_p"  "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+permutation "s1_begin" "s1_insert_d" "s2_insert_d" "s1_truncate_d" "s2_insert_d"   "s1_commit" 
+#  test concurrent reindex table
+permutation "s1_begin" "s2_begin" "s1_insert_p" "s2_insert_p" "s1_reindex_p"   "s2_reindex_p"   "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+#  test concurrent reindex index
+permutation "s1_begin" "s2_begin" "s1_insert_p" "s2_insert_p" "s1_reindex_i_p" "s2_reindex_i_p" "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+
+# test create index
+permutation "s2_insert_c" "s3_create_c" "s3_insert_c" "s1_insert_c" "s1_analyze_c" "s2_analyze_c" "s3_analyze_c" "s1_select_c" "s2_select_c" "s3_select_c"
+
+# test lock gtt
+permutation "s1_begin" "s2_begin" "s1_lock_p" "s2_lock_p" "s1_truncate_p" "s2_truncate_p" "s1_insert_p" "s2_insert_p" "s1_commit" "s2_commit" "s1_select_p" "s2_select_p"
diff --git a/src/test/isolation/specscanner.l b/src/test/isolation/specscanner.l
index aa6e89268ef..f782b9b8fcc 100644
--- a/src/test/isolation/specscanner.l
+++ b/src/test/isolation/specscanner.l
@@ -67,6 +67,8 @@ session			{ return SESSION; }
 setup			{ return SETUP; }
 step			{ return STEP; }
 teardown		{ return TEARDOWN; }
+initialize		 { return INITIALIZE; }
+destroy			 { return DESTROY; }
 
  /* Whitespace and comments */
 [\n]			{ yyline++; }
diff --git a/src/test/regress/expected/global_temporary_table.out b/src/test/regress/expected/global_temporary_table.out
new file mode 100644
index 00000000000..5b8203f28f5
--- /dev/null
+++ b/src/test/regress/expected/global_temporary_table.out
@@ -0,0 +1,577 @@
+--
+-- GLobal emparary table test case 
+--
+CREATE SCHEMA IF NOT EXISTS global_temporary_table;
+set search_path=global_temporary_table,sys;
+--
+--  test create global temp table basic syntax
+--
+create global temp table gtt_on_commit_default(a int primary key, b text);
+create global temp table gtt_on_commit_delete(a int primary key, b text) on commit delete rows;
+create global temp table gtt_on_commit_delete2(n int) with (on_commit_delete_rows='true');
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit PRESERVE rows;
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt_test_createindex(c0 tsvector,c1 varchar(100), c2 int);
+create global temp table gtt_test_alter(b text) with(on_commit_delete_rows=true);
+--
+-- test DML on global temp table
+--
+-- update empty temp table
+update gtt_on_commit_delete set b ='test';
+begin;
+insert into gtt_on_commit_delete values (1);
+update gtt_on_commit_delete set b ='test';
+-- should 1 row
+select * from gtt_on_commit_delete;
+ a |  b   
+---+------
+ 1 | test
+(1 row)
+
+commit;
+-- data delete after transaction commit
+-- should 0 row
+select * from gtt_on_commit_delete;
+ a | b 
+---+---
+(0 rows)
+
+-- update empty temp table
+update gtt_on_commit_preserve set b ='test';
+insert into gtt_on_commit_preserve values (2);
+begin;
+insert into gtt_on_commit_preserve values (3);
+update gtt_on_commit_preserve set b ='test';
+-- should 2 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 2 | test
+ 3 | test
+(2 rows)
+
+delete from gtt_on_commit_preserve where a=2;
+commit;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 3 | test
+(1 row)
+
+begin;
+insert into gtt_on_commit_preserve values (4);
+-- temp table support truncate;
+truncate gtt_on_commit_preserve;
+select * from gtt_on_commit_preserve order by a;
+ a | b 
+---+---
+(0 rows)
+
+rollback;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 3 | test
+(1 row)
+
+--
+-- test unsupported global temp partition table
+--
+-- should fail
+CREATE global temp TABLE global_temp_partition_01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+on commit delete rows;
+ERROR:  global temporary relation can only be a regular table or sequence
+CREATE TABLE regular_partition_01_2019 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+);
+CREATE TABLE regular_partition01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time);
+-- should fail
+CREATE global temp TABLE temp_partition01_2018
+PARTITION OF regular_partition01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+ERROR:  cannot create global temporary inherit table or global temporary partitioned table
+CREATE global temp TABLE global_temp_partition_01_2021 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+)on commit delete rows;
+-- should fail
+ALTER TABLE regular_partition01 ATTACH PARTITION global_temp_partition_01_2021 FOR VALUES FROM ('2021-01-01 00:00:00') TO ('2022-01-01 00:00:00');
+ERROR:  cannot attach a global temporary relation as partition of permanent relation "regular_partition01"
+--
+-- test unsupported inherit table
+--
+create table 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 inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent);
+ERROR:  cannot create global temporary inherit table or global temporary partitioned table
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent_global_temp) on commit delete rows;
+ERROR:  cannot create global temporary inherit table or global temporary partitioned table
+--
+-- test DDL on global temp table
+--
+create index idx_gtt_test_alter_b on gtt_test_alter (b);
+insert into gtt_test_alter values('test');
+alter table gtt_test_alter alter b type varchar;
+create index gtt_idx_1 on gtt_test_createindex using gin (c0);
+create index gtt_idx_2 on gtt_test_createindex using gist (c0);
+create index gtt_idx_3 on gtt_test_createindex using hash (c2);
+alter table gtt_test_rename rename to gtt_test_new;
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+create index CONCURRENTLY idx_b on gtt_on_commit_default (b);
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+insert into gtt_on_commit_default values(1,'test');
+create global temp table gtt_test_alter1 (a int primary key,b text);
+alter table gtt_test_alter1 alter a type varchar;
+-- should fail
+alter table gtt_on_commit_default alter a type varchar;
+ERROR:  cannot rewrite global temporary table "gtt_on_commit_default" when it has data in this session
+HINT:  Please create a new connection and execute ALTER TABLE on the new connection.
+-- should fail
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+ERROR:  cannot cluster global temporary table
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- should fail
+REINDEX (TABLESPACE pg_default) TABLE gtt_test_createindex;
+ERROR:  cannot change tablespace of global temporary table
+-- should fail
+REINDEX (TABLESPACE pg_default) INDEX gtt_idx_1;
+ERROR:  cannot change tablespace of global temporary table
+-- should fail
+alter table gtt_on_commit_default set ( on_commit_delete_rows='true');
+ERROR:  cannot set "on_commit_delete_rows" for relation "gtt_on_commit_default"
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  on_commit_delete_rows can only be used on global temporary table
+-- should fail
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temporary because they do not have storage
+create table foo();
+-- should fail
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  cannot set "on_commit_delete_rows" for relation "foo"
+-- should fail
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit drop;
+ERROR:  specifying ON COMMIT DROP is not supported on a global temporary table
+-- should fail
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  cannot specify both ON COMMIT clause and on_commit_delete_rows
+-- should fail
+CREATE MATERIALIZED VIEW mv_gtt_on_commit_default as select * from gtt_on_commit_default;
+ERROR:  materialized views must not use temporary tables or views
+-- test create table as select
+CREATE GLOBAL TEMPORARY TABLE test_create_table_as AS SELECT 1 AS a;
+-- test copy stmt
+create global temp table gtt_copytest (
+        c1 int,
+        "col with , comma" text,
+        "col with "" quote"  int);
+copy gtt_copytest from stdin csv header;
+select count(*) from gtt_copytest;
+ count 
+-------
+     2
+(1 row)
+
+--
+-- test foreign key dependencies for global temp table
+--
+CREATE global temp TABLE temp_products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+CREATE global temp TABLE temp_orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+)on commit delete rows;
+-- should fail
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+CREATE global temp TABLE temp_orders_2 (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products(product_no),
+    quantity integer
+);
+--should fail
+insert into temp_orders values(1,1,1);
+ERROR:  insert or update on table "temp_orders" violates foreign key constraint "temp_orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "temp_products".
+insert into temp_products values(1,'test',1.0);
+begin;
+insert into temp_orders values(1,1,1);
+commit;
+-- should 1 row
+select count(*) from temp_products;
+ count 
+-------
+     1
+(1 row)
+
+-- should 0 row
+select count(*) from temp_orders;
+ count 
+-------
+     0
+(1 row)
+
+--
+-- test sequence on global temp table
+--
+create global temp table global_temp_with_serial (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+-- support insert data to temp table in read only transaction
+insert into global_temp_with_serial (b) values(1);
+select * from global_temp_with_serial;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_delete(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_preserve(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table temp_table_with_sequence_oncommit_delete add c2 int default nextval('seq_1');
+alter table temp_table_with_sequence_oncommit_preserve add c2 int default nextval('seq_1');
+begin;
+insert into temp_table_with_sequence_oncommit_delete (c1)values(1);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(2);
+insert into temp_table_with_sequence_oncommit_delete (c1)values(3);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(4);
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  3 |  3
+(2 rows)
+
+commit;
+-- should 0 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_preserve order by c1;
+ c1 | c2 
+----+----
+  2 |  2
+  4 |  4
+(2 rows)
+
+--
+-- test statistics on temp table
+--
+create global temp table temp_table_test_statistics(a int);
+insert into temp_table_test_statistics values(generate_series(1,100000));
+create index idx_test_1 on temp_table_test_statistics (a);
+create index idx_test_2 on temp_table_test_statistics((a*10));
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+                   QUERY PLAN                   
+------------------------------------------------
+ Bitmap Heap Scan on temp_table_test_statistics
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_test_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+                   QUERY PLAN                   
+------------------------------------------------
+ Bitmap Heap Scan on temp_table_test_statistics
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_test_2
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+-- test statistic for whole row
+explain (costs off) select count(*) from temp_table_test_statistics group by temp_table_test_statistics;
+                  QUERY PLAN                  
+----------------------------------------------
+ HashAggregate
+   Group Key: temp_table_test_statistics.*
+   ->  Seq Scan on temp_table_test_statistics
+(3 rows)
+
+-- test statistic for system column
+explain (costs off) select count(*) from temp_table_test_statistics group by tableoid;
+                  QUERY PLAN                  
+----------------------------------------------
+ HashAggregate
+   Group Key: tableoid
+   ->  Seq Scan on temp_table_test_statistics
+(3 rows)
+
+analyze temp_table_test_statistics;
+-- indexscan by idx_test_1
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Index Only Scan using idx_test_1 on temp_table_test_statistics
+   Index Cond: (a = 200000)
+(2 rows)
+
+-- indexscan by idx_test_2
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+                        QUERY PLAN                         
+-----------------------------------------------------------
+ Index Scan using idx_test_2 on temp_table_test_statistics
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+explain (costs off) select count(*) from temp_table_test_statistics group by temp_table_test_statistics;
+                  QUERY PLAN                  
+----------------------------------------------
+ HashAggregate
+   Group Key: temp_table_test_statistics.*
+   ->  Seq Scan on temp_table_test_statistics
+(3 rows)
+
+explain (costs off) select count(*) from temp_table_test_statistics group by tableoid;
+                  QUERY PLAN                  
+----------------------------------------------
+ HashAggregate
+   Group Key: tableoid
+   ->  Seq Scan on temp_table_test_statistics
+(3 rows)
+
+--
+-- test temp table with toast table
+--
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |                0 |        483328 |           98304 |                 581632
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            32768 |                  |         32768 |               0 |                  32768
+(3 rows)
+
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |             8192 |        499712 |          106496 |                 606208
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            40960 |                  |         40960 |               0 |                  40960
+(3 rows)
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,10),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',10);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |             8192 |                0 |         16384 |           32768 |                  49152
+ idx_gtt_t_kenyon_1 |            16384 |                  |         16384 |               0 |                  16384
+ idx_gtt_t_kenyon_2 |            16384 |                  |         16384 |               0 |                  16384
+(3 rows)
+
+rollback;
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |             8192 |        499712 |          106496 |                 606208
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            40960 |                  |         40960 |               0 |                  40960
+(3 rows)
+
+-- test analyze/vacuum on global temp table
+ANALYZE gtt_t_kenyon;
+VACUUM gtt_t_kenyon;
+--
+-- test global temp table system view
+--
+create global temp table temp_table_test_systemview(a int primary key, b text) on commit PRESERVE rows;
+-- should empty, storage not initialized
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+ tablename 
+-----------
+(0 rows)
+
+-- should empty, storage not initialized
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     1
+(1 row)
+
+insert into temp_table_test_systemview values(generate_series(1,10000),'test');
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+         tablename          
+----------------------------
+ temp_table_test_systemview
+(1 row)
+
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     1
+(1 row)
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+       schemaname       |            tablename            | relpages | reltuples | relallvisible 
+------------------------+---------------------------------+----------+-----------+---------------
+ global_temporary_table | temp_table_test_systemview      |        0 |         0 |             0
+ global_temporary_table | temp_table_test_systemview_pkey |        1 |         0 |             0
+(2 rows)
+
+-- should empty
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+analyze temp_table_test_systemview;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+       schemaname       |            tablename            | relpages | reltuples | relallvisible 
+------------------------+---------------------------------+----------+-----------+---------------
+ global_temporary_table | temp_table_test_systemview      |       55 |     10000 |             0
+ global_temporary_table | temp_table_test_systemview_pkey |       30 |     10000 |             0
+(2 rows)
+
+-- get data after analyze;
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+       schemaname       |         tablename          | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------------------+----------------------------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ global_temporary_table | temp_table_test_systemview | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ global_temporary_table | temp_table_test_systemview | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+-- get all object info in current schema
+select relname ,relkind, relpersistence, reloptions from pg_class c, pg_namespace n where c.relnamespace = n.oid and n.nspname = 'global_temporary_table' order by relkind,relpersistence,relname;
+                     relname                     | relkind | relpersistence |          reloptions           
+-------------------------------------------------+---------+----------------+-------------------------------
+ global_temp_partition_01_2021_id_seq            | S       | g              | {on_commit_delete_rows=false}
+ global_temp_with_serial_a_seq                   | S       | g              | {on_commit_delete_rows=false}
+ regular_partition01_id_seq                      | S       | p              | 
+ regular_partition_01_2019_id_seq                | S       | p              | 
+ seq_1                                           | S       | p              | 
+ gtt_idx_1                                       | i       | g              | 
+ gtt_idx_2                                       | i       | g              | 
+ gtt_idx_3                                       | i       | g              | 
+ gtt_on_commit_default_pkey                      | i       | g              | 
+ gtt_on_commit_delete_pkey                       | i       | g              | 
+ gtt_on_commit_preserve_pkey                     | i       | g              | 
+ gtt_test_alter1_pkey                            | i       | g              | 
+ gtt_test_rename_pkey                            | i       | g              | 
+ idx_b                                           | i       | g              | 
+ idx_gtt_t_kenyon_1                              | i       | g              | 
+ idx_gtt_t_kenyon_2                              | i       | g              | 
+ idx_gtt_test_alter_b                            | i       | g              | 
+ idx_test_1                                      | i       | g              | 
+ idx_test_2                                      | i       | g              | 
+ products_pkey                                   | i       | g              | 
+ temp_orders_2_pkey                              | i       | g              | 
+ temp_orders_pkey                                | i       | g              | 
+ temp_products_pkey                              | i       | g              | 
+ temp_table_test_systemview_pkey                 | i       | g              | 
+ temp_table_with_sequence_oncommit_delete_pkey   | i       | g              | 
+ temp_table_with_sequence_oncommit_preserve_pkey | i       | g              | 
+ regular_partition01                             | p       | p              | 
+ global_temp_partition_01_2021                   | r       | g              | {on_commit_delete_rows=true}
+ global_temp_with_serial                         | r       | g              | {on_commit_delete_rows=false}
+ gtt_copytest                                    | r       | g              | {on_commit_delete_rows=false}
+ gtt_on_commit_default                           | r       | g              | {on_commit_delete_rows=false}
+ gtt_on_commit_delete                            | r       | g              | {on_commit_delete_rows=true}
+ gtt_on_commit_delete2                           | r       | g              | {on_commit_delete_rows=true}
+ gtt_on_commit_preserve                          | r       | g              | {on_commit_delete_rows=false}
+ gtt_t_kenyon                                    | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_alter                                  | r       | g              | {on_commit_delete_rows=true}
+ gtt_test_alter1                                 | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_createindex                            | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_new                                    | r       | g              | {on_commit_delete_rows=false}
+ inherits_parent_global_temp                     | r       | g              | {on_commit_delete_rows=true}
+ products                                        | r       | g              | {on_commit_delete_rows=false}
+ temp_orders                                     | r       | g              | {on_commit_delete_rows=true}
+ temp_orders_2                                   | r       | g              | {on_commit_delete_rows=false}
+ temp_products                                   | r       | g              | {on_commit_delete_rows=false}
+ temp_table_test_statistics                      | r       | g              | {on_commit_delete_rows=false}
+ temp_table_test_systemview                      | r       | g              | {on_commit_delete_rows=false}
+ temp_table_with_sequence_oncommit_delete        | r       | g              | {on_commit_delete_rows=true}
+ temp_table_with_sequence_oncommit_preserve      | r       | g              | {on_commit_delete_rows=false}
+ test_create_table_as                            | r       | g              | {on_commit_delete_rows=false}
+ foo                                             | r       | p              | 
+ inherits_parent                                 | r       | p              | 
+ regular_partition_01_2019                       | r       | p              | 
+(52 rows)
+
+reset search_path;
+drop schema global_temporary_table cascade;
+NOTICE:  drop cascades to 27 other objects
+DETAIL:  drop cascades to table global_temporary_table.gtt_on_commit_default
+drop cascades to table global_temporary_table.gtt_on_commit_delete
+drop cascades to table global_temporary_table.gtt_on_commit_delete2
+drop cascades to table global_temporary_table.gtt_on_commit_preserve
+drop cascades to table global_temporary_table.gtt_test_new
+drop cascades to table global_temporary_table.gtt_test_createindex
+drop cascades to table global_temporary_table.gtt_test_alter
+drop cascades to table global_temporary_table.regular_partition_01_2019
+drop cascades to table global_temporary_table.regular_partition01
+drop cascades to table global_temporary_table.global_temp_partition_01_2021
+drop cascades to table global_temporary_table.inherits_parent
+drop cascades to table global_temporary_table.inherits_parent_global_temp
+drop cascades to table global_temporary_table.gtt_test_alter1
+drop cascades to table global_temporary_table.foo
+drop cascades to table global_temporary_table.test_create_table_as
+drop cascades to table global_temporary_table.gtt_copytest
+drop cascades to table global_temporary_table.temp_products
+drop cascades to table global_temporary_table.products
+drop cascades to table global_temporary_table.temp_orders
+drop cascades to table global_temporary_table.temp_orders_2
+drop cascades to table global_temporary_table.global_temp_with_serial
+drop cascades to sequence global_temporary_table.seq_1
+drop cascades to table global_temporary_table.temp_table_with_sequence_oncommit_delete
+drop cascades to table global_temporary_table.temp_table_with_sequence_oncommit_preserve
+drop cascades to table global_temporary_table.temp_table_test_statistics
+drop cascades to table global_temporary_table.gtt_t_kenyon
+drop cascades to table global_temporary_table.temp_table_test_systemview
+-- should empty
+select * from pg_list_gtt_relfrozenxids();
+ pid | relfrozenxid 
+-----+--------------
+(0 rows)
+
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 1420288d67b..faa70c947fa 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1337,6 +1337,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 6d8f524ae9e..7924bc02f59 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -135,3 +135,6 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: global_temporary_table
diff --git a/src/test/regress/sql/global_temporary_table.sql b/src/test/regress/sql/global_temporary_table.sql
new file mode 100644
index 00000000000..51cd9bbbc0c
--- /dev/null
+++ b/src/test/regress/sql/global_temporary_table.sql
@@ -0,0 +1,315 @@
+--
+-- GLobal emparary table test case 
+--
+
+CREATE SCHEMA IF NOT EXISTS global_temporary_table;
+set search_path=global_temporary_table,sys;
+
+--
+--  test create global temp table basic syntax
+--
+create global temp table gtt_on_commit_default(a int primary key, b text);
+create global temp table gtt_on_commit_delete(a int primary key, b text) on commit delete rows;
+create global temp table gtt_on_commit_delete2(n int) with (on_commit_delete_rows='true');
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit PRESERVE rows;
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt_test_createindex(c0 tsvector,c1 varchar(100), c2 int);
+create global temp table gtt_test_alter(b text) with(on_commit_delete_rows=true);
+--
+-- test DML on global temp table
+--
+
+-- update empty temp table
+update gtt_on_commit_delete set b ='test';
+begin;
+insert into gtt_on_commit_delete values (1);
+update gtt_on_commit_delete set b ='test';
+-- should 1 row
+select * from gtt_on_commit_delete;
+commit;
+-- data delete after transaction commit
+-- should 0 row
+select * from gtt_on_commit_delete;
+
+-- update empty temp table
+update gtt_on_commit_preserve set b ='test';
+insert into gtt_on_commit_preserve values (2);
+begin;
+insert into gtt_on_commit_preserve values (3);
+update gtt_on_commit_preserve set b ='test';
+-- should 2 row
+select * from gtt_on_commit_preserve order by a;
+delete from gtt_on_commit_preserve where a=2;
+commit;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+
+begin;
+insert into gtt_on_commit_preserve values (4);
+-- temp table support truncate;
+truncate gtt_on_commit_preserve;
+select * from gtt_on_commit_preserve order by a;
+rollback;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+
+--
+-- test unsupported global temp partition table
+--
+
+-- should fail
+CREATE global temp TABLE global_temp_partition_01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+on commit delete rows;
+
+CREATE TABLE regular_partition_01_2019 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+);
+
+CREATE TABLE regular_partition01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time);
+
+-- should fail
+CREATE global temp TABLE temp_partition01_2018
+PARTITION OF regular_partition01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE global_temp_partition_01_2021 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+)on commit delete rows;
+
+-- should fail
+ALTER TABLE regular_partition01 ATTACH PARTITION global_temp_partition_01_2021 FOR VALUES FROM ('2021-01-01 00:00:00') TO ('2022-01-01 00:00:00');
+
+--
+-- test unsupported inherit table
+--
+create table 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 inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent);
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent_global_temp) on commit delete rows;
+
+--
+-- test DDL on global temp table
+--
+create index idx_gtt_test_alter_b on gtt_test_alter (b);
+insert into gtt_test_alter values('test');
+alter table gtt_test_alter alter b type varchar;
+create index gtt_idx_1 on gtt_test_createindex using gin (c0);
+create index gtt_idx_2 on gtt_test_createindex using gist (c0);
+create index gtt_idx_3 on gtt_test_createindex using hash (c2);
+alter table gtt_test_rename rename to gtt_test_new;
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+create index CONCURRENTLY idx_b on gtt_on_commit_default (b);
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+insert into gtt_on_commit_default values(1,'test');
+create global temp table gtt_test_alter1 (a int primary key,b text);
+alter table gtt_test_alter1 alter a type varchar;
+-- should fail
+alter table gtt_on_commit_default alter a type varchar;
+-- should fail
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) on commit delete rows;
+-- should fail
+REINDEX (TABLESPACE pg_default) TABLE gtt_test_createindex;
+-- should fail
+REINDEX (TABLESPACE pg_default) INDEX gtt_idx_1;
+-- should fail
+alter table gtt_on_commit_default set ( on_commit_delete_rows='true');
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) with(on_commit_delete_rows=true);
+-- should fail
+create or replace global temp view gtt_v as select 5;
+create table foo();
+-- should fail
+alter table foo set (on_commit_delete_rows='true');
+-- should fail
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit drop;
+-- should fail
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+-- should fail
+CREATE MATERIALIZED VIEW mv_gtt_on_commit_default as select * from gtt_on_commit_default;
+-- test create table as select
+CREATE GLOBAL TEMPORARY TABLE test_create_table_as AS SELECT 1 AS a;
+-- test copy stmt
+create global temp table gtt_copytest (
+        c1 int,
+        "col with , comma" text,
+        "col with "" quote"  int);
+
+copy gtt_copytest from stdin csv header;
+this is just a line full of junk that would error out if parsed
+1,a,1
+2,b,2
+\.
+select count(*) from gtt_copytest;
+
+--
+-- test foreign key dependencies for global temp table
+--
+CREATE global temp TABLE temp_products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+CREATE global temp TABLE temp_orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+)on commit delete rows;
+
+-- should fail
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+);
+
+CREATE global temp TABLE temp_orders_2 (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products(product_no),
+    quantity integer
+);
+
+--should fail
+insert into temp_orders values(1,1,1);
+
+insert into temp_products values(1,'test',1.0);
+begin;
+insert into temp_orders values(1,1,1);
+commit;
+-- should 1 row
+select count(*) from temp_products;
+-- should 0 row
+select count(*) from temp_orders;
+
+--
+-- test sequence on global temp table
+--
+create global temp table global_temp_with_serial (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+-- support insert data to temp table in read only transaction
+insert into global_temp_with_serial (b) values(1);
+select * from global_temp_with_serial;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_delete(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_preserve(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table temp_table_with_sequence_oncommit_delete add c2 int default nextval('seq_1');
+alter table temp_table_with_sequence_oncommit_preserve add c2 int default nextval('seq_1');
+begin;
+insert into temp_table_with_sequence_oncommit_delete (c1)values(1);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(2);
+insert into temp_table_with_sequence_oncommit_delete (c1)values(3);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(4);
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+commit;
+-- should 0 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_preserve order by c1;
+
+--
+-- test statistics on temp table
+--
+create global temp table temp_table_test_statistics(a int);
+insert into temp_table_test_statistics values(generate_series(1,100000));
+create index idx_test_1 on temp_table_test_statistics (a);
+create index idx_test_2 on temp_table_test_statistics((a*10));
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+-- test statistic for whole row
+explain (costs off) select count(*) from temp_table_test_statistics group by temp_table_test_statistics;
+-- test statistic for system column
+explain (costs off) select count(*) from temp_table_test_statistics group by tableoid;
+analyze temp_table_test_statistics;
+-- indexscan by idx_test_1
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+-- indexscan by idx_test_2
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+explain (costs off) select count(*) from temp_table_test_statistics group by temp_table_test_statistics;
+explain (costs off) select count(*) from temp_table_test_statistics group by tableoid;
+
+--
+-- test temp table with toast table
+--
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,10),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',10);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+rollback;
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+
+-- test analyze/vacuum on global temp table
+ANALYZE gtt_t_kenyon;
+VACUUM gtt_t_kenyon;
+
+--
+-- test global temp table system view
+--
+create global temp table temp_table_test_systemview(a int primary key, b text) on commit PRESERVE rows;
+-- should empty, storage not initialized
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+-- should empty, storage not initialized
+select count(*) from pg_list_gtt_relfrozenxids();
+insert into temp_table_test_systemview values(generate_series(1,10000),'test');
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+select count(*) from pg_list_gtt_relfrozenxids();
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+-- should empty
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+analyze temp_table_test_systemview;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+-- get data after analyze;
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+
+-- get all object info in current schema
+select relname ,relkind, relpersistence, reloptions from pg_class c, pg_namespace n where c.relnamespace = n.oid and n.nspname = 'global_temporary_table' order by relkind,relpersistence,relname;
+
+reset search_path;
+drop schema global_temporary_table cascade;
+-- should empty
+select * from pg_list_gtt_relfrozenxids();
+
-- 
2.32.0 (Apple Git-132)

#351Wenjing Zeng
wjzeng2012@gmail.com
In reply to: Wenjing Zeng (#350)
4 attachment(s)
Re: [Proposal] Global temporary tables

Update GTT v67 to fix conflicts with the latest code.

Regards, Wenjing.

Wenjing Zeng <wjzeng2012@gmail.com> 于2022年2月15日周二 17:03写道:

Show quoted text

Update GTT v66 to fix conflicts with the latest code.

Regards, Wenjing.

Wenjing Zeng <wjzeng2012@gmail.com> 于2022年1月20日周四 17:53写道:

very glad to see your reply.
Thank you very much for your review of the code and found so many
problems.
There was a conflict between the latest code and patch, I have corrected
it and provided a new patch (V65).
Waiting for your feedback.

Regards, Wenjing.

Andrew Bille <andrewbille@gmail.com> 于2022年1月10日周一 17:17写道:

Hi!

I could not detect crashes with your last patch, so I think the patch is
ready for a review.
Please, also consider fixing error messages, as existing ones don't
follow message writing guidelines.
https://www.postgresql.org/docs/14/error-style-guide.html

I corrected the ERROR message of GTT according to the link and the
existing error message.
Some comments and code refactoring were also done.

Regards, Andrew

On Thu, Dec 23, 2021 at 7:36 PM wenjing <wjzeng2012@gmail.com> wrote:

Andrew Bille <andrewbille@gmail.com> 于2021年12月21日周二 14:00写道:

Hi!
Thanks for new patches.
Yet another crash reproduced on master with v63 patches:

CREATE TABLESPACE ts LOCATION '/tmp/ts';
CREATE GLOBAL TEMP TABLE tbl (num1 bigint);
INSERT INTO tbl (num1) values (1);
CREATE INDEX tbl_idx ON tbl (num1);
REINDEX (TABLESPACE ts) TABLE tbl;

This is a feature made in PG14 that supports reindex change
tablespaces.
Thank you for pointing that out and I fixed it in v64.
Waiting for your feedback.

Attachments:

0004-gtt-v67-regress.patchapplication/octet-stream; name=0004-gtt-v67-regress.patchDownload
diff --git a/src/test/isolation/expected/gtt-sequence.out b/src/test/isolation/expected/gtt-sequence.out
new file mode 100644
index 00000000000..31db2ebd423
--- /dev/null
+++ b/src/test/isolation/expected/gtt-sequence.out
@@ -0,0 +1,48 @@
+unused step name: s1_seq_restart
+Parsed test spec with 2 sessions
+
+starting permutation: s1_seq_next s2_seq_next s1_seq_next
+step s1_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      1
+(1 row)
+
+step s2_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      1
+(1 row)
+
+step s1_seq_next: select nextval('gtt_with_seq_c2_seq');
+nextval
+-------
+      2
+(1 row)
+
+
+starting permutation: s1_select s2_select s1_insert s2_insert s1_select s2_select
+step s1_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+(0 rows)
+
+step s2_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+(0 rows)
+
+step s1_insert: insert into gtt_with_seq values(1);
+step s2_insert: insert into gtt_with_seq values(10);
+step s1_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+ 1| 3
+(1 row)
+
+step s2_select: select * from gtt_with_seq order by c1,c2;
+c1|c2
+--+--
+10| 1
+(1 row)
+
diff --git a/src/test/isolation/expected/gtt-table.out b/src/test/isolation/expected/gtt-table.out
new file mode 100644
index 00000000000..5825773aa12
--- /dev/null
+++ b/src/test/isolation/expected/gtt-table.out
@@ -0,0 +1,675 @@
+Parsed test spec with 3 sessions
+
+starting permutation: s1_update_d
+step s1_update_d: update gtt_on_commit_delete_row set b = 'update'
+
+starting permutation: s1_select_d s1_insert_d s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_begin s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+1|test1
+(1 row)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_begin s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+1|test1
+(1 row)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_select_d s1_truncate_d s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_begin s1_insert_d s1_select_d s1_truncate_d s1_select_d s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_select_d s1_insert_d s1_begin s1_insert_d s1_select_d s1_truncate_d s1_select_d s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_insert_d s1_select_d s1_begin s1_insert_d s1_select_d s1_save_1 s1_truncate_d s1_insert_d s1_select_d s1_save_2 s1_truncate_d s1_insert_d s1_select_d s1_save_3 s1_rollback_to_save_2 s1_select_d s1_insert_d s1_select_d s1_commit s1_select_d
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_save_1: SAVEPOINT save1
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_save_2: SAVEPOINT save2
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+4|test1
+(1 row)
+
+step s1_save_3: SAVEPOINT save3
+step s1_rollback_to_save_2: rollback to savepoint save2
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+5|test1
+(2 rows)
+
+step s1_commit: commit
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_insert_d s1_select_d s1_begin s1_insert_d s1_select_d s1_save_1 s1_truncate_d s1_insert_d s1_select_d s1_save_2 s1_truncate_d s1_insert_d s1_select_d s1_save_3 s1_rollback_to_save_2 s1_select_d s1_insert_d s1_select_d s1_rollback s1_select_d
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+2|test1
+(1 row)
+
+step s1_save_1: SAVEPOINT save1
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_save_2: SAVEPOINT save2
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+4|test1
+(1 row)
+
+step s1_save_3: SAVEPOINT save3
+step s1_rollback_to_save_2: rollback to savepoint save2
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+(1 row)
+
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b    
+-+-----
+3|test1
+5|test1
+(2 rows)
+
+step s1_rollback: rollback
+step s1_select_d: select a,b from gtt_on_commit_delete_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_update_p
+step s2_update_p: update gtt_on_commit_preserve_row set b = 'update'
+
+starting permutation: s2_select_p s2_insert_p s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_begin s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_begin s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_select_p s2_insert_p s2_select_p s2_truncate_p s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s2_select_p s2_insert_p s2_begin s2_insert_p s2_select_p s2_truncate_p s2_select_p s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+
+starting permutation: s2_select_p s2_insert_p s2_begin s2_insert_p s2_select_p s2_truncate_p s2_select_p s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_insert_p s2_select_p s2_begin s2_insert_p s2_select_p s2_save_1 s2_truncate_p s2_insert_p s2_select_p s2_save_2 s2_truncate_p s2_insert_p s2_select_p s2_save_3 s2_rollback_to_save_2 s2_select_p s2_insert_p s2_select_p s2_commit s2_select_p
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_save_1: SAVEPOINT save1
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_save_2: SAVEPOINT save2
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+4|test10
+(1 row)
+
+step s2_save_3: SAVEPOINT save3
+step s2_rollback_to_save_2: rollback to savepoint save2
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+step s2_commit: commit
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+
+starting permutation: s2_insert_p s2_select_p s2_begin s2_insert_p s2_select_p s2_save_1 s2_truncate_p s2_insert_p s2_select_p s2_save_2 s2_truncate_p s2_insert_p s2_select_p s2_save_3 s2_rollback_to_save_2 s2_select_p s2_insert_p s2_select_p s2_rollback s2_select_p
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+step s2_begin: begin
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+2|test10
+(2 rows)
+
+step s2_save_1: SAVEPOINT save1
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_save_2: SAVEPOINT save2
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+4|test10
+(1 row)
+
+step s2_save_3: SAVEPOINT save3
+step s2_rollback_to_save_2: rollback to savepoint save2
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+(1 row)
+
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+3|test10
+5|test10
+(2 rows)
+
+step s2_rollback: rollback
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_insert_p s2_insert_p s1_select_p s2_select_p
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_truncate_p s2_truncate_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b
+-+-
+(0 rows)
+
+
+starting permutation: s1_begin s1_insert_d s2_insert_d s1_truncate_d s2_insert_d s1_commit
+step s1_begin: begin
+step s1_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s2_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_truncate_d: truncate gtt_on_commit_delete_row
+step s2_insert_d: insert into gtt_on_commit_delete_row (b) values('test1')
+step s1_commit: commit
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_reindex_p s2_reindex_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_reindex_p: reindex table gtt_on_commit_preserve_row
+step s2_reindex_p: reindex table gtt_on_commit_preserve_row
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s1_begin s2_begin s1_insert_p s2_insert_p s1_reindex_i_p s2_reindex_i_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_reindex_i_p: reindex index gtt_on_commit_preserve_row_pkey
+step s2_reindex_i_p: reindex index gtt_on_commit_preserve_row_pkey
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
+
+starting permutation: s2_insert_c s3_create_c s3_insert_c s1_insert_c s1_analyze_c s2_analyze_c s3_analyze_c s1_select_c s2_select_c s3_select_c
+step s2_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s3_create_c: create unique index idx_temp_table_a on gtt_test_createindex(a)
+step s3_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s1_insert_c: insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')
+step s1_analyze_c: analyze gtt_test_createindex
+step s2_analyze_c: analyze gtt_test_createindex
+step s3_analyze_c: analyze gtt_test_createindex
+step s1_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                                               
+---------------------------------------------------------
+Index Scan using idx_temp_table_a on gtt_test_createindex
+  Index Cond: (a = 1)                                    
+(2 rows)
+
+step s2_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                      
+--------------------------------
+Seq Scan on gtt_test_createindex
+  Filter: (a = 1)               
+(2 rows)
+
+step s3_select_c: explain (costs off) select * from gtt_test_createindex where a = 1
+QUERY PLAN                                               
+---------------------------------------------------------
+Index Scan using idx_temp_table_a on gtt_test_createindex
+  Index Cond: (a = 1)                                    
+(2 rows)
+
+
+starting permutation: s1_begin s2_begin s1_lock_p s2_lock_p s1_truncate_p s2_truncate_p s1_insert_p s2_insert_p s1_commit s2_commit s1_select_p s2_select_p
+step s1_begin: begin
+step s2_begin: begin
+step s1_lock_p: LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE
+step s2_lock_p: LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE
+step s1_truncate_p: truncate gtt_on_commit_preserve_row
+step s2_truncate_p: truncate gtt_on_commit_preserve_row
+step s1_insert_p: insert into gtt_on_commit_preserve_row (b) values('test20')
+step s2_insert_p: insert into gtt_on_commit_preserve_row (b) values('test10')
+step s1_commit: commit
+step s2_commit: commit
+step s1_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test20
+(1 row)
+
+step s2_select_p: select a,b from gtt_on_commit_preserve_row order by a,b
+a|b     
+-+------
+1|test10
+(1 row)
+
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index 0dae483e827..77d4cff5fcb 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -98,3 +98,5 @@ test: plpgsql-toast
 test: truncate-conflict
 test: serializable-parallel
 test: serializable-parallel-2
+test: gtt-sequence
+test: gtt-table
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 12179f25146..7cf0b20e636 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -80,9 +80,30 @@ disconnect_atexit(void)
 {
 	int			i;
 
-	for (i = 0; i < nconns; i++)
+	for (i = 1; i < nconns; i++)
 		if (conns[i].conn)
 			PQfinish(conns[i].conn);
+
+	if (parseresult.destroy)
+	{
+		PGresult   *res;
+
+		res = PQexec(conns[0].conn, parseresult.destroy);
+		if (PQresultStatus(res) == PGRES_TUPLES_OK)
+		{
+			printResultSet(res);
+		}
+		else if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		{
+			fprintf(stderr, "destroy failed: %s",
+					PQerrorMessage(conns[0].conn));
+			/* don't exit on teardown failure */
+		}
+		PQclear(res);
+	}
+
+	if (conns[0].conn)
+		PQfinish(conns[0].conn);
 }
 
 int
@@ -238,6 +259,24 @@ main(int argc, char **argv)
 	PQclear(res);
 	termPQExpBuffer(&wait_query);
 
+	if (parseresult.initialize)
+	{
+		PGresult   *res;
+
+		res = PQexec(conns[0].conn, parseresult.initialize);
+		if (PQresultStatus(res) == PGRES_TUPLES_OK)
+		{
+			printResultSet(res);
+		}
+		else if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		{
+			fprintf(stderr, "initialize failed: %s",
+					PQerrorMessage(conns[0].conn));
+			/* don't exit on teardown failure */
+		}
+		PQclear(res);
+	}
+
 	/*
 	 * Run the permutations specified in the spec, or all if none were
 	 * explicitly specified.
diff --git a/src/test/isolation/isolationtester.h b/src/test/isolation/isolationtester.h
index e00bc6b816b..e15d3c6d3ea 100644
--- a/src/test/isolation/isolationtester.h
+++ b/src/test/isolation/isolationtester.h
@@ -81,6 +81,8 @@ typedef struct
 	int			nsessions;
 	Permutation **permutations;
 	int			npermutations;
+	char	   *initialize;
+	char	   *destroy;
 } TestSpec;
 
 extern TestSpec parseresult;
diff --git a/src/test/isolation/specparse.y b/src/test/isolation/specparse.y
index eb368184b8b..fb0107eccf2 100644
--- a/src/test/isolation/specparse.y
+++ b/src/test/isolation/specparse.y
@@ -39,7 +39,7 @@ TestSpec		parseresult;			/* result of parsing is left here */
 }
 
 %type <ptr_list> setup_list
-%type <str>  opt_setup opt_teardown
+%type <str>  opt_setup opt_teardown opt_initialize opt_destroy
 %type <str> setup
 %type <ptr_list> step_list session_list permutation_list opt_permutation_list
 %type <ptr_list> permutation_step_list blocker_list
@@ -51,23 +51,27 @@ TestSpec		parseresult;			/* result of parsing is left here */
 
 %token <str> sqlblock identifier
 %token <integer> INTEGER
-%token NOTICES PERMUTATION SESSION SETUP STEP TEARDOWN TEST
+%token NOTICES PERMUTATION SESSION SETUP STEP TEARDOWN TEST INITIALIZE DESTROY
 
 %%
 
 TestSpec:
+			opt_initialize
 			setup_list
 			opt_teardown
+			opt_destroy
 			session_list
 			opt_permutation_list
 			{
-				parseresult.setupsqls = (char **) $1.elements;
-				parseresult.nsetupsqls = $1.nelements;
-				parseresult.teardownsql = $2;
-				parseresult.sessions = (Session **) $3.elements;
-				parseresult.nsessions = $3.nelements;
-				parseresult.permutations = (Permutation **) $4.elements;
-				parseresult.npermutations = $4.nelements;
+				parseresult.setupsqls = (char **) $2.elements;
+				parseresult.nsetupsqls = $2.nelements;
+				parseresult.teardownsql = $3;
+				parseresult.sessions = (Session **) $5.elements;
+				parseresult.nsessions = $5.nelements;
+				parseresult.permutations = (Permutation **) $6.elements;
+				parseresult.npermutations = $6.nelements;
+				parseresult.initialize = $1;
+				parseresult.destroy = $4;
 			}
 		;
 
@@ -100,6 +104,16 @@ opt_teardown:
 			| TEARDOWN sqlblock	{ $$ = $2; }
 		;
 
+opt_initialize:
+			/* EMPTY */			{ $$ = NULL; }
+			| INITIALIZE sqlblock	{ $$ = $2; }
+		;
+
+opt_destroy:
+			/* EMPTY */			{ $$ = NULL; }
+			| DESTROY sqlblock	{ $$ = $2; }
+		;
+
 session_list:
 			session_list session
 			{
diff --git a/src/test/isolation/specs/gtt-sequence.spec b/src/test/isolation/specs/gtt-sequence.spec
new file mode 100644
index 00000000000..88eece45e29
--- /dev/null
+++ b/src/test/isolation/specs/gtt-sequence.spec
@@ -0,0 +1,39 @@
+# Tests for global temporary relations
+
+initialize
+{
+  CREATE GLOBAL TEMPORARY TABLE if not exists gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+}
+
+destroy
+{
+  /* wait other backend exit */
+  select pg_sleep(1);
+
+  DROP TABLE gtt_with_seq;
+}
+
+# Session 1
+session "s1"
+step "s1_seq_next" { select nextval('gtt_with_seq_c2_seq'); }
+step "s1_seq_restart" { alter sequence gtt_with_seq_c2_seq RESTART; }
+step "s1_insert" { insert into gtt_with_seq values(1); }
+step "s1_select" { select * from gtt_with_seq order by c1,c2; }
+teardown
+{
+  TRUNCATE gtt_with_seq;
+}
+
+# Session 2
+session "s2"
+step "s2_seq_next" { select nextval('gtt_with_seq_c2_seq'); }
+step "s2_insert" { insert into gtt_with_seq values(10); }
+step "s2_select" { select * from gtt_with_seq order by c1,c2; }
+teardown
+{
+  TRUNCATE gtt_with_seq RESTART IDENTITY;
+}
+
+permutation "s1_seq_next" "s2_seq_next" "s1_seq_next"
+permutation "s1_select" "s2_select" "s1_insert" "s2_insert" "s1_select" "s2_select"
+
diff --git a/src/test/isolation/specs/gtt-table.spec b/src/test/isolation/specs/gtt-table.spec
new file mode 100644
index 00000000000..e0396b21ef0
--- /dev/null
+++ b/src/test/isolation/specs/gtt-table.spec
@@ -0,0 +1,135 @@
+# Tests for global temporary relations
+
+initialize
+{
+  create global temp table gtt_on_commit_delete_row(a bigserial primary key, b text) on commit delete rows;
+  create global temp table gtt_on_commit_preserve_row(a bigserial primary key, b text) on commit preserve rows;
+  create global temp table gtt_test_createindex(a int, b char(1000)) on commit preserve rows;
+}
+
+destroy
+{
+  /* wait other backend exit */
+  select pg_sleep(1);
+
+  DROP TABLE gtt_on_commit_delete_row;
+  DROP TABLE gtt_on_commit_preserve_row;
+  DROP TABLE gtt_test_createindex;
+}
+
+# Session 1
+session "s1"
+step "s1_begin" {begin}
+step "s1_commit" {commit}
+step "s1_rollback" {rollback}
+step "s1_insert_d" {insert into gtt_on_commit_delete_row (b) values('test1')}
+step "s1_select_d" {select a,b from gtt_on_commit_delete_row order by a,b}
+step "s1_insert_p" {insert into gtt_on_commit_preserve_row (b) values('test20')}
+step "s1_select_p" {select a,b from gtt_on_commit_preserve_row order by a,b}
+step "s1_truncate_d" {truncate gtt_on_commit_delete_row}
+step "s1_truncate_p" {truncate gtt_on_commit_preserve_row}
+step "s1_lock_p" {LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE}
+step "s1_update_d" {update gtt_on_commit_delete_row set b = 'update'}
+step "s1_save_1" {SAVEPOINT save1}
+step "s1_save_2" {SAVEPOINT save2}
+step "s1_save_3" {SAVEPOINT save3}
+step "s1_rollback_to_save_2" {rollback to savepoint save2}
+step "s1_reindex_p" {reindex table gtt_on_commit_preserve_row}
+step "s1_reindex_i_p" {reindex index gtt_on_commit_preserve_row_pkey}
+step "s1_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s1_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s1_analyze_c" {analyze gtt_test_createindex}
+teardown
+{
+  TRUNCATE gtt_on_commit_delete_row RESTART IDENTITY;
+  TRUNCATE gtt_on_commit_preserve_row RESTART IDENTITY;
+}
+
+# Session 2
+session "s2"
+step "s2_begin" {begin}
+step "s2_commit" {commit}
+step "s2_rollback" {rollback}
+step "s2_insert_p" {insert into gtt_on_commit_preserve_row (b) values('test10')}
+step "s2_select_p" {select a,b from gtt_on_commit_preserve_row order by a,b}
+step "s2_insert_d" {insert into gtt_on_commit_delete_row (b) values('test1')}
+step "s2_truncate_p" {truncate gtt_on_commit_preserve_row}
+step "s2_update_p" {update gtt_on_commit_preserve_row set b = 'update'}
+step "s2_lock_p" {LOCK TABLE gtt_on_commit_preserve_row in ACCESS EXCLUSIVE MODE}
+step "s2_save_1" {SAVEPOINT save1}
+step "s2_save_2" {SAVEPOINT save2}
+step "s2_save_3" {SAVEPOINT save3}
+step "s2_rollback_to_save_2" {rollback to savepoint save2}
+step "s2_reindex_p" {reindex table gtt_on_commit_preserve_row}
+step "s2_reindex_i_p" {reindex index gtt_on_commit_preserve_row_pkey}
+step "s2_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s2_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s2_analyze_c" {analyze gtt_test_createindex}
+teardown
+{
+  TRUNCATE gtt_on_commit_delete_row RESTART IDENTITY;
+  TRUNCATE gtt_on_commit_preserve_row RESTART IDENTITY;
+}
+
+session "s3"
+step "s3_create_c" {create unique index idx_temp_table_a on gtt_test_createindex(a)}
+step "s3_insert_c" {insert into gtt_test_createindex(a,b) values(generate_series(1,100000),'test create index')}
+step "s3_select_c" {explain (costs off) select * from gtt_test_createindex where a = 1}
+step "s3_analyze_c" {analyze gtt_test_createindex}
+
+
+#
+# test on commit delete temp table
+#
+
+# test update empty temp table
+permutation "s1_update_d"
+# test insert into temp table
+permutation "s1_select_d" "s1_insert_d" "s1_select_d"
+# test temp table in transaction(commit)
+permutation "s1_select_d" "s1_begin"    "s1_insert_d" "s1_select_d"   "s1_commit"   "s1_select_d" 
+# test temp table in transaction(rollback)
+permutation "s1_select_d" "s1_begin"    "s1_insert_d" "s1_select_d"   "s1_rollback" "s1_select_d" 
+# test truncate
+permutation "s1_select_d" "s1_insert_d" "s1_select_d" "s1_truncate_d" "s1_select_d"
+# test truncate in transaction block
+permutation "s1_select_d" "s1_insert_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_truncate_d" "s1_select_d"   "s1_insert_d" "s1_select_d" "s1_commit"   "s1_select_d" 
+permutation "s1_select_d" "s1_insert_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_truncate_d" "s1_select_d"   "s1_insert_d" "s1_select_d" "s1_rollback" "s1_select_d"
+# test temp table with subtransaction or savepoint
+permutation "s1_insert_d" "s1_select_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_save_1"     "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_2"   "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_3" "s1_rollback_to_save_2" "s1_select_d" "s1_insert_d" "s1_select_d" "s1_commit" "s1_select_d"
+permutation "s1_insert_d" "s1_select_d" "s1_begin"    "s1_insert_d"   "s1_select_d" "s1_save_1"     "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_2"   "s1_truncate_d" "s1_insert_d" "s1_select_d" "s1_save_3" "s1_rollback_to_save_2" "s1_select_d" "s1_insert_d" "s1_select_d" "s1_rollback" "s1_select_d"
+
+#
+# test on commit preserve table
+#
+
+# same as test on commit delete temp table
+permutation "s2_update_p"
+permutation "s2_select_p" "s2_insert_p" "s2_select_p"
+permutation "s2_select_p" "s2_begin"    "s2_insert_p" "s2_select_p"   "s2_commit"   "s2_select_p"
+permutation "s2_select_p" "s2_begin"    "s2_insert_p" "s2_select_p"   "s2_rollback" "s2_select_p"
+permutation "s2_select_p" "s2_insert_p" "s2_select_p" "s2_truncate_p" "s2_select_p"
+permutation "s2_select_p" "s2_insert_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_truncate_p" "s2_select_p"   "s2_insert_p" "s2_select_p" "s2_commit"   "s2_select_p" 
+permutation "s2_select_p" "s2_insert_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_truncate_p" "s2_select_p"   "s2_insert_p" "s2_select_p" "s2_rollback" "s2_select_p" 
+permutation "s2_insert_p" "s2_select_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_save_1"     "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_2"   "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_3" "s2_rollback_to_save_2" "s2_select_p" "s2_insert_p" "s2_select_p" "s2_commit" "s2_select_p"
+permutation "s2_insert_p" "s2_select_p" "s2_begin"    "s2_insert_p"   "s2_select_p" "s2_save_1"     "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_2"   "s2_truncate_p" "s2_insert_p" "s2_select_p" "s2_save_3" "s2_rollback_to_save_2" "s2_select_p" "s2_insert_p" "s2_select_p" "s2_rollback" "s2_select_p"
+
+#
+# test concurrent operation on temp table
+#
+
+#  test concurrent read
+permutation "s1_insert_p" "s2_insert_p" "s1_select_p" "s2_select_p" 
+#  test concurrent truncate
+permutation "s1_begin" "s2_begin"    "s1_insert_p" "s2_insert_p"   "s1_truncate_p" "s2_truncate_p"  "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+permutation "s1_begin" "s1_insert_d" "s2_insert_d" "s1_truncate_d" "s2_insert_d"   "s1_commit" 
+#  test concurrent reindex table
+permutation "s1_begin" "s2_begin" "s1_insert_p" "s2_insert_p" "s1_reindex_p"   "s2_reindex_p"   "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+#  test concurrent reindex index
+permutation "s1_begin" "s2_begin" "s1_insert_p" "s2_insert_p" "s1_reindex_i_p" "s2_reindex_i_p" "s1_commit" "s2_commit" "s1_select_p" "s2_select_p" 
+
+# test create index
+permutation "s2_insert_c" "s3_create_c" "s3_insert_c" "s1_insert_c" "s1_analyze_c" "s2_analyze_c" "s3_analyze_c" "s1_select_c" "s2_select_c" "s3_select_c"
+
+# test lock gtt
+permutation "s1_begin" "s2_begin" "s1_lock_p" "s2_lock_p" "s1_truncate_p" "s2_truncate_p" "s1_insert_p" "s2_insert_p" "s1_commit" "s2_commit" "s1_select_p" "s2_select_p"
diff --git a/src/test/isolation/specscanner.l b/src/test/isolation/specscanner.l
index aa6e89268ef..f782b9b8fcc 100644
--- a/src/test/isolation/specscanner.l
+++ b/src/test/isolation/specscanner.l
@@ -67,6 +67,8 @@ session			{ return SESSION; }
 setup			{ return SETUP; }
 step			{ return STEP; }
 teardown		{ return TEARDOWN; }
+initialize		 { return INITIALIZE; }
+destroy			 { return DESTROY; }
 
  /* Whitespace and comments */
 [\n]			{ yyline++; }
diff --git a/src/test/regress/expected/global_temporary_table.out b/src/test/regress/expected/global_temporary_table.out
new file mode 100644
index 00000000000..5b8203f28f5
--- /dev/null
+++ b/src/test/regress/expected/global_temporary_table.out
@@ -0,0 +1,577 @@
+--
+-- GLobal emparary table test case 
+--
+CREATE SCHEMA IF NOT EXISTS global_temporary_table;
+set search_path=global_temporary_table,sys;
+--
+--  test create global temp table basic syntax
+--
+create global temp table gtt_on_commit_default(a int primary key, b text);
+create global temp table gtt_on_commit_delete(a int primary key, b text) on commit delete rows;
+create global temp table gtt_on_commit_delete2(n int) with (on_commit_delete_rows='true');
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit PRESERVE rows;
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt_test_createindex(c0 tsvector,c1 varchar(100), c2 int);
+create global temp table gtt_test_alter(b text) with(on_commit_delete_rows=true);
+--
+-- test DML on global temp table
+--
+-- update empty temp table
+update gtt_on_commit_delete set b ='test';
+begin;
+insert into gtt_on_commit_delete values (1);
+update gtt_on_commit_delete set b ='test';
+-- should 1 row
+select * from gtt_on_commit_delete;
+ a |  b   
+---+------
+ 1 | test
+(1 row)
+
+commit;
+-- data delete after transaction commit
+-- should 0 row
+select * from gtt_on_commit_delete;
+ a | b 
+---+---
+(0 rows)
+
+-- update empty temp table
+update gtt_on_commit_preserve set b ='test';
+insert into gtt_on_commit_preserve values (2);
+begin;
+insert into gtt_on_commit_preserve values (3);
+update gtt_on_commit_preserve set b ='test';
+-- should 2 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 2 | test
+ 3 | test
+(2 rows)
+
+delete from gtt_on_commit_preserve where a=2;
+commit;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 3 | test
+(1 row)
+
+begin;
+insert into gtt_on_commit_preserve values (4);
+-- temp table support truncate;
+truncate gtt_on_commit_preserve;
+select * from gtt_on_commit_preserve order by a;
+ a | b 
+---+---
+(0 rows)
+
+rollback;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+ a |  b   
+---+------
+ 3 | test
+(1 row)
+
+--
+-- test unsupported global temp partition table
+--
+-- should fail
+CREATE global temp TABLE global_temp_partition_01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+on commit delete rows;
+ERROR:  global temporary relation can only be a regular table or sequence
+CREATE TABLE regular_partition_01_2019 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+);
+CREATE TABLE regular_partition01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time);
+-- should fail
+CREATE global temp TABLE temp_partition01_2018
+PARTITION OF regular_partition01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+ERROR:  cannot create global temporary inherit table or global temporary partitioned table
+CREATE global temp TABLE global_temp_partition_01_2021 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+)on commit delete rows;
+-- should fail
+ALTER TABLE regular_partition01 ATTACH PARTITION global_temp_partition_01_2021 FOR VALUES FROM ('2021-01-01 00:00:00') TO ('2022-01-01 00:00:00');
+ERROR:  cannot attach a global temporary relation as partition of permanent relation "regular_partition01"
+--
+-- test unsupported inherit table
+--
+create table 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 inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent);
+ERROR:  cannot create global temporary inherit table or global temporary partitioned table
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent_global_temp) on commit delete rows;
+ERROR:  cannot create global temporary inherit table or global temporary partitioned table
+--
+-- test DDL on global temp table
+--
+create index idx_gtt_test_alter_b on gtt_test_alter (b);
+insert into gtt_test_alter values('test');
+alter table gtt_test_alter alter b type varchar;
+create index gtt_idx_1 on gtt_test_createindex using gin (c0);
+create index gtt_idx_2 on gtt_test_createindex using gist (c0);
+create index gtt_idx_3 on gtt_test_createindex using hash (c2);
+alter table gtt_test_rename rename to gtt_test_new;
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+create index CONCURRENTLY idx_b on gtt_on_commit_default (b);
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+insert into gtt_on_commit_default values(1,'test');
+create global temp table gtt_test_alter1 (a int primary key,b text);
+alter table gtt_test_alter1 alter a type varchar;
+-- should fail
+alter table gtt_on_commit_default alter a type varchar;
+ERROR:  cannot rewrite global temporary table "gtt_on_commit_default" when it has data in this session
+HINT:  Please create a new connection and execute ALTER TABLE on the new connection.
+-- should fail
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+ERROR:  cannot cluster global temporary table
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- should fail
+REINDEX (TABLESPACE pg_default) TABLE gtt_test_createindex;
+ERROR:  cannot change tablespace of global temporary table
+-- should fail
+REINDEX (TABLESPACE pg_default) INDEX gtt_idx_1;
+ERROR:  cannot change tablespace of global temporary table
+-- should fail
+alter table gtt_on_commit_default set ( on_commit_delete_rows='true');
+ERROR:  cannot set "on_commit_delete_rows" for relation "gtt_on_commit_default"
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  on_commit_delete_rows can only be used on global temporary table
+-- should fail
+create or replace global temp view gtt_v as select 5;
+ERROR:  views cannot be global temporary because they do not have storage
+create table foo();
+-- should fail
+alter table foo set (on_commit_delete_rows='true');
+ERROR:  cannot set "on_commit_delete_rows" for relation "foo"
+-- should fail
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit drop;
+ERROR:  specifying ON COMMIT DROP is not supported on a global temporary table
+-- should fail
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  cannot specify both ON COMMIT clause and on_commit_delete_rows
+-- should fail
+CREATE MATERIALIZED VIEW mv_gtt_on_commit_default as select * from gtt_on_commit_default;
+ERROR:  materialized views must not use temporary tables or views
+-- test create table as select
+CREATE GLOBAL TEMPORARY TABLE test_create_table_as AS SELECT 1 AS a;
+-- test copy stmt
+create global temp table gtt_copytest (
+        c1 int,
+        "col with , comma" text,
+        "col with "" quote"  int);
+copy gtt_copytest from stdin csv header;
+select count(*) from gtt_copytest;
+ count 
+-------
+     2
+(1 row)
+
+--
+-- test foreign key dependencies for global temp table
+--
+CREATE global temp TABLE temp_products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+CREATE global temp TABLE temp_orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+)on commit delete rows;
+-- should fail
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+);
+ERROR:  constraints on permanent tables may reference only permanent tables
+CREATE global temp TABLE temp_orders_2 (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products(product_no),
+    quantity integer
+);
+--should fail
+insert into temp_orders values(1,1,1);
+ERROR:  insert or update on table "temp_orders" violates foreign key constraint "temp_orders_product_no_fkey"
+DETAIL:  Key (product_no)=(1) is not present in table "temp_products".
+insert into temp_products values(1,'test',1.0);
+begin;
+insert into temp_orders values(1,1,1);
+commit;
+-- should 1 row
+select count(*) from temp_products;
+ count 
+-------
+     1
+(1 row)
+
+-- should 0 row
+select count(*) from temp_orders;
+ count 
+-------
+     0
+(1 row)
+
+--
+-- test sequence on global temp table
+--
+create global temp table global_temp_with_serial (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+-- support insert data to temp table in read only transaction
+insert into global_temp_with_serial (b) values(1);
+select * from global_temp_with_serial;
+ a | b 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_delete(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_preserve(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table temp_table_with_sequence_oncommit_delete add c2 int default nextval('seq_1');
+alter table temp_table_with_sequence_oncommit_preserve add c2 int default nextval('seq_1');
+begin;
+insert into temp_table_with_sequence_oncommit_delete (c1)values(1);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(2);
+insert into temp_table_with_sequence_oncommit_delete (c1)values(3);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(4);
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+ c1 | c2 
+----+----
+  1 |  1
+  3 |  3
+(2 rows)
+
+commit;
+-- should 0 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+ c1 | c2 
+----+----
+(0 rows)
+
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_preserve order by c1;
+ c1 | c2 
+----+----
+  2 |  2
+  4 |  4
+(2 rows)
+
+--
+-- test statistics on temp table
+--
+create global temp table temp_table_test_statistics(a int);
+insert into temp_table_test_statistics values(generate_series(1,100000));
+create index idx_test_1 on temp_table_test_statistics (a);
+create index idx_test_2 on temp_table_test_statistics((a*10));
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+                   QUERY PLAN                   
+------------------------------------------------
+ Bitmap Heap Scan on temp_table_test_statistics
+   Recheck Cond: (a = 200000)
+   ->  Bitmap Index Scan on idx_test_1
+         Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+                   QUERY PLAN                   
+------------------------------------------------
+ Bitmap Heap Scan on temp_table_test_statistics
+   Recheck Cond: ((a * 10) = 3)
+   ->  Bitmap Index Scan on idx_test_2
+         Index Cond: ((a * 10) = 3)
+(4 rows)
+
+-- test statistic for whole row
+explain (costs off) select count(*) from temp_table_test_statistics group by temp_table_test_statistics;
+                  QUERY PLAN                  
+----------------------------------------------
+ HashAggregate
+   Group Key: temp_table_test_statistics.*
+   ->  Seq Scan on temp_table_test_statistics
+(3 rows)
+
+-- test statistic for system column
+explain (costs off) select count(*) from temp_table_test_statistics group by tableoid;
+                  QUERY PLAN                  
+----------------------------------------------
+ HashAggregate
+   Group Key: tableoid
+   ->  Seq Scan on temp_table_test_statistics
+(3 rows)
+
+analyze temp_table_test_statistics;
+-- indexscan by idx_test_1
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Index Only Scan using idx_test_1 on temp_table_test_statistics
+   Index Cond: (a = 200000)
+(2 rows)
+
+-- indexscan by idx_test_2
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+                        QUERY PLAN                         
+-----------------------------------------------------------
+ Index Scan using idx_test_2 on temp_table_test_statistics
+   Index Cond: ((a * 10) = 3)
+(2 rows)
+
+explain (costs off) select count(*) from temp_table_test_statistics group by temp_table_test_statistics;
+                  QUERY PLAN                  
+----------------------------------------------
+ HashAggregate
+   Group Key: temp_table_test_statistics.*
+   ->  Seq Scan on temp_table_test_statistics
+(3 rows)
+
+explain (costs off) select count(*) from temp_table_test_statistics group by tableoid;
+                  QUERY PLAN                  
+----------------------------------------------
+ HashAggregate
+   Group Key: tableoid
+   ->  Seq Scan on temp_table_test_statistics
+(3 rows)
+
+--
+-- test temp table with toast table
+--
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |                0 |        483328 |           98304 |                 581632
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            32768 |                  |         32768 |               0 |                  32768
+(3 rows)
+
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |             8192 |        499712 |          106496 |                 606208
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            40960 |                  |         40960 |               0 |                  40960
+(3 rows)
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,10),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',10);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |             8192 |                0 |         16384 |           32768 |                  49152
+ idx_gtt_t_kenyon_1 |            16384 |                  |         16384 |               0 |                  16384
+ idx_gtt_t_kenyon_2 |            16384 |                  |         16384 |               0 |                  16384
+(3 rows)
+
+rollback;
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+      relname       | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size 
+--------------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon       |           450560 |             8192 |        499712 |          106496 |                 606208
+ idx_gtt_t_kenyon_1 |            65536 |                  |         65536 |               0 |                  65536
+ idx_gtt_t_kenyon_2 |            40960 |                  |         40960 |               0 |                  40960
+(3 rows)
+
+-- test analyze/vacuum on global temp table
+ANALYZE gtt_t_kenyon;
+VACUUM gtt_t_kenyon;
+--
+-- test global temp table system view
+--
+create global temp table temp_table_test_systemview(a int primary key, b text) on commit PRESERVE rows;
+-- should empty, storage not initialized
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+ tablename 
+-----------
+(0 rows)
+
+-- should empty, storage not initialized
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     1
+(1 row)
+
+insert into temp_table_test_systemview values(generate_series(1,10000),'test');
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+         tablename          
+----------------------------
+ temp_table_test_systemview
+(1 row)
+
+select count(*) from pg_list_gtt_relfrozenxids();
+ count 
+-------
+     1
+(1 row)
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+       schemaname       |            tablename            | relpages | reltuples | relallvisible 
+------------------------+---------------------------------+----------+-----------+---------------
+ global_temporary_table | temp_table_test_systemview      |        0 |         0 |             0
+ global_temporary_table | temp_table_test_systemview_pkey |        1 |         0 |             0
+(2 rows)
+
+-- should empty
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+analyze temp_table_test_systemview;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+       schemaname       |            tablename            | relpages | reltuples | relallvisible 
+------------------------+---------------------------------+----------+-----------+---------------
+ global_temporary_table | temp_table_test_systemview      |       55 |     10000 |             0
+ global_temporary_table | temp_table_test_systemview_pkey |       30 |     10000 |             0
+(2 rows)
+
+-- get data after analyze;
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+       schemaname       |         tablename          | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                                                                                                                                                                                                                                                histogram_bounds                                                                                                                                                                                                                                                 | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------------------+----------------------------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ global_temporary_table | temp_table_test_systemview | a       | f         |         0 |         4 |         -1 |                  |                   | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} |           1 |                   |                        | 
+ global_temporary_table | temp_table_test_systemview | b       | f         |         0 |         5 |          1 | {test}           | {1}               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |           1 |                   |                        | 
+(2 rows)
+
+-- get all object info in current schema
+select relname ,relkind, relpersistence, reloptions from pg_class c, pg_namespace n where c.relnamespace = n.oid and n.nspname = 'global_temporary_table' order by relkind,relpersistence,relname;
+                     relname                     | relkind | relpersistence |          reloptions           
+-------------------------------------------------+---------+----------------+-------------------------------
+ global_temp_partition_01_2021_id_seq            | S       | g              | {on_commit_delete_rows=false}
+ global_temp_with_serial_a_seq                   | S       | g              | {on_commit_delete_rows=false}
+ regular_partition01_id_seq                      | S       | p              | 
+ regular_partition_01_2019_id_seq                | S       | p              | 
+ seq_1                                           | S       | p              | 
+ gtt_idx_1                                       | i       | g              | 
+ gtt_idx_2                                       | i       | g              | 
+ gtt_idx_3                                       | i       | g              | 
+ gtt_on_commit_default_pkey                      | i       | g              | 
+ gtt_on_commit_delete_pkey                       | i       | g              | 
+ gtt_on_commit_preserve_pkey                     | i       | g              | 
+ gtt_test_alter1_pkey                            | i       | g              | 
+ gtt_test_rename_pkey                            | i       | g              | 
+ idx_b                                           | i       | g              | 
+ idx_gtt_t_kenyon_1                              | i       | g              | 
+ idx_gtt_t_kenyon_2                              | i       | g              | 
+ idx_gtt_test_alter_b                            | i       | g              | 
+ idx_test_1                                      | i       | g              | 
+ idx_test_2                                      | i       | g              | 
+ products_pkey                                   | i       | g              | 
+ temp_orders_2_pkey                              | i       | g              | 
+ temp_orders_pkey                                | i       | g              | 
+ temp_products_pkey                              | i       | g              | 
+ temp_table_test_systemview_pkey                 | i       | g              | 
+ temp_table_with_sequence_oncommit_delete_pkey   | i       | g              | 
+ temp_table_with_sequence_oncommit_preserve_pkey | i       | g              | 
+ regular_partition01                             | p       | p              | 
+ global_temp_partition_01_2021                   | r       | g              | {on_commit_delete_rows=true}
+ global_temp_with_serial                         | r       | g              | {on_commit_delete_rows=false}
+ gtt_copytest                                    | r       | g              | {on_commit_delete_rows=false}
+ gtt_on_commit_default                           | r       | g              | {on_commit_delete_rows=false}
+ gtt_on_commit_delete                            | r       | g              | {on_commit_delete_rows=true}
+ gtt_on_commit_delete2                           | r       | g              | {on_commit_delete_rows=true}
+ gtt_on_commit_preserve                          | r       | g              | {on_commit_delete_rows=false}
+ gtt_t_kenyon                                    | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_alter                                  | r       | g              | {on_commit_delete_rows=true}
+ gtt_test_alter1                                 | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_createindex                            | r       | g              | {on_commit_delete_rows=false}
+ gtt_test_new                                    | r       | g              | {on_commit_delete_rows=false}
+ inherits_parent_global_temp                     | r       | g              | {on_commit_delete_rows=true}
+ products                                        | r       | g              | {on_commit_delete_rows=false}
+ temp_orders                                     | r       | g              | {on_commit_delete_rows=true}
+ temp_orders_2                                   | r       | g              | {on_commit_delete_rows=false}
+ temp_products                                   | r       | g              | {on_commit_delete_rows=false}
+ temp_table_test_statistics                      | r       | g              | {on_commit_delete_rows=false}
+ temp_table_test_systemview                      | r       | g              | {on_commit_delete_rows=false}
+ temp_table_with_sequence_oncommit_delete        | r       | g              | {on_commit_delete_rows=true}
+ temp_table_with_sequence_oncommit_preserve      | r       | g              | {on_commit_delete_rows=false}
+ test_create_table_as                            | r       | g              | {on_commit_delete_rows=false}
+ foo                                             | r       | p              | 
+ inherits_parent                                 | r       | p              | 
+ regular_partition_01_2019                       | r       | p              | 
+(52 rows)
+
+reset search_path;
+drop schema global_temporary_table cascade;
+NOTICE:  drop cascades to 27 other objects
+DETAIL:  drop cascades to table global_temporary_table.gtt_on_commit_default
+drop cascades to table global_temporary_table.gtt_on_commit_delete
+drop cascades to table global_temporary_table.gtt_on_commit_delete2
+drop cascades to table global_temporary_table.gtt_on_commit_preserve
+drop cascades to table global_temporary_table.gtt_test_new
+drop cascades to table global_temporary_table.gtt_test_createindex
+drop cascades to table global_temporary_table.gtt_test_alter
+drop cascades to table global_temporary_table.regular_partition_01_2019
+drop cascades to table global_temporary_table.regular_partition01
+drop cascades to table global_temporary_table.global_temp_partition_01_2021
+drop cascades to table global_temporary_table.inherits_parent
+drop cascades to table global_temporary_table.inherits_parent_global_temp
+drop cascades to table global_temporary_table.gtt_test_alter1
+drop cascades to table global_temporary_table.foo
+drop cascades to table global_temporary_table.test_create_table_as
+drop cascades to table global_temporary_table.gtt_copytest
+drop cascades to table global_temporary_table.temp_products
+drop cascades to table global_temporary_table.products
+drop cascades to table global_temporary_table.temp_orders
+drop cascades to table global_temporary_table.temp_orders_2
+drop cascades to table global_temporary_table.global_temp_with_serial
+drop cascades to sequence global_temporary_table.seq_1
+drop cascades to table global_temporary_table.temp_table_with_sequence_oncommit_delete
+drop cascades to table global_temporary_table.temp_table_with_sequence_oncommit_preserve
+drop cascades to table global_temporary_table.temp_table_test_statistics
+drop cascades to table global_temporary_table.gtt_t_kenyon
+drop cascades to table global_temporary_table.temp_table_test_systemview
+-- should empty
+select * from pg_list_gtt_relfrozenxids();
+ pid | relfrozenxid 
+-----+--------------
+(0 rows)
+
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 1420288d67b..faa70c947fa 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1337,6 +1337,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relid,
+    s.pid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.relfilenode,
+    s.relpages,
+    s.reltuples,
+    s.relallvisible,
+    s.relfrozenxid,
+    s.relminmxid
+   FROM (pg_class c
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stavalues1
+            WHEN (s.stakind2 = 1) THEN s.stavalues2
+            WHEN (s.stakind3 = 1) THEN s.stavalues3
+            WHEN (s.stakind4 = 1) THEN s.stavalues4
+            WHEN (s.stakind5 = 1) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_vals,
+        CASE
+            WHEN (s.stakind1 = 1) THEN s.stanumbers1
+            WHEN (s.stakind2 = 1) THEN s.stanumbers2
+            WHEN (s.stakind3 = 1) THEN s.stanumbers3
+            WHEN (s.stakind4 = 1) THEN s.stanumbers4
+            WHEN (s.stakind5 = 1) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_freqs,
+        CASE
+            WHEN (s.stakind1 = 2) THEN s.stavalues1
+            WHEN (s.stakind2 = 2) THEN s.stavalues2
+            WHEN (s.stakind3 = 2) THEN s.stavalues3
+            WHEN (s.stakind4 = 2) THEN s.stavalues4
+            WHEN (s.stakind5 = 2) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS histogram_bounds,
+        CASE
+            WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+            WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+            WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+            WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+            WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+            ELSE NULL::real
+        END AS correlation,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stavalues1
+            WHEN (s.stakind2 = 4) THEN s.stavalues2
+            WHEN (s.stakind3 = 4) THEN s.stavalues3
+            WHEN (s.stakind4 = 4) THEN s.stavalues4
+            WHEN (s.stakind5 = 4) THEN s.stavalues5
+            ELSE NULL::text[]
+        END AS most_common_elems,
+        CASE
+            WHEN (s.stakind1 = 4) THEN s.stanumbers1
+            WHEN (s.stakind2 = 4) THEN s.stanumbers2
+            WHEN (s.stakind3 = 4) THEN s.stanumbers3
+            WHEN (s.stakind4 = 4) THEN s.stanumbers4
+            WHEN (s.stakind5 = 4) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN (s.stakind1 = 5) THEN s.stanumbers1
+            WHEN (s.stakind2 = 5) THEN s.stanumbers2
+            WHEN (s.stakind3 = 5) THEN s.stanumbers3
+            WHEN (s.stakind4 = 5) THEN s.stanumbers4
+            WHEN (s.stakind5 = 5) THEN s.stanumbers5
+            ELSE NULL::real[]
+        END AS elem_count_histogram
+   FROM ((pg_class c
+     JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+     LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+    LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+  WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
 pg_hba_file_rules| SELECT a.line_number,
     a.type,
     a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 6d8f524ae9e..7924bc02f59 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -135,3 +135,6 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: global_temporary_table
diff --git a/src/test/regress/sql/global_temporary_table.sql b/src/test/regress/sql/global_temporary_table.sql
new file mode 100644
index 00000000000..51cd9bbbc0c
--- /dev/null
+++ b/src/test/regress/sql/global_temporary_table.sql
@@ -0,0 +1,315 @@
+--
+-- GLobal emparary table test case 
+--
+
+CREATE SCHEMA IF NOT EXISTS global_temporary_table;
+set search_path=global_temporary_table,sys;
+
+--
+--  test create global temp table basic syntax
+--
+create global temp table gtt_on_commit_default(a int primary key, b text);
+create global temp table gtt_on_commit_delete(a int primary key, b text) on commit delete rows;
+create global temp table gtt_on_commit_delete2(n int) with (on_commit_delete_rows='true');
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit PRESERVE rows;
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt_test_createindex(c0 tsvector,c1 varchar(100), c2 int);
+create global temp table gtt_test_alter(b text) with(on_commit_delete_rows=true);
+--
+-- test DML on global temp table
+--
+
+-- update empty temp table
+update gtt_on_commit_delete set b ='test';
+begin;
+insert into gtt_on_commit_delete values (1);
+update gtt_on_commit_delete set b ='test';
+-- should 1 row
+select * from gtt_on_commit_delete;
+commit;
+-- data delete after transaction commit
+-- should 0 row
+select * from gtt_on_commit_delete;
+
+-- update empty temp table
+update gtt_on_commit_preserve set b ='test';
+insert into gtt_on_commit_preserve values (2);
+begin;
+insert into gtt_on_commit_preserve values (3);
+update gtt_on_commit_preserve set b ='test';
+-- should 2 row
+select * from gtt_on_commit_preserve order by a;
+delete from gtt_on_commit_preserve where a=2;
+commit;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+
+begin;
+insert into gtt_on_commit_preserve values (4);
+-- temp table support truncate;
+truncate gtt_on_commit_preserve;
+select * from gtt_on_commit_preserve order by a;
+rollback;
+-- should 1 row
+select * from gtt_on_commit_preserve order by a;
+
+--
+-- test unsupported global temp partition table
+--
+
+-- should fail
+CREATE global temp TABLE global_temp_partition_01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time)
+on commit delete rows;
+
+CREATE TABLE regular_partition_01_2019 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+);
+
+CREATE TABLE regular_partition01 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+) PARTITION BY RANGE (cre_time);
+
+-- should fail
+CREATE global temp TABLE temp_partition01_2018
+PARTITION OF regular_partition01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE global_temp_partition_01_2021 (
+id        bigserial NOT NULL,
+cre_time  timestamp without time zone,
+note      varchar(30)
+)on commit delete rows;
+
+-- should fail
+ALTER TABLE regular_partition01 ATTACH PARTITION global_temp_partition_01_2021 FOR VALUES FROM ('2021-01-01 00:00:00') TO ('2022-01-01 00:00:00');
+
+--
+-- test unsupported inherit table
+--
+create table 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 inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent);
+-- should fail
+create global temp table temp_inherit() inherits (inherits_parent_global_temp) on commit delete rows;
+
+--
+-- test DDL on global temp table
+--
+create index idx_gtt_test_alter_b on gtt_test_alter (b);
+insert into gtt_test_alter values('test');
+alter table gtt_test_alter alter b type varchar;
+create index gtt_idx_1 on gtt_test_createindex using gin (c0);
+create index gtt_idx_2 on gtt_test_createindex using gist (c0);
+create index gtt_idx_3 on gtt_test_createindex using hash (c2);
+alter table gtt_test_rename rename to gtt_test_new;
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+create index CONCURRENTLY idx_b on gtt_on_commit_default (b);
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+insert into gtt_on_commit_default values(1,'test');
+create global temp table gtt_test_alter1 (a int primary key,b text);
+alter table gtt_test_alter1 alter a type varchar;
+-- should fail
+alter table gtt_on_commit_default alter a type varchar;
+-- should fail
+cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) on commit delete rows;
+-- should fail
+REINDEX (TABLESPACE pg_default) TABLE gtt_test_createindex;
+-- should fail
+REINDEX (TABLESPACE pg_default) INDEX gtt_idx_1;
+-- should fail
+alter table gtt_on_commit_default set ( on_commit_delete_rows='true');
+-- should fail
+create table gtt_on_commit_default(a int primary key, b text) with(on_commit_delete_rows=true);
+-- should fail
+create or replace global temp view gtt_v as select 5;
+create table foo();
+-- should fail
+alter table foo set (on_commit_delete_rows='true');
+-- should fail
+create global temp table gtt_on_commit_preserve(a int primary key, b text) on commit drop;
+-- should fail
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+-- should fail
+CREATE MATERIALIZED VIEW mv_gtt_on_commit_default as select * from gtt_on_commit_default;
+-- test create table as select
+CREATE GLOBAL TEMPORARY TABLE test_create_table_as AS SELECT 1 AS a;
+-- test copy stmt
+create global temp table gtt_copytest (
+        c1 int,
+        "col with , comma" text,
+        "col with "" quote"  int);
+
+copy gtt_copytest from stdin csv header;
+this is just a line full of junk that would error out if parsed
+1,a,1
+2,b,2
+\.
+select count(*) from gtt_copytest;
+
+--
+-- test foreign key dependencies for global temp table
+--
+CREATE global temp TABLE temp_products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+CREATE global temp TABLE temp_orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+)on commit delete rows;
+
+-- should fail
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES temp_products(product_no),
+    quantity integer
+);
+
+CREATE global temp TABLE temp_orders_2 (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products(product_no),
+    quantity integer
+);
+
+--should fail
+insert into temp_orders values(1,1,1);
+
+insert into temp_products values(1,'test',1.0);
+begin;
+insert into temp_orders values(1,1,1);
+commit;
+-- should 1 row
+select count(*) from temp_products;
+-- should 0 row
+select count(*) from temp_orders;
+
+--
+-- test sequence on global temp table
+--
+create global temp table global_temp_with_serial (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+-- support insert data to temp table in read only transaction
+insert into global_temp_with_serial (b) values(1);
+select * from global_temp_with_serial;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_delete(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE temp_table_with_sequence_oncommit_preserve(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table temp_table_with_sequence_oncommit_delete add c2 int default nextval('seq_1');
+alter table temp_table_with_sequence_oncommit_preserve add c2 int default nextval('seq_1');
+begin;
+insert into temp_table_with_sequence_oncommit_delete (c1)values(1);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(2);
+insert into temp_table_with_sequence_oncommit_delete (c1)values(3);
+insert into temp_table_with_sequence_oncommit_preserve (c1)values(4);
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+commit;
+-- should 0 row
+select * from temp_table_with_sequence_oncommit_delete order by c1;
+-- should 2 row
+select * from temp_table_with_sequence_oncommit_preserve order by c1;
+
+--
+-- test statistics on temp table
+--
+create global temp table temp_table_test_statistics(a int);
+insert into temp_table_test_statistics values(generate_series(1,100000));
+create index idx_test_1 on temp_table_test_statistics (a);
+create index idx_test_2 on temp_table_test_statistics((a*10));
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+-- test statistic for whole row
+explain (costs off) select count(*) from temp_table_test_statistics group by temp_table_test_statistics;
+-- test statistic for system column
+explain (costs off) select count(*) from temp_table_test_statistics group by tableoid;
+analyze temp_table_test_statistics;
+-- indexscan by idx_test_1
+explain (costs off) select * from temp_table_test_statistics where a=200000;
+-- indexscan by idx_test_2
+explain (costs off) select * from temp_table_test_statistics where a*10=3;
+explain (costs off) select count(*) from temp_table_test_statistics group by temp_table_test_statistics;
+explain (costs off) select count(*) from temp_table_test_statistics group by tableoid;
+
+--
+-- test temp table with toast table
+--
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,10),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',10);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+rollback;
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname like '%t_kenyon%' order by relname;
+
+-- test analyze/vacuum on global temp table
+ANALYZE gtt_t_kenyon;
+VACUUM gtt_t_kenyon;
+
+--
+-- test global temp table system view
+--
+create global temp table temp_table_test_systemview(a int primary key, b text) on commit PRESERVE rows;
+-- should empty, storage not initialized
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+-- should empty, storage not initialized
+select count(*) from pg_list_gtt_relfrozenxids();
+insert into temp_table_test_systemview values(generate_series(1,10000),'test');
+select tablename from pg_gtt_attached_pids where tablename = 'temp_table_test_systemview';
+select count(*) from pg_list_gtt_relfrozenxids();
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+-- should empty
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+analyze temp_table_test_systemview;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where tablename in('temp_table_test_systemview','temp_table_test_systemview_pkey') order by tablename;
+-- get data after analyze;
+select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
+
+-- get all object info in current schema
+select relname ,relkind, relpersistence, reloptions from pg_class c, pg_namespace n where c.relnamespace = n.oid and n.nspname = 'global_temporary_table' order by relkind,relpersistence,relname;
+
+reset search_path;
+drop schema global_temporary_table cascade;
+-- should empty
+select * from pg_list_gtt_relfrozenxids();
+
-- 
2.32.0 (Apple Git-132)

0001-gtt-v67-reademe.patchapplication/octet-stream; name=0001-gtt-v67-reademe.patchDownload
diff --git a/README.gtt.txt b/README.gtt.txt
new file mode 100644
index 00000000000..d181df9acd7
--- /dev/null
+++ b/README.gtt.txt
@@ -0,0 +1,172 @@
+Global Temporary Table(GTT)
+=========================================
+
+Feature description
+-----------------------------------------
+
+Previously, temporary tables are defined once and automatically
+exist (starting with empty contents) in every session before using them.
+
+The temporary table implementation in PostgreSQL, known as Local temp tables(LTT),
+did not fully comply with the SQL standard. This version added the support of
+Global Temporary Table .
+
+The metadata of Global Temporary Table is persistent and shared among sessions.
+The data stored in the Global temporary table is independent of sessions. This
+means, when a session creates a Global Temporary Table and writes some data.
+Other sessions cannot see those data, but they have an empty Global Temporary
+Table with same schema.
+
+Like local temporary table, Global Temporary Table supports ON COMMIT PRESERVE ROWS
+or ON COMMIT DELETE ROWS clause, so that data in the temporary table can be
+cleaned up or preserved automatically when a session exits or a transaction COMMITs.
+
+Unlike Local Temporary Table, Global Temporary Table does not support
+ON COMMIT DROP clauses.
+
+In following paragraphs, we use GTT for Global Temporary Table and LTT for
+local temporary table.
+
+Main design ideas
+-----------------------------------------
+In general, GTT and LTT use the same storage and buffer design and
+implementation. The storage files for both types of temporary tables are named
+as t_backendid_relfilenode, and the local buffer is used to cache the data.
+
+The schema of GTTs is shared among sessions while their data are not. We build
+a new mechanisms to manage those non-shared data and their statistics.
+Here is the summary of changes:
+
+1) CATALOG
+GTTs store session-specific data. The storage information of GTTs'data, their
+transaction information, and their statistics are not stored in the catalog.
+
+2) STORAGE INFO & STATISTICS INFO & TRANSACTION INFO
+In order to maintain durability and availability of GTTs'session-specific data,
+their storage information, statistics, and transaction information is managed
+in a local hash table tt_storage_local_hash.
+
+3) DDL
+Currently, GTT supports almost all table'DDL except CLUSTER/VACUUM FULL.
+Part of the DDL behavior is limited by shared definitions and multiple copies of
+local data, and we added some structures to handle this.
+
+A shared hash table active_gtt_shared_hash is added to track the state of the
+GTT in a different session. This information is recorded in the hash table
+during the DDL execution of the GTT.
+
+The data stored in a GTT can only be modified or accessed by owning session.
+The statements that only modify data in a GTT do not need a high level of
+table locking. The operations making those changes include truncate GTT,
+reindex GTT, and lock GTT.
+
+4) MVCC commit log(clog) cleanup
+Each GTT in a session has its own piece of data, and they have their own
+transaction information. We set up data structures to track and maintain
+this information. The cleaning of CLOGs also needs to consider the transaction
+information of GTT.
+
+Detailed design
+-----------------------------------------
+
+1. CATALOG
+1.1 relpersistence
+define RELPERSISTENCEGLOBALTEMP 'g'
+Mark Global Temporary Table in pg_class relpersistence to 'g'. The relpersistence
+of indexes created on the GTT, sequences on GTT and toast tables on GTT are
+also set to 'g'
+
+1.2 on commit clause
+LTT's status associated with on commit DELETE ROWS and on commit PRESERVE ROWS
+is not stored in catalog. Instead, GTTs need a bool value on_commit_delete_rows
+in reloptions which is shared among sessions.
+
+1.3 gram.y
+GTT is already supported in syntax tree. We remove the warning message
+"GLOBAL is deprecated in temporary table creation" and mark
+relpersistence = RELPERSISTENCEGLOBALTEMP.
+
+2. STORAGE INFO & STATISTICS INFO & TRANSACTION INFO
+2.1. gtt_storage_local_hash
+Each backend creates a local hashtable gtt_storage_local_hash to track a GTT's
+storage file information, statistics, and transaction information.
+
+2.2 GTT storage file info track
+1) When one session inserts data into a GTT for the first time, record the
+storage info to gtt_storage_local_hash.
+2) Use beforeshmemexit to ensure that all files of session GTT are deleted when
+the session exits.
+
+2.3 statistics info
+1) relpages reltuples relallvisible relfilenode
+2) The statistics of each column from pg_statistic
+All the above information is stored in gtt_storage_local_hash.
+When doing vacuum or analyze, GTT's statistic is updated, which is used by
+the SQL planner.
+The statistics summarizes only data in the current session.
+
+2.3 transaction info track
+frozenxid minmulti from pg_class is stored to gtt_storage_local_hash.
+
+3 DDL
+3.1. active_gtt_shared_hash
+This is the hash table created in shared memory to trace the GTT files initialized
+in each session. Each hash entry contains a bitmap that records the backendid of
+the initialized GTT file. With this hash table, we know which backend/session
+is using this GTT. Such information is used during GTT's DDL operations.
+
+3.2 DROP GTT
+One GTT is allowed to be deleted when there is only one session using the table
+and the session is the current session. After holding the lock on GTT,
+active_gtt_shared_hash is checked to ensure that.
+
+3.3 ALTER GTT/DROP INDEX ON GTT
+Same as drop GTT.
+
+3.4 CREATE INDEX ON GTT
+1) create index on GTT statements build index based on local data in a session.
+2) After the index is created, record the index metadata to the catalog.
+3) Other sessions can enable or disable the local GTT index.
+
+3.5 TRUNCATE/REINDEX GTT
+The SQL truncate/reindex command open the GTT using AccessShareLock lock,
+not AccessExclusiveLock, because this command only cleans up local data and
+local buffers in current session. This allows these operations to be executed
+concurrently between sessions, unlike normal tables.
+
+3.6 LOCK GTT
+A lock GTT statement does not hold any relation lock.
+
+3.7 CLUSTER GTT/VACUUM FULL GTT
+The current version does not support.
+
+4 MVCC commit log(clog) cleanup
+
+The GTT storage file contains transaction information. Queries for GTT data rely
+on transaction information such as clog. The transaction information required by
+each session may be completely different. We need to ensure that the transaction
+information of the GTT data is not cleaned up during its lifetime and that
+transaction resources are recycled at the instance level.
+
+4.1 The session level GTT oldest frozenxid
+1) To manage all GTT transaction information, add session level oldest frozenxid
+in each session. When one GTT is created or removed, record the session level
+oldest frozenxid and store it in MyProc.
+2) When vacuum advances the database's frozenxid, session level oldest frozenxid
+should be considered. This is acquired by searching all of MyProc. This way,
+we can avoid the clog required by GTTs to be cleaned.
+
+4.2 vacuum GTT
+Users can perform vacuum over a GTT to clean up local data in the GTT.
+
+4.3 autovacuum GTT
+Autovacuum skips all GTTs, because the data in GTTs is only visible in current session.
+
+5 OTHERS
+5.1 Parallel query
+Planner does not produce parallel query plans for SQL related to GTT. Because
+GTT private data cannot be accessed across processes.
+
+5.2 WAL and Logical replication
+Like LTT, the DML on GTT does not record WAL and is not parsed or replay by
+the logical replication.
\ No newline at end of file
-- 
2.30.1 (Apple Git-130)

0003-gtt-v67-implementation.patchapplication/octet-stream; name=0003-gtt-v67-implementation.patchDownload
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index f996f9a5727..18f1f7a42c0 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -18,6 +18,7 @@
 #include "access/toast_internals.h"
 #include "access/visibilitymap.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "funcapi.h"
 #include "miscadmin.h"
 #include "storage/bufmgr.h"
@@ -340,6 +341,13 @@ verify_heapam(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(ctx.rel) &&
+		!gtt_storage_attached(RelationGetRelid(ctx.rel)))
+	{
+		relation_close(ctx.rel, AccessShareLock);
+		PG_RETURN_NULL();
+	}
+
 	/* Early exit if the relation is empty */
 	nblocks = RelationGetNumberOfBlocks(ctx.rel);
 	if (!nblocks)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index d592655258a..051fe802824 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -159,6 +159,18 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * For global temporary table save on commit clause info to reloptions.
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temporary table on commit options",
+			RELOPT_KIND_HEAP,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},
 	/* list terminator */
 	{{NULL}}
 };
@@ -1834,6 +1846,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 d4bf0c7563d..b623e958c38 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1024,7 +1024,7 @@ gistproperty(Oid index_oid, int attno,
 XLogRecPtr
 gistGetFakeLSN(Relation rel)
 {
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	if (RELATION_IS_TEMP(rel))
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index a259a301fa8..6a85ec9debe 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -153,7 +153,7 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	if (!RELATION_IS_TEMP(index))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 2240cfd936d..2ee21752e02 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -5888,6 +5888,7 @@ heap_abort_speculative(Relation relation, ItemPointer tid)
 	Buffer		buffer;
 	TransactionId prune_xid;
 
+	Assert(TransactionIdIsNormal(relation->rd_rel->relfrozenxid));
 	Assert(ItemPointerIsValid(tid));
 
 	block = ItemPointerGetBlockNumber(tid);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 39ef8a0b77d..0bb308d1c5e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -593,7 +593,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
@@ -645,7 +645,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(RelationGetSmgr(rel), dstrel, MAIN_FORKNUM,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 242511a235f..c2eebe52c18 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -333,6 +333,9 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
 	TransactionId FreezeLimit;
 	MultiXactId MultiXactCutoff;
 
+	Assert(TransactionIdIsNormal(rel->rd_rel->relfrozenxid));
+        Assert(MultiXactIdIsValid(rel->rd_rel->relminmxid));
+
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (IsAutoVacuumWorkerProcess() &&
 							  params->log_min_duration >= 0));
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 6b5f01e1d07..099f942c936 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -677,6 +678,14 @@ _bt_getrootheight(Relation rel)
 	{
 		Buffer		metabuf;
 
+		/*
+		 * If a global temporary table storage file is not initialized in the
+		 * this session, its index does not have a root page, just returns 0.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
 		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
 		metad = _bt_getmeta(rel, metabuf);
 
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index eefebb7bb83..97bec1e0e98 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -44,6 +44,7 @@ OBJS = \
 	pg_subscription.o \
 	pg_type.o \
 	storage.o \
+	storage_gtt.o \
 	toasting.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index dfd5fb669ee..c0fd68652f7 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -504,6 +504,7 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 7e99de88b34..049eb621bdd 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -62,6 +62,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"
@@ -102,6 +103,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -349,8 +351,21 @@ heap_create(const char *relname,
 	if (!RELKIND_HAS_TABLESPACE(relkind))
 		reltablespace = InvalidOid;
 
+	/*
+	 * For create global temporary table, initialization storage information
+	 * and recorded in into pg_class, but not initialization stroage file.
+	 * When data is inserted into a temporary table, its storage file is initialized.
+	 */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		create_storage = false;
+		if (OidIsValid(relfilenode))
+			elog(ERROR, "global temporary table can not reuse an existing relfilenode");
+
+		relfilenode = relid;
+	}
 	/* Don't create storage for relkinds without physical storage. */
-	if (!RELKIND_HAS_STORAGE(relkind))
+	else if (!RELKIND_HAS_STORAGE(relkind))
 		create_storage = false;
 	else
 	{
@@ -403,7 +418,7 @@ heap_create(const char *relname,
 											relpersistence,
 											relfrozenxid, relminmxid);
 		else if (RELKIND_HAS_STORAGE(rel->rd_rel->relkind))
-			RelationCreateStorage(rel->rd_node, relpersistence);
+			RelationCreateStorage(rel->rd_node, relpersistence, rel);
 		else
 			Assert(false);
 	}
@@ -970,6 +985,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -995,8 +1011,21 @@ AddNewRelationTuple(Relation pg_class_desc,
 		new_rel_reltup->reltuples = 1;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/*
+	 * The transaction information of the global temporary table is stored
+	 * in hash table, not in pg_class.
+	 */
+	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;
@@ -1404,6 +1433,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1486,10 +1516,13 @@ 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
+	 * For local temporary table, if there's a special on-commit action, remember it.
+	 * For global temporary table, on-commit action is recorded during initial storage.
+	 * See remember_gtt_storage_info.
 	 */
-	if (oncommit != ONCOMMIT_NOOP)
-		register_on_commit_action(relid, oncommit);
+	if (oncommit != ONCOMMIT_NOOP &&
+		relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+		register_on_commit_action(relid, oncommit, false);
 
 	/*
 	 * ok, the relation has been cataloged, so close our relations and return
@@ -1986,6 +2019,13 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/*
+	 * Only when other sessions are not using this global temporary table,
+	 * is it allowed to drop it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		CheckGlobalTempTableNotInUse(rel, "DROP GLOBAL TEMPORARY TABLE");
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3272,7 +3312,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  * the specified relation.  Caller must hold exclusive lock on rel.
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3284,7 +3324,7 @@ RelationTruncateIndexes(Relation heapRelation)
 		IndexInfo  *indexInfo;
 
 		/* Open the index relation; use exclusive lock, just to be sure */
-		currentIndex = index_open(indexId, AccessExclusiveLock);
+		currentIndex = index_open(indexId, lockmode);
 
 		/*
 		 * Fetch info needed for index_build.  Since we know there are no
@@ -3320,31 +3360,49 @@ RelationTruncateIndexes(Relation heapRelation)
  * ON COMMIT truncation of temporary tables, where it doesn't matter.
  */
 void
-heap_truncate(List *relids)
+heap_truncate(List *relids, bool is_global_temp)
 {
 	List	   *relations = NIL;
-	ListCell   *cell;
+	List	   *lock_modes = NIL;
+	ListCell   *cell_rel;
+	ListCell   *cell_lock;
 
 	/* Open relations for processing, and grab exclusive access on each */
-	foreach(cell, relids)
+	foreach(cell_rel, relids)
 	{
-		Oid			rid = lfirst_oid(cell);
+		Oid			rid = lfirst_oid(cell_rel);
 		Relation	rel;
+		LOCKMODE	lockmode;
+
+		/*
+		 * Truncate global temporary table only clears local data,
+		 * so only low-level locks need to be held.
+		 */
+		if (is_global_temp)
+		{
+			lockmode = RowExclusiveLock;
+			if (!gtt_storage_attached(rid))
+				continue;
+		}
+		else
+			lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
+		lock_modes = lappend_int(lock_modes, lockmode);
 	}
 
 	/* Don't allow truncate on tables that are referenced by foreign keys */
 	heap_truncate_check_FKs(relations, true);
 
 	/* OK to do it */
-	foreach(cell, relations)
+	forboth(cell_rel, relations, cell_lock, lock_modes)
 	{
-		Relation	rel = lfirst(cell);
+		Relation	rel = lfirst(cell_rel);
+		LOCKMODE	lockmode = lfirst_int(cell_lock);
 
 		/* Truncate the relation */
-		heap_truncate_one_rel(rel);
+		heap_truncate_one_rel(rel, lockmode);
 
 		/* Close the relation, but keep exclusive lock on it until commit */
 		table_close(rel, NoLock);
@@ -3361,7 +3419,7 @@ heap_truncate(List *relids)
  * checked permissions etc, and must have obtained AccessExclusiveLock.
  */
 void
-heap_truncate_one_rel(Relation rel)
+heap_truncate_one_rel(Relation rel, LOCKMODE lockmode)
 {
 	Oid			toastrelid;
 
@@ -3374,18 +3432,25 @@ heap_truncate_one_rel(Relation rel)
 
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		gtt_update_relstats(rel, 0, 0, 0,
+							RecentXmin,GetOldestMultiXactId());
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
 	toastrelid = rel->rd_rel->reltoastrelid;
 	if (OidIsValid(toastrelid))
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		if (RELATION_IS_GLOBAL_TEMP(toastrel))
+			gtt_update_relstats(toastrel, 0, 0, 0,
+								RecentXmin, GetOldestMultiXactId());
+
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
@@ -3856,3 +3921,15 @@ StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
 
 	CacheInvalidateRelcache(parent);
 }
+
+void
+CheckGlobalTempTableNotInUse(Relation rel, const char *stmt)
+{
+	if (RELATION_IS_GLOBAL_TEMP(rel) &&
+		is_other_backend_use_gtt(RelationGetRelid(rel)))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_IN_USE),
+				 errmsg("cannot %s \"%s\" because it is being used by other session",
+						stmt, RelationGetRelationName(rel)),
+				 errdetail("Please try detach all other sessions using this table and try again.")));
+}
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 5e3fc2b35dc..03900de5a00 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -54,6 +54,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"
@@ -125,7 +126,8 @@ static void UpdateIndexRelation(Oid indexoid, Oid heapoid,
 								bool isready);
 static void index_update_stats(Relation rel,
 							   bool hasindex,
-							   double reltuples);
+							   double reltuples,
+							   bool isreindex);
 static void IndexCheckExclusion(Relation heapRelation,
 								Relation indexRelation,
 								IndexInfo *indexInfo);
@@ -737,6 +739,21 @@ index_create(Relation heapRelation,
 	MultiXactId relminmxid;
 	bool		create_storage = !OidIsValid(relFileNode);
 
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		/* disable create index on global temporary table with concurrent mode */
+		concurrent = false;
+
+		/*
+		 * For the case that some backend is applied relcache message to create
+		 * an index on a global temporary table, if this table in the current
+		 * backend are not initialized, the creation of index storage on the
+		 * table are also skipped.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+			flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/* constraint flags can only be set when a constraint is requested */
 	Assert((constr_flags == 0) ||
 		   ((flags & INDEX_CREATE_ADD_CONSTRAINT) != 0));
@@ -1243,7 +1260,8 @@ index_create(Relation heapRelation,
 		 */
 		index_update_stats(heapRelation,
 						   true,
-						   -1.0);
+						   -1.0,
+						   false);
 		/* Make the above update visible */
 		CommandCounterIncrement();
 	}
@@ -2151,7 +2169,7 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 * lock (see comments in RemoveRelations), and a non-concurrent DROP is
 	 * more efficient.
 	 */
-	Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+	Assert(!RelpersistenceTsTemp(get_rel_persistence(indexId)) ||
 		   (!concurrent && !concurrent_lock_mode));
 
 	/*
@@ -2183,6 +2201,14 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/*
+	 * Allow to drop index on global temporary table when only current
+	 * backend use it.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation))
+		CheckGlobalTempTableNotInUse(userHeapRelation,
+									 "DROP INDEX ON GLOBAL TEMPORARY TABLE");
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2789,7 +2815,8 @@ FormIndexDatum(IndexInfo *indexInfo,
 static void
 index_update_stats(Relation rel,
 				   bool hasindex,
-				   double reltuples)
+				   double reltuples,
+				   bool isreindex)
 {
 	Oid			relid = RelationGetRelid(rel);
 	Relation	pg_class;
@@ -2797,6 +2824,13 @@ index_update_stats(Relation rel,
 	Form_pg_class rd_rel;
 	bool		dirty;
 
+	/*
+	 * Most of the global Temp table data is updated to the local hash, and reindex
+	 * does not refresh relcache, so call a separate function.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		return index_update_gtt_relstats(rel, hasindex, reltuples, isreindex);
+
 	/*
 	 * We always update the pg_class row using a non-transactional,
 	 * overwrite-in-place update.  There are several reasons for this:
@@ -3016,6 +3050,25 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, progress_index, progress_vals);
 	}
 
+	/* For build index on global temporary table */
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		/*
+		 * If the storage for the index in this session is not initialized,
+		 * it needs to be created.
+		 */
+		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+		{
+			/* Before create init storage, fix the local Relcache first */
+			gtt_index_force_enable(indexRelation);
+
+			Assert(gtt_storage_attached(RelationGetRelid(heapRelation)));
+
+			/* Init storage for index */
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+		}
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3098,11 +3151,13 @@ index_build(Relation heapRelation,
 	 */
 	index_update_stats(heapRelation,
 					   true,
-					   stats->heap_tuples);
+					   stats->heap_tuples,
+					   isreindex);
 
 	index_update_stats(indexRelation,
 					   false,
-					   stats->index_tuples);
+					   stats->index_tuples,
+					   isreindex);
 
 	/* Make the updated catalog row versions visible */
 	CommandCounterIncrement();
@@ -3557,6 +3612,8 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	PGRUsage	ru0;
 	bool		progress = ((params->options & REINDEXOPT_REPORT_PROGRESS) != 0);
 	bool		set_tablespace = false;
+	LOCKMODE	lockmode_on_heap = ShareLock;
+	LOCKMODE	lockmode_on_index = AccessExclusiveLock;
 
 	pg_rusage_init(&ru0);
 
@@ -3570,10 +3627,35 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	if (!OidIsValid(heapId))
 		return;
 
+	/*
+	 * For reindex on global temporary table, If the storage for the index
+	 * in current session is not initialized, nothing is done.
+	 */
+	if (persistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		if (OidIsValid(params->tablespaceOid))
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot change tablespace of global temporary table")));
+
+		if (!gtt_storage_attached(indexId))
+		{
+			/* Suppress use of the target index while rebuilding it */
+			SetReindexProcessing(heapId, indexId);
+			/* Re-allow use of target index */
+			ResetReindexProcessing();
+			return;
+		}
+
+		/* For global temp table reindex handles local data, using low-level locks */
+		lockmode_on_heap = AccessShareLock;
+		lockmode_on_index = AccessShareLock;
+	}
+
 	if ((params->options & REINDEXOPT_MISSING_OK) != 0)
-		heapRelation = try_table_open(heapId, ShareLock);
+		heapRelation = try_table_open(heapId, lockmode_on_heap);
 	else
-		heapRelation = table_open(heapId, ShareLock);
+		heapRelation = table_open(heapId, lockmode_on_heap);
 
 	/* if relation is gone, leave */
 	if (!heapRelation)
@@ -3599,7 +3681,7 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	 * Open the target index relation and get an exclusive lock on it, to
 	 * ensure that no one else is touching this particular index.
 	 */
-	iRel = index_open(indexId, AccessExclusiveLock);
+	iRel = index_open(indexId, lockmode_on_index);
 
 	if (progress)
 		pgstat_progress_update_param(PROGRESS_CREATEIDX_ACCESS_METHOD_OID,
@@ -3841,7 +3923,7 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
  * index rebuild.
  */
 bool
-reindex_relation(Oid relid, int flags, ReindexParams *params)
+reindex_relation(Oid relid, int flags, ReindexParams *params, LOCKMODE lockmode)
 {
 	Relation	rel;
 	Oid			toast_relid;
@@ -3857,9 +3939,9 @@ reindex_relation(Oid relid, int flags, ReindexParams *params)
 	 * should match ReindexTable().
 	 */
 	if ((params->options & REINDEXOPT_MISSING_OK) != 0)
-		rel = try_table_open(relid, ShareLock);
+		rel = try_table_open(relid, lockmode);
 	else
-		rel = table_open(relid, ShareLock);
+		rel = table_open(relid, lockmode);
 
 	/* if relation is gone, leave */
 	if (!rel)
@@ -3966,7 +4048,7 @@ reindex_relation(Oid relid, int flags, ReindexParams *params)
 
 		newparams.options &= ~(REINDEXOPT_MISSING_OK);
 		newparams.tablespaceOid = InvalidOid;
-		result |= reindex_relation(toast_relid, flags, &newparams);
+		result |= reindex_relation(toast_relid, flags, &newparams, lockmode);
 	}
 
 	return result;
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index fafb9349cce..34dfe7d430d 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -657,6 +657,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/* Do not allow create global temporary table in temporary schemas */
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temporary relation in temporary schema")));
+			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 9b8075536a7..733d89c0e8a 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,10 +27,12 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
 #include "utils/hsearch.h"
+#include "utils/inval.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
 
@@ -61,6 +63,7 @@ typedef struct PendingRelDelete
 {
 	RelFileNode relnode;		/* relation that may need to be deleted */
 	BackendId	backend;		/* InvalidBackendId if not a temp rel */
+	Oid			temprelOid;			/* InvalidOid if not a global temporary rel */
 	bool		atCommit;		/* T=delete at commit; F=delete at abort */
 	int			nestLevel;		/* xact nesting level of request */
 	struct PendingRelDelete *next;	/* linked-list link */
@@ -115,7 +118,7 @@ AddPendingSync(const RelFileNode *rnode)
  * transaction aborts later on, the storage will be destroyed.
  */
 SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
 {
 	PendingRelDelete *pending;
 	SMgrRelation srel;
@@ -126,7 +129,12 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/*
+		 * global temporary table and local temporary table use same
+		 * design on storage module.
+		 */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -154,6 +162,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rnode;
 	pending->backend = backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = false;	/* delete if abort */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
@@ -165,6 +174,30 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 		AddPendingSync(&rnode);
 	}
 
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		Assert(rel && RELATION_IS_GLOBAL_TEMP(rel));
+
+		/*
+		 * Remember the reloid of global temporary table, which is used for
+		 * transaction commit or rollback.
+		 * see smgrDoPendingDeletes.
+		 */
+		pending->temprelOid = RelationGetRelid(rel);
+
+		/* Remember global temporary table storage info to localhash */
+		remember_gtt_storage_info(rnode, rel);
+
+		/* Make cache invalid and set new relnode to local relcache. */
+		CacheInvalidateRelcache(rel);
+
+		/*
+		 * Make the pg_class row change or relation map change visible.  This will
+		 * cause the relcache entry to get updated, too.
+		 */
+		CommandCounterIncrement();
+	}
+
 	return srel;
 }
 
@@ -201,11 +234,20 @@ RelationDropStorage(Relation rel)
 		MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
 	pending->relnode = rel->rd_node;
 	pending->backend = rel->rd_backend;
+	pending->temprelOid = InvalidOid;
 	pending->atCommit = true;	/* delete if commit */
 	pending->nestLevel = GetCurrentTransactionNestLevel();
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/*
+	 * Remember the reloid of global temporary table, which is used for
+	 * transaction commit or rollback.
+	 * see smgrDoPendingDeletes.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		pending->temprelOid = RelationGetRelid(rel);
+
 	/*
 	 * NOTE: if the relation was created in this transaction, it will now be
 	 * present in the pending-delete list twice, once with atCommit true and
@@ -618,6 +660,7 @@ smgrDoPendingDeletes(bool isCommit)
 	int			nrels = 0,
 				maxrels = 0;
 	SMgrRelation *srels = NULL;
+	Oid			*reloids = NULL;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -647,14 +690,18 @@ smgrDoPendingDeletes(bool isCommit)
 				{
 					maxrels = 8;
 					srels = palloc(sizeof(SMgrRelation) * maxrels);
+					reloids = palloc(sizeof(Oid) * maxrels);
 				}
 				else if (maxrels <= nrels)
 				{
 					maxrels *= 2;
 					srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+					reloids = repalloc(reloids, sizeof(Oid) * maxrels);
 				}
 
-				srels[nrels++] = srel;
+				srels[nrels] = srel;
+				reloids[nrels] = pending->temprelOid;
+				nrels++;
 			}
 			/* must explicitly free the list entry */
 			pfree(pending);
@@ -664,12 +711,21 @@ smgrDoPendingDeletes(bool isCommit)
 
 	if (nrels > 0)
 	{
+		int	i;
+
 		smgrdounlinkall(srels, nrels, false);
 
-		for (int i = 0; i < nrels; i++)
+		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* free global temporary table info in localhash */
+			if (gtt_storage_attached(reloids[i]))
+				forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+		}
+
 		pfree(srels);
+		pfree(reloids);
 	}
 }
 
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 00000000000..31ddef288e2
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1651 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  The body implementation of Global temparary table.
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *	  See src/backend/catalog/GTT_README for Global temparary table's
+ *	  requirements and design.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/heapam.h"
+#include "access/multixact.h"
+#include "access/visibilitymap.h"
+#include "catalog/catalog.h"
+#include "catalog/index.h"
+#include "catalog/pg_statistic.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
+#include "commands/tablecmds.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "nodes/pg_list.h"
+#include "storage/bufmgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/sinvaladt.h"
+#include "utils/catcache.h"
+#include "utils/guc.h"
+#include "utils/inval.h"
+#include "utils/syscache.h"
+
+/*
+ * WORDNUM/BITNUM/BITMAPSET_SIZE copy from bitmapset.c, and use BITS_PER_BITMAPWORD
+ * and typedef bitmapword from nodes/bitmapset.h. GTT records the status of global
+ * temporary tables in each session using bitmaps, which are stored in shared memory.
+ */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_info_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+
+int	vacuum_gtt_defer_check_age = 0;
+
+/*
+ * Global temp table or senquence has separate state in each session,
+ * record in a shared memory bitmap.
+ * However, index and toast relation for global temp table is not,
+ * because they depend on tables and don't exist independently.
+ */
+#define GLOBAL_TEMP_RELKIND_STATE_IS_SHARED(relkind) \
+	((relkind) == RELKIND_RELATION || \
+	 (relkind) == RELKIND_SEQUENCE)
+
+/*
+ * The Global temporary table's shared hash table data structure
+ */
+typedef struct gtt_ctl_data
+{
+	LWLock		lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+	Oid			dbNode;
+	Oid			relNode;
+} gtt_fnode;
+
+/* record this global temporary table in which backends are being used */
+typedef struct
+{
+	gtt_fnode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+/*
+ * The Global temporary table's local hash table data structure
+ */
+/* Record the storage information and statistical information of the global temporary table */
+typedef struct
+{
+	Oid			relfilenode;	/* relation */
+	Oid			spcnode;		/* tablespace */
+
+	/* pg_class relation statistics */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+
+	/* pg_statistic column statistics */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_relfilenode;
+
+typedef struct
+{
+	Oid			relid;
+
+	List		*relfilenode_list;
+
+	char		relkind;
+	bool		on_commit_delete;
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid, char relkind);
+static void gtt_storage_checkout(Oid relid, bool isCommit, char relkind);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_free_statistics(gtt_relfilenode *rnode);
+static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, Oid spcnode, bool missing_ok);
+static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok);
+static Bitmapset *copy_active_gtt_bitmap(Oid relid);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+/*
+ * Calculate shared hash table entry size for GTT.
+ */
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+	int	wordnum;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	wordnum = WORDNUM(GetMaxBackends() + 1);
+
+	/* hash entry header size */
+	hash_entry_size = MAXALIGN(sizeof(gtt_shared_hash_entry));
+
+	/*
+	 * hash entry data size
+	 * this is a bitmap in shared memory, each backend have a bit.
+	 * ensure we have enough words to store the upper bit.
+	 */
+	hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+	return hash_entry_size;
+}
+
+/*
+ * Calculate shared hash table max size for GTT.
+ */
+Size
+active_gtt_shared_hash_size(void)
+{
+	Size	size = 0;
+	Size	hash_entry_size = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	/* shared hash header size */
+	size = MAXALIGN(sizeof(gtt_ctl_data));
+	/* hash entry size */
+	hash_entry_size = action_gtt_shared_hash_entry_size();
+	/* max size */
+	size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+	return size;
+}
+
+/*
+ * Initialization shared hash table for GTT.
+ */
+void
+active_gtt_shared_hash_init(void)
+{
+	HASHCTL info;
+	bool	found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	gtt_shared_ctl = ShmemInitStruct("gtt_shared_ctl",
+									 sizeof(gtt_ctl_data),
+									 &found);
+
+	if (!found)
+	{
+		LWLockRegisterTranche(LWTRANCHE_GLOBAL_TEMP_TABLE_CTL, "gtt_shared_ctl");
+		LWLockInitialize(&gtt_shared_ctl->lock, LWTRANCHE_GLOBAL_TEMP_TABLE_CTL);
+		gtt_shared_ctl->max_entry = max_active_gtt;
+		gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+	}
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(gtt_fnode);
+	info.entrysize = action_gtt_shared_hash_entry_size();
+	active_gtt_shared_hash = ShmemInitHash("active gtt shared hash",
+										   gtt_shared_ctl->max_entry,
+										   gtt_shared_ctl->max_entry,
+										   &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+/*
+ * Record GTT relid to shared hash table, which means that current session is using this GTT.
+ */
+static void
+gtt_storage_checkin(Oid relid, char relkind)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			found;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (!GLOBAL_TEMP_RELKIND_STATE_IS_SHARED(relkind))
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash, (void *)&(fnode), HASH_ENTER_NULL, &found);
+	if (!entry)
+	{
+		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_global_temporary_table.")));
+	}
+
+	if (!found)
+	{
+		int		wordnum;
+
+		/* init bitmap in shared memory */
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(GetMaxBackends() + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	/* record current backendid in shared bitmap */
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+/*
+ * Remove the GTT relid record from the shared hash table which means that current session is
+ * not use this GTT.
+ */
+static void
+gtt_storage_checkout(Oid relid, bool isCommit, char relkind)
+{
+	gtt_shared_hash_entry	*entry;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (!GLOBAL_TEMP_RELKIND_STATE_IS_SHARED(relkind))
+		return;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = hash_search(active_gtt_shared_hash, (void *) &(fnode), HASH_FIND, NULL);
+	if (!entry)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		if (isCommit)
+			elog(WARNING, "relid %u not exist in gtt shared hash when drop local storage", relid);
+
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= GetMaxBackends());
+
+	/* remove current backendid from shared bitmap */
+	bms_del_member(entry->map, MyBackendId);
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return;
+}
+
+/*
+ * Gets usage information for a GTT from shared hash table.
+ * The information is in the form of bitmap.
+ * Quickly copy the entire bitmap from shared memory and return it.
+ * that to avoid holding locks for a long time.
+ */
+static Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	Bitmapset		*map_copy = NULL;
+	gtt_fnode		fnode;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash, (void *) &(fnode), HASH_FIND, NULL);
+	if (entry)
+	{
+		Assert(entry->map);
+		/* copy the entire bitmap */
+		if (!bms_is_empty(entry->map))
+			map_copy = bms_copy(entry->map);
+	}
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return map_copy;
+}
+
+/*
+ * Check if there are other sessions using this GTT besides the current session.
+ */
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			in_use = false;
+	gtt_fnode		fnode;
+	int			num_use = 0;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	fnode.dbNode = MyDatabaseId;
+	fnode.relNode = relid;
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash, (void *) &(fnode), HASH_FIND, NULL);
+	if (!entry)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		return false;
+	}
+
+	Assert(entry->map);
+	Assert(MyBackendId >= 1 && MyBackendId <= GetMaxBackends());
+
+	/* how many backend are using this GTT */
+	num_use = bms_num_members(entry->map);
+	if (num_use == 0)
+		in_use = false;
+	else if (num_use == 1)
+	{
+		/* check if this is itself */
+		if(bms_is_member(MyBackendId, entry->map))
+			in_use = false;
+		else
+			in_use = true;
+	}
+	else
+		in_use = true;
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return in_use;
+}
+
+/*
+ * Record GTT information to local hash.
+ * They include GTT storage info, transaction info and statistical info.
+ */
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+	gtt_local_hash_entry		*entry;
+	MemoryContext			oldcontext;
+	gtt_relfilenode			*new_node = NULL;
+	Oid				relid = RelationGetRelid(rel);
+	int				natts = 0;
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("Global temporary table is disabled"),
+				 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temporary table");
+
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rel->rd_index &&
+		(!rel->rd_index->indisvalid ||
+		 !rel->rd_index->indisready ||
+		 !rel->rd_index->indislive))
+		 elog(ERROR, "index \"%s\" is invalid that cannot create storage", RelationGetRelationName(rel));
+
+	/* First time through: initialize the hash table */
+	if (!gtt_storage_local_hash)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		HASHCTL		ctl;
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_info_context = AllocSetContextCreate(CacheMemoryContext,
+												 "gtt info context",
+												 ALLOCSET_DEFAULT_SIZES);
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		ctl.hcxt = gtt_info_context;
+		gtt_storage_local_hash = hash_create("global temporary table info",
+											 GTT_LOCAL_HASH_SIZE,
+											 &ctl, HASH_ELEM | HASH_BLOBS);
+	}
+
+	Assert(CacheMemoryContext);
+	Assert(gtt_info_context);
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	entry = gtt_search_by_relid(relid, true);
+	if (!entry)
+	{
+		bool	found = false;
+
+		/* Look up or create an entry */
+		entry = hash_search(gtt_storage_local_hash, (void *) &relid, HASH_ENTER, &found);
+		if (found)
+		{
+			MemoryContextSwitchTo(oldcontext);
+			elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+						MyBackendId, relid);
+		}
+
+		entry->relfilenode_list = NIL;
+		entry->relkind = rel->rd_rel->relkind;
+		entry->on_commit_delete = false;
+
+		if (entry->relkind == RELKIND_RELATION)
+		{
+			/* record the on commit clause */
+			if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+			{
+				entry->on_commit_delete = true;
+				register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS, true);
+			}
+		}
+
+		gtt_storage_checkin(relid, entry->relkind);
+	}
+
+	/* record storage info relstat columnstats and transaction info to relfilenode list */
+	new_node = palloc0(sizeof(gtt_relfilenode));
+	new_node->relfilenode = rnode.relNode;
+	new_node->spcnode = rnode.spcNode;
+	new_node->relpages = 0;
+	new_node->reltuples = 0;
+	new_node->relallvisible = 0;
+	new_node->relfrozenxid = InvalidTransactionId;
+	new_node->relminmxid = InvalidMultiXactId;
+	new_node->natts = 0;
+	new_node->attnum = NULL;
+	new_node->att_stat_tups = NULL;
+	entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+	/* init structure for column statistics */
+	natts = RelationGetNumberOfAttributes(rel);
+	new_node->attnum = palloc0(sizeof(int) * natts);
+	new_node->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+	new_node->natts = natts;
+
+	/* remember transaction info */
+	if (RELKIND_HAS_TABLE_AM(entry->relkind))
+	{
+		new_node->relfrozenxid = RecentXmin;
+		new_node->relminmxid = GetOldestMultiXactId();
+
+		insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+
+	/* Registration callbacks are used to trigger cleanup during process exit */
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+/*
+ * Remove GTT information from local hash when transaction commit/rollback.
+ */
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+	gtt_local_hash_entry	*entry = NULL;
+	gtt_relfilenode		*d_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"global temp relid %u not found in local hash", relid);
+
+		return;
+	}
+
+	d_rnode = gtt_search_relfilenode(entry, rnode.relNode, rnode.spcNode, true);
+	if (d_rnode == NULL)
+	{
+		if (isCommit)
+			elog(ERROR,"global temp relfilenode %u not found in rel %u", rnode.relNode, relid);
+		else
+		{
+			/* rollback transaction */
+			if (entry->relfilenode_list == NIL)
+			{
+				gtt_storage_checkout(relid, isCommit, entry->relkind);
+
+				hash_search(gtt_storage_local_hash, (void *) &(relid), HASH_REMOVE, NULL);
+			}
+
+			return;
+		}
+	}
+
+	/* Clean up transaction info from Local order list and MyProc */
+	if (RELKIND_HAS_TABLE_AM(entry->relkind))
+	{
+		Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+
+		/* this is valid relfrozenxid */
+		if (TransactionIdIsValid(d_rnode->relfrozenxid))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+	}
+
+	/* delete relfilenode from rel entry */
+	entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+	gtt_free_statistics(d_rnode);
+
+	if (entry->relfilenode_list == NIL)
+	{
+		/* tell shared hash that current session will no longer use this GTT */
+		gtt_storage_checkout(relid, isCommit, entry->relkind);
+
+		hash_search(gtt_storage_local_hash, (void *) &(relid), HASH_REMOVE, NULL);
+	}
+
+	return;
+}
+
+/*
+ * Check if current session is using this GTT.
+ */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool			found = false;
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (!OidIsValid(relid))
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry)
+		found = true;
+
+	return found;
+}
+
+/*
+ * When backend exit, bulk cleaning all GTT storage and local buffer of this backend.
+ */
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS			status;
+	gtt_local_hash_entry	*entry;
+
+	if (!gtt_storage_local_hash)
+		return;
+
+	/* Need to ensure we have a usable transaction. */
+	AbortOutOfAnyTransaction();
+
+	/* Search all relfilenode for GTT in current session */
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		ListCell *lc;
+
+		foreach(lc, entry->relfilenode_list)
+		{
+			SMgrRelation	srel[1];
+			RelFileNode		rnode;
+			gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+			rnode.spcNode = gtt_rnode->spcnode;
+			rnode.dbNode = MyDatabaseId;
+			rnode.relNode = gtt_rnode->relfilenode;
+			srel[0] = smgropen(rnode, MyBackendId);
+			smgrdounlinkall(srel, 1, false);
+			smgrclose(srel[0]);
+		}
+
+		gtt_storage_checkout(entry->relid, false, entry->relkind);
+
+		hash_search(gtt_storage_local_hash, (void *) &(entry->relid), HASH_REMOVE, NULL);
+	}
+
+	/* set to global area */
+	MyProc->gtt_frozenxid = InvalidTransactionId;
+
+	return;
+}
+
+/*
+ * Update GTT relstats(relpage/reltuple/relallvisible)
+ * to local hash.
+ */
+void
+gtt_update_relstats(Relation relation, BlockNumber relpages, double reltuples,
+						BlockNumber relallvisible, TransactionId relfrozenxid,
+						TransactionId relminmxid)
+{
+	Oid						relid = RelationGetRelid(relation);
+	gtt_relfilenode			*gtt_rnode = NULL;
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (!OidIsValid(relid))
+		return;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	if (relpages >= 0 &&
+		gtt_rnode->relpages != (int32)relpages)
+	{
+		gtt_rnode->relpages = (int32)relpages;
+		relation->rd_rel->relpages = (int32) relpages;
+	}
+
+	if (reltuples >= 0 &&
+		gtt_rnode->reltuples != (float4)reltuples)
+	{
+		gtt_rnode->reltuples = (float4)reltuples;
+		relation->rd_rel->reltuples = (float4)reltuples;
+	}
+
+	if (relallvisible >= 0 &&
+		gtt_rnode->relallvisible != (int32)relallvisible)
+	{
+		gtt_rnode->relallvisible = (int32)relallvisible;
+		relation->rd_rel->relallvisible = (int32)relallvisible;
+	}
+
+	/* only heap contain transaction information and relallvisible */
+	if (RELKIND_HAS_TABLE_AM(entry->relkind))
+	{
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			gtt_rnode->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNextTransactionId(), gtt_rnode->relfrozenxid)))
+		{
+			/* set to local order list */
+			remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+			gtt_rnode->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			/* set to global area */
+			set_gtt_session_relfrozenxid();
+			relation->rd_rel->relfrozenxid = relfrozenxid;
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			gtt_rnode->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+		{
+			gtt_rnode->relminmxid = relminmxid;
+			relation->rd_rel->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search GTT relstats(relpage/reltuple/relallvisible)
+ * from local has.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return false;
+
+	Assert(entry->relid == relid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return false;
+
+	if (relpages)
+		*relpages = gtt_rnode->relpages;
+
+	if (reltuples)
+		*reltuples = gtt_rnode->reltuples;
+
+	if (relallvisible)
+		*relallvisible = gtt_rnode->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = gtt_rnode->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = gtt_rnode->relminmxid;
+
+	return true;
+}
+
+/*
+ * Update GTT info(definition is same as pg_statistic)
+ * to local hash.
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode		*gtt_rnode = NULL;
+	MemoryContext		oldcontext;
+	bool		found = false;
+	int			i = 0;
+
+	/* not support whole row or system column */
+	if (attnum <= 0)
+		return;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return;
+
+	Assert(entry->relid == reloid);
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return;
+
+	/* switch context to gtt_info_context for store tuple at heap_form_tuple */
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == 0)
+		{
+			Assert(gtt_rnode->att_stat_tups[i] == NULL);
+			gtt_rnode->attnum[i] = attnum;
+			gtt_rnode->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+			found = true;
+			break;
+		}
+		else if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			heap_freetuple(gtt_rnode->att_stat_tups[i]);
+			gtt_rnode->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+			found = true;
+			break;
+		}
+	}
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!found)
+		elog(WARNING, "analyze can not update relid %u column %d statistics after add or drop column, try truncate table first", reloid, attnum);
+
+	return;
+}
+
+/*
+ * Search GTT statistic info(definition is same as pg_statistic)
+ * from local hash.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	int			i = 0;
+	gtt_relfilenode		*gtt_rnode = NULL;
+
+	/* not support whole row or system column */
+	if (attnum <= 0)
+		return NULL;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	entry = gtt_search_by_relid(reloid, true);
+	if (entry == NULL)
+		return NULL;
+
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return NULL;
+
+	for (i = 0; i < gtt_rnode->natts; i++)
+	{
+		if (gtt_rnode->attnum[i] == attnum)
+		{
+			Assert(gtt_rnode->att_stat_tups[i]);
+			return gtt_rnode->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Insert a RelfrozenXID into the list and keep the list in order.
+ */
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int		i;
+
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+
+	/* No, so find the entry it belongs after */
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;
+
+		i++;
+	}
+	gtt_session_relfrozenxid_list =
+		list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Remove a RelfrozenXID from order list gtt_session_relfrozenxid_list.
+ */
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+/*
+ * Update of backend Level oldest relfrozenxid to MyProc.
+ * This makes each backend's oldest RelFrozenxID globally visible.
+ */
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list != NIL)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	if (MyProc->gtt_frozenxid != gtt_frozenxid)
+		MyProc->gtt_frozenxid = gtt_frozenxid;
+}
+
+/*
+ * Get GTT column level data statistics.
+ */
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate		*tupstore;
+	HeapTuple		tuple;
+	Relation		rel = NULL;
+	Oid			reloid = PG_GETARG_OID(0);
+	int			attnum = PG_GETARG_INT32(1);
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	Relation		pg_tatistic = NULL;
+	TupleDesc		sd;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	if (!RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+	sd = RelationGetDescr(pg_tatistic);
+
+	/* get data from local hash */
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		Datum		values[Natts_pg_statistic];
+		bool		isnull[Natts_pg_statistic];
+		HeapTuple	res = NULL;
+
+		memset(&values, 0, sizeof(values));
+		memset(&isnull, 0, sizeof(isnull));
+		heap_deform_tuple(tuple, sd, values, isnull);
+		res = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, res);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, AccessShareLock);
+	relation_close(pg_tatistic, AccessShareLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get GTT table level data statistics.
+ */
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate	*tupstore;
+	TupleDesc	tupdesc;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Oid			reloid = PG_GETARG_OID(0);
+	Oid			relnode = 0;
+	BlockNumber		relpages = 0;
+	BlockNumber		relallvisible = 0;
+	uint32			relfrozenxid = 0;
+	uint32			relminmxid = 0;
+	double			reltuples = 0;
+	Relation		rel = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	if (!RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+	relnode = gtt_fetch_current_relfilenode(reloid);
+	if (relnode != InvalidOid)
+	{
+		Datum	values[6];
+		bool	isnull[6];
+
+		memset(isnull, 0, sizeof(isnull));
+		memset(values, 0, sizeof(values));
+		values[0] = UInt32GetDatum(relnode);
+		values[1] = Int32GetDatum(relpages);
+		values[2] = Float4GetDatum((float4)reltuples);
+		values[3] = Int32GetDatum(relallvisible);
+		values[4] = UInt32GetDatum(relfrozenxid);
+		values[5] = UInt32GetDatum(relminmxid);
+		tuple = heap_form_tuple(tupdesc, values, isnull);
+		tuplestore_puttuple(tupstore, tuple);
+	}
+	tuplestore_donestoring(tupstore);
+
+	relation_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get a list of backend pids that are currently using this GTT.
+ */
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	PGPROC			*proc = NULL;
+	Bitmapset		*map = NULL;
+	Tuplestorestate		*tupstore;
+	TupleDesc		tupdesc;
+	MemoryContext		oldcontext;
+	HeapTuple		tuple;
+	Oid			reloid = PG_GETARG_OID(0);
+	Relation		rel = NULL;
+	int				backendid = 0;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	rel = relation_open(reloid, AccessShareLock);
+	if (!RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+		relation_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	/* get data from share hash */
+	map = copy_active_gtt_bitmap(reloid);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			/* backendid map to process pid */
+			proc = BackendIdGetProc(backendid);
+			if (proc && proc->pid > 0)
+			{
+				Datum	values[2];
+				bool	isnull[2];
+				pid_t	pid = proc->pid;
+
+				memset(isnull, 0, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = UInt32GetDatum(reloid);
+				values[1] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	relation_close(rel, AccessShareLock);
+
+	return (Datum) 0;
+}
+
+/*
+ * Get backend level oldest relfrozenxid of each backend using GTT in current database.
+ */
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo		*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	Tuplestorestate		*tupstore;
+	TupleDesc		tupdesc;
+	MemoryContext	oldcontext;
+	TransactionId	oldest = InvalidTransactionId;
+	List			*pids = NULL;
+	List			*xids = NULL;
+
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	/* Get all session level oldest relfrozenxid that in current database use global temp table */
+	oldest = gtt_get_oldest_frozenxids_in_current_database(&pids, &xids);
+	if (TransactionIdIsValid(oldest))
+	{
+		HeapTuple		tuple;
+		ListCell		*lc1 = NULL;
+		ListCell		*lc2 = NULL;
+
+		Assert(list_length(pids) == list_length(xids));
+		forboth(lc1, pids, lc2, xids)
+		{
+			Datum	values[2];
+			bool	isnull[2];
+
+			memset(isnull, 0, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(lfirst_int(lc1));
+			values[1] = UInt32GetDatum(lfirst_oid(lc2));
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	list_free(pids);
+	list_free(xids);
+
+	return (Datum) 0;
+}
+
+/*
+ * In order to build the GTT index, force enable GTT'index.
+ */
+void
+gtt_index_force_enable(Relation index)
+{
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	Assert(index->rd_rel->relkind == RELKIND_INDEX);
+	Assert(OidIsValid(RelationGetRelid(index)));
+
+	index->rd_index->indisvalid = true;
+	index->rd_index->indislive = true;
+	index->rd_index->indisready = true;
+}
+
+/*
+ * Fix the local state of the GTT's index.
+ */
+void
+gtt_correct_index_session_state(Relation index)
+{
+	Oid indexOid = RelationGetRelid(index);
+	Oid heapOid = index->rd_index->indrelid;
+
+	/* Must be GTT */
+	if (!RELATION_IS_GLOBAL_TEMP(index))
+		return;
+
+	if (!index->rd_index->indisvalid)
+		return;
+
+	/*
+	 * If this GTT is not initialized in the current session,
+	 * its index status is temporarily set to invalid(local relcache).
+	 */
+	if (gtt_storage_attached(heapOid) &&
+		!gtt_storage_attached(indexOid))
+	{
+		index->rd_index->indisvalid = false;
+		index->rd_index->indislive = false;
+		index->rd_index->indisready = false;
+	}
+
+	return;
+}
+
+/*
+ * Initialize storage of GTT and build empty index in this session.
+ */
+void
+gtt_init_storage(CmdType operation, Relation relation)
+{
+	Oid			toastrelid;
+	List		*indexoidlist = NIL;
+	ListCell	*l;
+
+	if (!(operation == CMD_INSERT))
+		return;
+
+	if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+		return;
+
+	if (!RELATION_IS_GLOBAL_TEMP(relation))
+		return;
+
+	/* Each GTT is initialized once in each backend */
+	if (gtt_storage_attached(RelationGetRelid(relation)))
+		return;
+
+	/* init heap storage */
+	RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+	indexoidlist = RelationGetIndexList(relation);
+	foreach(l, indexoidlist)
+	{
+		Oid			indexOid = lfirst_oid(l);
+		Relation	index = index_open(indexOid, RowExclusiveLock);
+		IndexInfo	*info = BuildDummyIndexInfo(index);
+
+		index_build(relation, index, info, true, false);
+		/* after build index, index re-enabled */
+		Assert(index->rd_index->indisvalid);
+		Assert(index->rd_index->indislive);
+		Assert(index->rd_index->indisready);
+		index_close(index, NoLock);
+	}
+	list_free(indexoidlist);
+
+	/* rebuild index for global temp toast table */
+	toastrelid = relation->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+		ListCell	*indlist;
+
+		toastrel = table_open(toastrelid, RowExclusiveLock);
+
+		/* init index storage */
+		RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+		foreach(indlist, RelationGetIndexList(toastrel))
+		{
+			Oid			indexId = lfirst_oid(indlist);
+			Relation	index = index_open(indexId, RowExclusiveLock);
+			IndexInfo	*info = BuildDummyIndexInfo(index);
+
+			/* build empty index */
+			index_build(toastrel, index, info, true, false);
+			Assert(index->rd_index->indisvalid);
+			Assert(index->rd_index->indislive);
+			Assert(index->rd_index->indisready);
+			index_close(index, NoLock);
+		}
+
+		table_close(toastrel, NoLock);
+	}
+
+	return;
+}
+
+/*
+ * Release the data structure memory used to store GTT storage info.
+ */
+static void
+gtt_free_statistics(gtt_relfilenode *rnode)
+{
+	int i;
+
+	Assert(rnode);
+
+	for (i = 0; i < rnode->natts; i++)
+	{
+		if (rnode->att_stat_tups[i])
+		{
+			heap_freetuple(rnode->att_stat_tups[i]);
+			rnode->att_stat_tups[i] = NULL;
+		}
+	}
+
+	if (rnode->attnum)
+		pfree(rnode->attnum);
+
+	if (rnode->att_stat_tups)
+		pfree(rnode->att_stat_tups);
+
+	pfree(rnode);
+
+	return;
+}
+
+/*
+ * Get the current relfilenode of this GTT.
+ */
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+	gtt_local_hash_entry	*entry;
+	gtt_relfilenode			*gtt_rnode = NULL;
+
+	if (max_active_gtt <= 0)
+		return InvalidOid;
+
+	entry = gtt_search_by_relid(relid, true);
+	if (entry == NULL)
+		return InvalidOid;
+
+	Assert(entry->relid == relid);
+	gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+	if (gtt_rnode == NULL)
+		return InvalidOid;
+
+	return gtt_rnode->relfilenode;
+}
+
+/*
+ * Get a relfilenode used by this GTT during the transaction life cycle.
+ */
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry	*entry, Oid relfilenode, Oid spcnode, bool missing_ok)
+{
+	gtt_relfilenode		*rnode = NULL;
+	ListCell		*lc;
+
+	Assert(entry);
+	foreach(lc, entry->relfilenode_list)
+	{
+		gtt_relfilenode	*gtt_rnode = lfirst(lc);
+		if (gtt_rnode->relfilenode == relfilenode &&
+			gtt_rnode->spcnode == spcnode)
+		{
+			rnode = gtt_rnode;
+			break;
+		}
+	}
+
+	if (!missing_ok && rnode == NULL)
+		elog(ERROR, "search relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+	return rnode;
+}
+
+/*
+ * Get one GTT info from local hash.
+ */
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+	gtt_local_hash_entry	*entry = NULL;
+
+	if (!gtt_storage_local_hash)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash, (void *) &(relid), HASH_FIND, NULL);
+	if (!entry && !missing_ok)
+		elog(ERROR, "relid %u not found in local hash", relid);
+
+	return entry;
+}
+
+/*
+ * update pg_class entry after CREATE INDEX or REINDEX for global temp table
+ */
+void
+index_update_gtt_relstats(Relation rel, bool hasindex, double reltuples, bool isreindex)
+{
+	Oid			relid = RelationGetRelid(rel);
+
+	Assert(RELATION_IS_GLOBAL_TEMP(rel));
+
+	/* see index_update_stats() */
+	if (reltuples == 0 && rel->rd_rel->reltuples < 0)
+		reltuples = -1;
+
+	/* update reltuples relpages relallvisible to localhash */
+	if (reltuples >= 0)
+	{
+		BlockNumber relpages = RelationGetNumberOfBlocks(rel);
+		BlockNumber relallvisible = 0;
+
+		if (rel->rd_rel->relkind != RELKIND_INDEX)
+			visibilitymap_count(rel, &relallvisible, NULL);
+		else
+			relallvisible = 0;
+
+		gtt_update_relstats(rel, relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
+	}
+
+	/* update relhasindex to pg_class */
+	if (hasindex != rel->rd_rel->relhasindex)
+	{
+		Relation		pg_class = table_open(RelationRelationId, RowExclusiveLock);
+		Form_pg_class	rd_rel;
+		HeapTuple		tuple;
+
+		Assert(rel->rd_rel->relkind != RELKIND_INDEX);
+		tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "could not find tuple for relation %u", relid);
+
+		rd_rel = (Form_pg_class) GETSTRUCT(tuple);
+		rd_rel->relhasindex = hasindex;
+		heap_inplace_update(pg_class, tuple);
+		heap_freetuple(tuple);
+		table_close(pg_class, RowExclusiveLock);
+	}
+	else if (!isreindex)
+	{
+		/*
+		 * For global temp table
+		 * Even if pg_class does not change, relcache needs to be rebuilt
+		 * for flush rd_indexlist list (for a table) at create index process.
+		 *
+		 * Each session index has an independent data and cache(rd_amcache)
+		 * so relcache of the table and index do not need to be refreshed at reindex process.
+		 * This is different from the reindex of a regular table.
+		 */
+		CacheInvalidateRelcache(rel);
+	}
+}
+
+/*
+ * update statistics for one global temp relation
+ */
+void
+vac_update_gtt_relstats(Relation relation,
+					BlockNumber num_pages, double num_tuples,
+					BlockNumber num_all_visible_pages,
+					bool hasindex, TransactionId frozenxid,
+					MultiXactId minmulti, bool in_outer_xact)
+{
+	Oid			relid = RelationGetRelid(relation);
+	Relation	pg_class;
+	HeapTuple	ctup;
+	Form_pg_class pgcform;
+	bool		dirty = false;
+	List		*idxs = NIL;
+
+	Assert(RELATION_IS_GLOBAL_TEMP(relation));
+
+	/* For global temporary table, store relstats and transaction info to the localhash */
+	gtt_update_relstats(relation, num_pages, num_tuples,
+						num_all_visible_pages, frozenxid, minmulti);
+
+	if (relation->rd_rel->relkind == RELKIND_RELATION)
+		idxs = RelationGetIndexList(relation);
+
+	pg_class = table_open(RelationRelationId, RowExclusiveLock);
+
+	/* Fetch a copy of the tuple to scribble on */
+	ctup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(ctup))
+		elog(ERROR, "pg_class entry for relid %u vanished during vacuuming", relid);
+	pgcform = (Form_pg_class) GETSTRUCT(ctup);
+
+	/* Apply DDL updates, but not inside an outer transaction (see above) */
+	if (!in_outer_xact)
+	{
+		/*
+		 * If we didn't find any indexes, reset relhasindex.
+		 *
+		 * Global temporary table may contain indexes that are not valid locally.
+		 * The catalog should not be updated based on local invalid index.
+		 */
+		if (pgcform->relhasindex && !hasindex && idxs == NIL)
+		{
+			pgcform->relhasindex = false;
+			dirty = true;
+		}
+
+		/* We also clear relhasrules and relhastriggers if needed */
+		if (pgcform->relhasrules && relation->rd_rules == NULL)
+		{
+			pgcform->relhasrules = false;
+			dirty = true;
+		}
+		if (pgcform->relhastriggers && relation->trigdesc == NULL)
+		{
+			pgcform->relhastriggers = false;
+			dirty = true;
+		}
+	}
+
+	/* If anything changed, write out the tuple. */
+	if (dirty)
+		heap_inplace_update(pg_class, ctup);
+
+	table_close(pg_class, RowExclusiveLock);
+
+	list_free(idxs);
+}
+
+void
+GlobalTempRelationSetNewRelfilenode(Relation relation)
+{
+	Oid			newrelfilenode;
+	MultiXactId minmulti = InvalidMultiXactId;
+	TransactionId freezeXid = InvalidTransactionId;
+	RelFileNode newrnode;
+
+	Assert(RELATION_IS_GLOBAL_TEMP(relation));
+	Assert(!RelationIsMapped(relation));
+
+	/* Allocate a new relfilenode */
+	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
+									   RELPERSISTENCE_GLOBAL_TEMP);
+
+	/*
+	 * Schedule unlinking of the old storage at transaction commit.
+	 */
+	RelationDropStorage(relation);
+
+	newrnode = relation->rd_node;
+	newrnode.relNode = newrelfilenode;
+
+	if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind))
+	{
+		table_relation_set_new_filenode(relation, &newrnode,
+										RELPERSISTENCE_GLOBAL_TEMP,
+										&freezeXid, &minmulti);
+	}
+	else if (RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+	{
+		/* handle these directly, at least for now */
+		SMgrRelation srel;
+
+		srel = RelationCreateStorage(newrnode, RELPERSISTENCE_GLOBAL_TEMP, relation);
+		smgrclose(srel);
+	}
+	else
+	{
+		/* we shouldn't be called for anything else */
+		elog(ERROR, "relation \"%s\" does not have storage",
+			 RelationGetRelationName(relation));
+	}
+
+	RelationAssumeNewRelfilenode(relation);
+
+	/* The local relcache and hashtable have been updated */
+	Assert(gtt_fetch_current_relfilenode(RelationGetRelid(relation)) == newrelfilenode);
+	Assert(relation->rd_node.relNode == newrelfilenode);
+}
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 3cb69b1f87b..1625ee9345b 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE VIEW pg_sequences AS
     WHERE NOT pg_is_other_temp_schema(N.oid)
           AND relkind = 'S';
 
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    s.*
+ FROM
+     pg_class c
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+    c.relname AS tablename,
+    a.attname,
+    s.stainherit AS inherited,
+    s.stanullfrac AS null_frac,
+    s.stawidth AS avg_width,
+    s.stadistinct AS n_distinct,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stavalues1
+            WHEN s.stakind2 = 1 THEN s.stavalues2
+            WHEN s.stakind3 = 1 THEN s.stavalues3
+            WHEN s.stakind4 = 1 THEN s.stavalues4
+            WHEN s.stakind5 = 1 THEN s.stavalues5
+        END AS most_common_vals,
+        CASE
+            WHEN s.stakind1 = 1 THEN s.stanumbers1
+            WHEN s.stakind2 = 1 THEN s.stanumbers2
+            WHEN s.stakind3 = 1 THEN s.stanumbers3
+            WHEN s.stakind4 = 1 THEN s.stanumbers4
+            WHEN s.stakind5 = 1 THEN s.stanumbers5
+        END AS most_common_freqs,
+        CASE
+            WHEN s.stakind1 = 2 THEN s.stavalues1
+            WHEN s.stakind2 = 2 THEN s.stavalues2
+            WHEN s.stakind3 = 2 THEN s.stavalues3
+            WHEN s.stakind4 = 2 THEN s.stavalues4
+            WHEN s.stakind5 = 2 THEN s.stavalues5
+        END AS histogram_bounds,
+        CASE
+            WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+            WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+            WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+            WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+            WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+        END AS correlation,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stavalues1
+            WHEN s.stakind2 = 4 THEN s.stavalues2
+            WHEN s.stakind3 = 4 THEN s.stavalues3
+            WHEN s.stakind4 = 4 THEN s.stavalues4
+            WHEN s.stakind5 = 4 THEN s.stavalues5
+        END AS most_common_elems,
+        CASE
+            WHEN s.stakind1 = 4 THEN s.stanumbers1
+            WHEN s.stakind2 = 4 THEN s.stanumbers2
+            WHEN s.stakind3 = 4 THEN s.stanumbers3
+            WHEN s.stakind4 = 4 THEN s.stanumbers4
+            WHEN s.stakind5 = 4 THEN s.stanumbers5
+        END AS most_common_elem_freqs,
+        CASE
+            WHEN s.stakind1 = 5 THEN s.stanumbers1
+            WHEN s.stakind2 = 5 THEN s.stanumbers2
+            WHEN s.stakind3 = 5 THEN s.stanumbers3
+            WHEN s.stakind4 = 5 THEN s.stanumbers4
+            WHEN s.stakind5 = 5 THEN s.stanumbers5
+        END AS elem_count_histogram
+   FROM
+     pg_class c
+     JOIN pg_attribute a ON c.oid = a.attrelid
+     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+     pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+  WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
 CREATE VIEW pg_stats WITH (security_barrier) AS
     SELECT
         nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 736479295ad..f08ac47b729 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "commands/dbcommands.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -105,7 +106,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);
 
@@ -186,6 +187,17 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+		!gtt_storage_attached(RelationGetRelid(onerel)))
+	{
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * We can ANALYZE any table except pg_statistic. See update_attstats
 	 */
@@ -602,14 +614,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]));
 		}
 
 		/* Build extended statistics (if there are any). */
@@ -1620,7 +1633,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;
@@ -1722,31 +1735,48 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/*
+		 * For global temporary table,
+		 * Update column statistic to localhash, not pg_statistic.
+		 */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
 
-		heap_freetuple(stup);
+			if (HeapTupleIsValid(oldtup))
+			{
+				/* Yes, replace it */
+				stup = heap_modify_tuple(oldtup,
+										 RelationGetDescr(sd),
+										 values,
+										 nulls,
+										 replaces);
+				ReleaseSysCache(oldtup);
+				CatalogTupleUpdate(sd, &stup->t_self, stup);
+			}
+			else
+			{
+				/* No, insert new tuple */
+				stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+				CatalogTupleInsert(sd, stup);
+			}
+
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 02a7e94bf9b..cba222ddee0 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/toasting.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
@@ -390,6 +391,22 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/*
+	 * Skip the global temporary table that did not initialize the storage
+	 * in this backend.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+	{
+		if (gtt_storage_attached(RelationGetRelid(OldHeap)))
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("cannot cluster global temporary table")));
+
+		relation_close(OldHeap, AccessExclusiveLock);
+		pgstat_progress_end_command();
+		return;
+	}
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
@@ -585,6 +602,9 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
 	TransactionId frozenXid;
 	MultiXactId cutoffMulti;
 
+	/* not support cluster global temp table yet */
+	Assert(!RELATION_IS_GLOBAL_TEMP(OldHeap));
+
 	/* Mark the correct index as clustered */
 	if (OidIsValid(indexOid))
 		mark_index_clustered(OldHeap, indexOid, true);
@@ -1428,7 +1448,7 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 	pgstat_progress_update_param(PROGRESS_CLUSTER_PHASE,
 								 PROGRESS_CLUSTER_PHASE_REBUILD_INDEX);
 
-	reindex_relation(OIDOldHeap, reindex_flags, &reindex_params);
+	reindex_relation(OIDOldHeap, reindex_flags, &reindex_params, ShareLock);
 
 	/* Report that we are now doing clean up */
 	pgstat_progress_update_param(PROGRESS_CLUSTER_PHASE,
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 7da7105d44b..8351f41a154 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -289,7 +289,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 		Assert(rel);
 
 		/* check read-only transaction and parallel mode */
-		if (XactReadOnly && !rel->rd_islocaltemp)
+		if (XactReadOnly && !RELATION_IS_TEMP_ON_CURRENT_SESSION(rel))
 			PreventCommandIfReadOnly("COPY FROM");
 
 		cstate = BeginCopyFrom(pstate, rel, whereClause,
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 7b3f5a84b82..03692b4a73a 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -30,6 +30,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/namespace.h"
+#include "catalog/storage_gtt.h"
 #include "commands/copy.h"
 #include "commands/copyfrom_internal.h"
 #include "commands/progress.h"
@@ -652,7 +653,7 @@ CopyFrom(CopyFromState cstate)
 	 */
 	ExecInitRangeTable(estate, cstate->range_table);
 	resultRelInfo = target_resultRelInfo = makeNode(ResultRelInfo);
-	ExecInitResultRelation(estate, resultRelInfo, 1);
+	ExecInitResultRelation(estate, resultRelInfo, 1, CMD_INSERT);
 
 	/* Verify the named relation is a valid target for INSERT */
 	CheckValidResultRel(resultRelInfo, CMD_INSERT);
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 9abbb6b5552..a944f244496 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -32,6 +32,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/namespace.h"
+#include "catalog/storage_gtt.h"
 #include "catalog/toasting.h"
 #include "commands/createas.h"
 #include "commands/matview.h"
@@ -520,6 +521,12 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 	 */
 	intoRelationDesc = table_open(intoRelationAddr.objectId, AccessExclusiveLock);
 
+	/*
+	 * Try initializing the global Temp table storage file before writing data
+	 * to the table.
+	 */
+	gtt_init_storage(CMD_INSERT, intoRelationDesc);
+
 	/*
 	 * Make sure the constructed table does not have RLS enabled.
 	 *
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index cd30f15eba6..e9a4c39e18d 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -111,6 +111,7 @@ struct ReindexIndexCallbackState
 {
 	ReindexParams params;		/* options from statement */
 	Oid			locked_table_oid;	/* tracks previously locked table */
+	LOCKMODE	lockmode;
 };
 
 /*
@@ -570,7 +571,7 @@ DefineIndex(Oid relationId,
 	 * is more efficient.  Do this before any use of the concurrent option is
 	 * done.
 	 */
-	if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+	if (stmt->concurrent && !RelpersistenceTsTemp(get_rel_persistence(relationId)))
 		concurrent = true;
 	else
 		concurrent = false;
@@ -2582,24 +2583,46 @@ ReindexIndex(RangeVar *indexRelation, ReindexParams *params, bool isTopLevel)
 	 */
 	state.params = *params;
 	state.locked_table_oid = InvalidOid;
+	state.lockmode = AccessShareLock;
 	indOid = RangeVarGetRelidExtended(indexRelation,
-									  (params->options & REINDEXOPT_CONCURRENTLY) != 0 ?
-									  ShareUpdateExclusiveLock : AccessExclusiveLock,
+									  AccessShareLock,
 									  0,
 									  RangeVarCallbackForReindexIndex,
 									  &state);
 
 	/*
 	 * Obtain the current persistence and kind of the existing index.  We
-	 * already hold a lock on the index.
+	 * already hold a AccessShareLock on the index.
+	 * If this is not a global temp object, apply a larger lock.
 	 */
 	persistence = get_rel_persistence(indOid);
-	relkind = get_rel_relkind(indOid);
+	if (persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		LOCKMODE	table_lockmode;
+		LOCKMODE	index_lockmode;
+
+		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0)
+		{
+			table_lockmode = ShareUpdateExclusiveLock;
+			index_lockmode = ShareUpdateExclusiveLock;
+		}
+		else
+		{
+			table_lockmode = ShareLock;
+			index_lockmode = AccessExclusiveLock;
+		}
 
+		/* lock heap first */
+		Assert(OidIsValid(state.locked_table_oid));
+		LockRelationOid(state.locked_table_oid, table_lockmode);
+		LockRelationOid(indOid, index_lockmode);
+	}
+
+	relkind = get_rel_relkind(indOid);
 	if (relkind == RELKIND_PARTITIONED_INDEX)
 		ReindexPartitions(indOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 persistence != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(persistence))
 		ReindexRelationConcurrently(indOid, params);
 	else
 	{
@@ -2621,15 +2644,7 @@ RangeVarCallbackForReindexIndex(const RangeVar *relation,
 {
 	char		relkind;
 	struct ReindexIndexCallbackState *state = arg;
-	LOCKMODE	table_lockmode;
-
-	/*
-	 * Lock level here should match table lock in reindex_index() for
-	 * non-concurrent case and table locks used by index_concurrently_*() for
-	 * concurrent case.
-	 */
-	table_lockmode = (state->params.options & REINDEXOPT_CONCURRENTLY) != 0 ?
-		ShareUpdateExclusiveLock : ShareLock;
+	LOCKMODE	table_lockmode = state->lockmode;
 
 	/*
 	 * If we previously locked some other index's heap, and the name we're
@@ -2690,6 +2705,8 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 {
 	Oid			heapOid;
 	bool		result;
+	char		relpersistence;
+	LOCKMODE	lockmode;
 
 	/*
 	 * The lock level used here should match reindex_relation().
@@ -2700,15 +2717,27 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 	 * locks on our temporary table.
 	 */
 	heapOid = RangeVarGetRelidExtended(relation,
-									   (params->options & REINDEXOPT_CONCURRENTLY) != 0 ?
-									   ShareUpdateExclusiveLock : ShareLock,
+									   AccessShareLock,
 									   0,
 									   RangeVarCallbackOwnsTable, NULL);
 
+	relpersistence = get_rel_persistence(heapOid);
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		lockmode = AccessShareLock;
+	else
+	{
+		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0)
+			lockmode = ShareUpdateExclusiveLock;
+		else
+			lockmode = ShareLock;
+
+		LockRelationOid(heapOid, lockmode);
+	}
+
 	if (get_rel_relkind(heapOid) == RELKIND_PARTITIONED_TABLE)
 		ReindexPartitions(heapOid, params, isTopLevel);
 	else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			 get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+			 !RelpersistenceTsTemp(relpersistence))
 	{
 		result = ReindexRelationConcurrently(heapOid, params);
 
@@ -2725,7 +2754,8 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
 		result = reindex_relation(heapOid,
 								  REINDEX_REL_PROCESS_TOAST |
 								  REINDEX_REL_CHECK_CONSTRAINTS,
-								  &newparams);
+								  &newparams,
+								  lockmode);
 		if (!result)
 			ereport(NOTICE,
 					(errmsg("table \"%s\" has no indexes to reindex",
@@ -3120,7 +3150,7 @@ ReindexMultipleInternal(List *relids, ReindexParams *params)
 		Assert(!RELKIND_HAS_PARTITIONS(relkind));
 
 		if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
-			relpersistence != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(relpersistence))
 		{
 			ReindexParams newparams = *params;
 
@@ -3142,13 +3172,20 @@ ReindexMultipleInternal(List *relids, ReindexParams *params)
 		{
 			bool		result;
 			ReindexParams newparams = *params;
+			LOCKMODE	lockmode;
+
+			if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+				lockmode = AccessShareLock;
+			else
+				lockmode = ShareLock;
 
 			newparams.options |=
 				REINDEXOPT_REPORT_PROGRESS | REINDEXOPT_MISSING_OK;
 			result = reindex_relation(relid,
 									  REINDEX_REL_PROCESS_TOAST |
 									  REINDEX_REL_CHECK_CONSTRAINTS,
-									  &newparams);
+									  &newparams,
+									  lockmode);
 
 			if (result && (params->options & REINDEXOPT_VERBOSE) != 0)
 				ereport(INFO,
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 4b3f79704f8..c544885f53e 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -51,12 +51,33 @@ LockTableCommand(LockStmt *lockstmt)
 		RangeVar   *rv = (RangeVar *) lfirst(p);
 		bool		recurse = rv->inh;
 		Oid			reloid;
+		LOCKMODE	lockmode = lockstmt->mode;
+		char		relpersistence;
 
-		reloid = RangeVarGetRelidExtended(rv, lockstmt->mode,
-										  lockstmt->nowait ? RVR_NOWAIT : 0,
+		reloid = RangeVarGetRelidExtended(rv, NoLock, 0,
 										  RangeVarCallbackForLockTable,
 										  (void *) &lockstmt->mode);
 
+		relpersistence = get_rel_persistence(reloid);
+		/* lock statement does not hold any lock on global temp table */
+		if (relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			if (!lockstmt->nowait)
+				LockRelationOid(reloid, lockmode);
+			else if (!ConditionalLockRelationOid(reloid, lockmode))
+			{
+				/* try to throw error by name; relation could be deleted... */
+				char	   *relname = get_rel_name(reloid);
+
+				if (!relname)
+					return;		/* child concurrently dropped, just skip it */
+				ereport(ERROR,
+						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						 errmsg("could not obtain lock on relation \"%s\"",
+								relname)));
+			}
+		}
+
 		if (get_rel_relkind(reloid) == RELKIND_VIEW)
 			LockViewRecurse(reloid, lockstmt->mode, lockstmt->nowait, NIL);
 		else if (recurse)
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index ab592ce2f15..1d0e7077020 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -25,11 +25,14 @@
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
 #include "catalog/dependency.h"
+#include "catalog/heap.h"
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
@@ -108,6 +111,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
 static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
 
 
 /*
@@ -220,9 +224,16 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	rel = table_open(seqoid, AccessExclusiveLock);
 	tupDesc = RelationGetDescr(rel);
 
-	/* now initialize the sequence's data */
-	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	/*
+	 * For global temp sequence, the storage is not initialized
+	 * when it is created, but when it is used.
+	 */
+	if (!RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		/* now initialize the sequence's data */
+		tuple = heap_form_tuple(tupDesc, value, null);
+		fill_seq_with_data(rel, tuple);
+	}
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -275,8 +286,6 @@ ResetSequence(Oid seq_relid)
 	Buffer		buf;
 	HeapTupleData seqdatatuple;
 	HeapTuple	tuple;
-	HeapTuple	pgstuple;
-	Form_pg_sequence pgsform;
 	int64		startv;
 
 	/*
@@ -287,12 +296,7 @@ ResetSequence(Oid seq_relid)
 	init_sequence(seq_relid, &elm, &seq_rel);
 	(void) read_seq_tuple(seq_rel, &buf, &seqdatatuple);
 
-	pgstuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seq_relid));
-	if (!HeapTupleIsValid(pgstuple))
-		elog(ERROR, "cache lookup failed for sequence %u", seq_relid);
-	pgsform = (Form_pg_sequence) GETSTRUCT(pgstuple);
-	startv = pgsform->seqstart;
-	ReleaseSysCache(pgstuple);
+	startv = get_seqence_start_value(seq_relid);
 
 	/*
 	 * Copy the existing sequence tuple.
@@ -457,6 +461,9 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 
 	init_sequence(relid, &elm, &seqrel);
 
+	if (RELATION_IS_GLOBAL_TEMP(seqrel))
+		CheckGlobalTempTableNotInUse(seqrel, "ALTER GLOBAL TEMPORARY SEQUENCE");
+
 	rel = table_open(SequenceRelationId, RowExclusiveLock);
 	seqtuple = SearchSysCacheCopy1(SEQRELID,
 								   ObjectIdGetDatum(relid));
@@ -617,7 +624,7 @@ nextval_internal(Oid relid, bool check_permissions)
 						RelationGetRelationName(seqrel))));
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("nextval()");
 
 	/*
@@ -961,7 +968,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 	ReleaseSysCache(pgstuple);
 
 	/* read-only transactions may only modify temp sequences */
-	if (!seqrel->rd_islocaltemp)
+	if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
 		PreventCommandIfReadOnly("setval()");
 
 	/*
@@ -1185,6 +1192,14 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	/* Return results */
 	*p_elm = elm;
 	*p_rel = seqrel;
+
+	/* Initializes the storage for sequence which the global temporary table belongs. */
+	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+		!gtt_storage_attached(RelationGetRelid(seqrel)))
+	{
+		RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+		gtt_init_sequence(seqrel);
+	}
 }
 
 
@@ -1959,3 +1974,57 @@ seq_mask(char *page, BlockNumber blkno)
 
 	mask_unused_space(page);
 }
+
+/*
+ * Get the startValue of the sequence from syscache.
+ */
+int64
+get_seqence_start_value(Oid seqid)
+{
+	HeapTuple	seqtuple;
+	Form_pg_sequence	seqform;
+	int64		start;
+
+	seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u",
+			 seqid);
+
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+	start = seqform->seqstart;
+	ReleaseSysCache(seqtuple);
+
+	return start;
+}
+
+/*
+ * Initialize sequence which global temporary table belongs.
+ */
+void
+gtt_init_sequence(Relation rel)
+{
+	Datum		value[SEQ_COL_LASTCOL];
+	bool		null[SEQ_COL_LASTCOL];
+	HeapTuple	tuple;
+	int64		startv = get_seqence_start_value(RelationGetRelid(rel));
+
+	/*
+	 * last_value from pg_sequence.seqstart
+	 * log_cnt = 0
+	 * is_called = false
+	 */
+	value[SEQ_COL_LASTVAL - 1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+	null[SEQ_COL_LASTVAL - 1] = false;
+
+	value[SEQ_COL_LOG - 1] = Int64GetDatum((int64)0);
+	null[SEQ_COL_LOG - 1] = false;
+
+	value[SEQ_COL_CALLED - 1] = BoolGetDatum(false);
+	null[SEQ_COL_CALLED - 1] = false;
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+	fill_seq_with_data(rel, tuple);
+	heap_freetuple(tuple);
+
+	return;
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 3e83f375b55..5b8f083a110 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"
@@ -119,6 +120,7 @@ typedef struct OnCommitItem
 	 */
 	SubTransactionId creating_subid;
 	SubTransactionId deleting_subid;
+	bool			 is_global_temp;
 } OnCommitItem;
 
 static List *on_commits = NIL;
@@ -625,7 +627,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
 static char GetAttributeCompression(Oid atttypid, char *compression);
-
+static OnCommitAction gtt_oncommit_option(List *options);
 
 /* ----------------------------------------------------------------
  *		DefineRelation
@@ -670,6 +672,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	OnCommitAction	oncommit_action = ONCOMMIT_NOOP;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -681,7 +684,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * Check consistency of arguments
 	 */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !RelpersistenceTsTemp(stmt->relation->relpersistence))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -711,7 +714,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	if (RelpersistenceTsTemp(stmt->relation->relpersistence)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -812,6 +815,59 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* For global temporary table */
+	oncommit_action = gtt_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		if (!(relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("global temporary relation can only be a regular table or sequence")));
+
+		if (inheritOids)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot create global temporary inherit table or global temporary partitioned table")));
+
+		/* Check oncommit clause and save to reloptions */
+		if (oncommit_action != ONCOMMIT_NOOP)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot specify both ON COMMIT clause and on_commit_delete_rows")));
+
+			stmt->oncommit = oncommit_action;
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_DROP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("specifying ON COMMIT DROP is not supported on a global temporary table")));
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (oncommit_action != ONCOMMIT_NOOP)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("on_commit_delete_rows can only be used on global temporary table")));
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1436,7 +1492,7 @@ RemoveRelations(DropStmt *drop)
 		 * relation persistence cannot be known without its OID.
 		 */
 		if (drop->concurrent &&
-			get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+			!RelpersistenceTsTemp(get_rel_persistence(relOid)))
 		{
 			Assert(list_length(drop->objects) == 1 &&
 				   drop->removeType == OBJECT_INDEX);
@@ -1645,9 +1701,9 @@ ExecuteTruncate(TruncateStmt *stmt)
 		Relation	rel;
 		bool		recurse = rv->inh;
 		Oid			myrelid;
-		LOCKMODE	lockmode = AccessExclusiveLock;
+		LOCKMODE	lockmode;
 
-		myrelid = RangeVarGetRelidExtended(rv, lockmode,
+		myrelid = RangeVarGetRelidExtended(rv, AccessShareLock,
 										   0, RangeVarCallbackForTruncate,
 										   NULL);
 
@@ -1655,9 +1711,21 @@ ExecuteTruncate(TruncateStmt *stmt)
 		if (list_member_oid(relids, myrelid))
 			continue;
 
-		/* open the relation, we already hold a lock on it */
+		/* open the relation, we need hold a low-level lock first */
 		rel = table_open(myrelid, NoLock);
 
+		/*
+		 * Truncate global temp table only cleans up the data in current session,
+		 * only low-level locks are required.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel))
+			lockmode = AccessShareLock;
+		else
+		{
+			lockmode = AccessExclusiveLock;
+			LockRelationOid(myrelid, lockmode);
+		}
+
 		/*
 		 * RangeVarGetRelidExtended() has done most checks with its callback,
 		 * but other checks with the now-opened Relation remain.
@@ -1907,6 +1975,7 @@ ExecuteTruncateGuts(List *explicit_rels,
 	foreach(cell, rels)
 	{
 		Relation	rel = (Relation) lfirst(cell);
+		LOCKMODE	lockmode;
 
 		/* Skip partitioned tables as there is nothing to do */
 		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
@@ -1957,6 +2026,19 @@ ExecuteTruncateGuts(List *explicit_rels,
 			continue;
 		}
 
+		/*
+		 * Skip the global temporary table that is not initialized for storage
+		 * in current session.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel))
+		{
+			lockmode = AccessShareLock;
+			if (!gtt_storage_attached(RelationGetRelid(rel)))
+				continue;
+		}
+		else
+			lockmode = AccessExclusiveLock;
+
 		/*
 		 * Normally, we need a transaction-safe truncation here.  However, if
 		 * the table was either created in the current (sub)transaction or has
@@ -1968,7 +2050,7 @@ ExecuteTruncateGuts(List *explicit_rels,
 			rel->rd_newRelfilenodeSubid == mySubid)
 		{
 			/* Immediate, non-rollbackable truncation is OK */
-			heap_truncate_one_rel(rel);
+			heap_truncate_one_rel(rel, lockmode);
 		}
 		else
 		{
@@ -2002,7 +2084,7 @@ ExecuteTruncateGuts(List *explicit_rels,
 			if (OidIsValid(toast_relid))
 			{
 				Relation	toastrel = relation_open(toast_relid,
-													 AccessExclusiveLock);
+													 lockmode);
 
 				RelationSetNewRelfilenode(toastrel,
 										  toastrel->rd_rel->relpersistence);
@@ -2013,7 +2095,7 @@ ExecuteTruncateGuts(List *explicit_rels,
 			 * Reconstruct the indexes to match, and we're done.
 			 */
 			reindex_relation(heap_relid, REINDEX_REL_PROCESS_TOAST,
-							 &reindex_params);
+							 &reindex_params, lockmode);
 		}
 
 		pgstat_count_truncate(rel);
@@ -3284,6 +3366,12 @@ CheckRelationTableSpaceMove(Relation rel, Oid newTableSpaceId)
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot move temporary tables of other sessions")));
 
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot move global temporary table \"%s\"",
+				 		RelationGetRelationName(rel))));
+
 	return true;
 }
 
@@ -4053,6 +4141,10 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
+	/* We allow to alter global temporary table only current session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		CheckGlobalTempTableNotInUse(rel, "ALTER GLOBAL TEMPORARY TABLE");
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -5384,6 +5476,25 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 
 			rel = table_open(tab->relid, NoLock);
 			find_composite_type_dependencies(rel->rd_rel->reltype, rel, NULL);
+
+			if (RELATION_IS_GLOBAL_TEMP(rel) && tab->rewrite > 0)
+			{
+				if (tab->chgPersistence)
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot change global temporary table persistence setting")));
+
+				if(gtt_storage_attached(tab->relid))
+					ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot rewrite global temporary table \"%s\" when it has data in this session",
+								RelationGetRelationName(rel)),
+						 errhint("Please create a new connection and execute ALTER TABLE on the new connection.")));
+
+				/* global temp table has no data in this session, so only change catalog */
+				tab->rewrite = 0;
+			}
+
 			table_close(rel, NoLock);
 		}
 
@@ -5435,6 +5546,9 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot rewrite temporary tables of other sessions")));
 
+			/* Not support rewrite global temp table */
+			Assert(!RELATION_IS_GLOBAL_TEMP(OldHeap));
+
 			/*
 			 * Select destination tablespace (same as original unless user
 			 * requested a change)
@@ -9082,6 +9196,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("constraints on global temporary tables may reference only global temporary tables")));
+			break;
 	}
 
 	/*
@@ -13440,7 +13560,9 @@ TryReuseIndex(Oid oldId, IndexStmt *stmt)
 		Relation	irel = index_open(oldId, NoLock);
 
 		/* If it's a partitioned index, there is no storage to share. */
-		if (irel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
+		/* multiple global temp table are not allow use same relfilenode */
+		if (irel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX &&
+			!RELATION_IS_GLOBAL_TEMP(irel))
 		{
 			stmt->oldNode = irel->rd_node.relNode;
 			stmt->oldCreateSubid = irel->rd_createSubid;
@@ -14102,6 +14224,11 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
 
+	/* option on_commit_delete_rows is only for global temp table and cannot be set by ALTER TABLE */
+	if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+		elog(ERROR, "cannot set \"on_commit_delete_rows\" for relation \"%s\"",
+					RelationGetRelationName(rel));
+
 	pgclass = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch heap tuple */
@@ -14602,7 +14729,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(RelationGetSmgr(rel), dstrel, MAIN_FORKNUM,
@@ -16203,6 +16330,7 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
 	switch (rel->rd_rel->relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -16644,7 +16772,7 @@ AlterSeqNamespaces(Relation classRel, Relation rel,
  * Register a newly-created relation's ON COMMIT action.
  */
 void
-register_on_commit_action(Oid relid, OnCommitAction action)
+register_on_commit_action(Oid relid, OnCommitAction action, bool is_gloal_temp)
 {
 	OnCommitItem *oc;
 	MemoryContext oldcxt;
@@ -16663,6 +16791,7 @@ register_on_commit_action(Oid relid, OnCommitAction action)
 	oc->oncommit = action;
 	oc->creating_subid = GetCurrentSubTransactionId();
 	oc->deleting_subid = InvalidSubTransactionId;
+	oc->is_global_temp = is_gloal_temp;
 
 	/*
 	 * We use lcons() here so that ON COMMIT actions are processed in reverse
@@ -16708,6 +16837,7 @@ PreCommit_on_commit_actions(void)
 	ListCell   *l;
 	List	   *oids_to_truncate = NIL;
 	List	   *oids_to_drop = NIL;
+	List	   *oids_to_truncate_gtt = NIL;
 
 	foreach(l, on_commits)
 	{
@@ -16731,7 +16861,12 @@ PreCommit_on_commit_actions(void)
 				 * tables, as they must still be empty.
 				 */
 				if ((MyXactFlags & XACT_FLAGS_ACCESSEDTEMPNAMESPACE))
-					oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid);
+				{
+					if (oc->is_global_temp)
+						oids_to_truncate_gtt = lappend_oid(oids_to_truncate_gtt, oc->relid);
+					else
+						oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid);
+				}
 				break;
 			case ONCOMMIT_DROP:
 				oids_to_drop = lappend_oid(oids_to_drop, oc->relid);
@@ -16748,7 +16883,10 @@ PreCommit_on_commit_actions(void)
 	 * exists at truncation time.
 	 */
 	if (oids_to_truncate != NIL)
-		heap_truncate(oids_to_truncate);
+		heap_truncate(oids_to_truncate, false);
+
+	if (oids_to_truncate_gtt != NIL)
+		heap_truncate(oids_to_truncate_gtt, true);
 
 	if (oids_to_drop != NIL)
 	{
@@ -17747,6 +17885,13 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot attach temporary relation of another session as partition")));
 
+	/* If the parent is permanent, so must be all of its partitions. */
+	if (attachrel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach a global temporary relation as partition of permanent relation \"%s\"",
+						RelationGetRelationName(rel))));
+
 	/* Check if there are any columns in attachrel that aren't in the parent */
 	tupleDesc = RelationGetDescr(attachrel);
 	natts = tupleDesc->natts;
@@ -19239,3 +19384,39 @@ GetAttributeCompression(Oid atttypid, char *compression)
 
 	return cmethod;
 }
+
+/*
+ * Parse the on commit clause for the temporary table
+ */
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+	ListCell		*listptr;
+	OnCommitAction	action = ONCOMMIT_NOOP;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+		{
+			bool	res = false;
+			char	*sval = defGetString(def);
+
+			/* It has to be a Boolean value */
+			if (!parse_bool(sval, &res))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
+
+			if (res)
+				action = ONCOMMIT_DELETE_ROWS;
+			else
+				action = ONCOMMIT_PRESERVE_ROWS;
+
+			break;
+		}
+	}
+
+	return action;
+}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 50a4a612e58..8a3e867055a 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -39,7 +39,9 @@
 #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/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/vacuum.h"
 #include "miscadmin.h"
@@ -1332,6 +1334,11 @@ vac_update_relstats(Relation relation,
 	Form_pg_class pgcform;
 	bool		dirty;
 
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+		return vac_update_gtt_relstats(relation, num_pages, num_tuples,
+									   num_all_visible_pages, hasindex,
+									   frozenxid, minmulti, in_outer_xact);
+
 	rd = table_open(RelationRelationId, RowExclusiveLock);
 
 	/* Fetch a copy of the tuple to scribble on */
@@ -1434,7 +1441,6 @@ vac_update_relstats(Relation relation,
 	table_close(rd, RowExclusiveLock);
 }
 
-
 /*
  *	vac_update_datfrozenxid() -- update pg_database.datfrozenxid for our DB
  *
@@ -1526,6 +1532,13 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/*
+		 * The relfrozenxid for a global temporary talble is stored in localhash,
+		 * not pg_class, See list_all_session_gtt_frozenxids()
+		 */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1583,6 +1596,42 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/* If enable global temporary table */
+	if (max_active_gtt > 0)
+	{
+		TransactionId	safe_age;
+		TransactionId	oldest_gtt_frozenxid =
+			gtt_get_oldest_frozenxids_in_current_database(NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid))
+		{
+			safe_age = oldest_gtt_frozenxid + vacuum_gtt_defer_check_age;
+			if (safe_age < FirstNormalTransactionId)
+				safe_age += FirstNormalTransactionId;
+
+			/*
+			 * We tolerate that the minimum age of gtt is less than
+			 * the minimum age of conventional tables, otherwise it will
+			 * throw warning message.
+			 */
+			if (TransactionIdIsNormal(safe_age) &&
+				TransactionIdPrecedes(safe_age, newFrozenXid))
+			{
+				ereport(WARNING,
+						(errmsg("global temporary table oldest relfrozenxid %u is far in the past",
+								oldest_gtt_frozenxid),
+						 errdetail("The oldest relfrozenxid %u in database \"%s\"", newFrozenXid, get_database_name(MyDatabaseId)),
+						 errhint("please consider cleaning up the data in global temporary table to avoid wraparound problems.")));
+			}
+
+			/*
+			 * We need to ensure that the clog required by gtt is not cleand.
+			 */
+			if (TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+				newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
@@ -1934,6 +1983,19 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 		return false;
 	}
 
+	/*
+	 * Skip those global temporary table that are not initialized in
+	 * this session.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel) &&
+		!gtt_storage_attached(RelationGetRelid(rel)))
+	{
+		relation_close(rel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		return false;
+	}
+
 	/*
 	 * Silently ignore tables that are temp tables of other backends ---
 	 * trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 459e9821d08..b94776f80cf 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -543,6 +543,12 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
+	/* Global temporary table are not sensible. */
+	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("views cannot be global temporary because they do not have storage")));
+
 	/*
 	 * If the user didn't explicitly ask for a temporary view, check whether
 	 * we need one implicitly.  We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 549d9eb6963..370e4d09d1d 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -784,6 +784,10 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 		if (isTempNamespace(get_rel_namespace(rte->relid)))
 			continue;
 
+		/* Global temp table is one kind of temp table */
+		if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
 	}
 
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 9df1f81ea89..2207dccf274 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -50,6 +50,7 @@
 #include "access/table.h"
 #include "access/tableam.h"
 #include "access/transam.h"
+#include "catalog/storage_gtt.h"
 #include "executor/executor.h"
 #include "executor/execPartition.h"
 #include "jit/jit.h"
@@ -832,7 +833,7 @@ ExecGetRangeTableRelation(EState *estate, Index rti)
  */
 void
 ExecInitResultRelation(EState *estate, ResultRelInfo *resultRelInfo,
-					   Index rti)
+					   Index rti, CmdType operation)
 {
 	Relation	resultRelationDesc;
 
@@ -843,6 +844,9 @@ ExecInitResultRelation(EState *estate, ResultRelInfo *resultRelInfo,
 					  NULL,
 					  estate->es_instrument);
 
+	/* Check and init global temporary table storage in this session */
+	gtt_init_storage(operation, resultRelationDesc);
+
 	if (estate->es_result_relations == NULL)
 		estate->es_result_relations = (ResultRelInfo **)
 			palloc0(estate->es_range_table_size * sizeof(ResultRelInfo *));
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 5ec699a9bd1..1401d999b40 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -38,6 +38,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -2754,13 +2755,13 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	{
 		mtstate->rootResultRelInfo = makeNode(ResultRelInfo);
 		ExecInitResultRelation(estate, mtstate->rootResultRelInfo,
-							   node->rootRelation);
+							   node->rootRelation, operation);
 	}
 	else
 	{
 		mtstate->rootResultRelInfo = mtstate->resultRelInfo;
 		ExecInitResultRelation(estate, mtstate->resultRelInfo,
-							   linitial_int(node->resultRelations));
+							   linitial_int(node->resultRelations), operation);
 	}
 
 	/* set up epqstate with dummy subplan data for the moment */
@@ -2788,7 +2789,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 		if (resultRelInfo != mtstate->rootResultRelInfo)
 		{
-			ExecInitResultRelation(estate, resultRelInfo, resultRelation);
+			ExecInitResultRelation(estate, resultRelInfo, resultRelation, operation);
 
 			/*
 			 * For child result relations, store the root result relation
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 169b1d53fc8..07a616d626b 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -48,7 +48,7 @@
 #include "partitioning/partprune.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/lsyscache.h"
-
+#include "utils/rel.h"
 
 /* results of subquery_is_pushdown_safe */
 typedef struct pushdown_safety_info
@@ -619,7 +619,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			if (RelpersistenceTsTemp(get_rel_persistence(rte->relid)))
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index bd09f85aea1..a56f0b8ceee 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6071,7 +6071,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
-	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+	if (RELATION_IS_TEMP(heap) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index a5002ad8955..22f64506caa 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -31,6 +31,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
 #include "catalog/pg_statistic_ext_data.h"
+#include "catalog/storage_gtt.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -222,6 +223,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				continue;
 			}
 
+			/* Ignore empty index for global temporary table in this session */
+			if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+				!gtt_storage_attached(RelationGetRelid(indexRelation)))
+			{
+				index_close(indexRelation, NoLock);
+				continue;
+			}
+
 			/*
 			 * If the index is valid, but cannot yet be used, ignore it; but
 			 * mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6ac2e9ce237..2d62d0a4036 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2902,7 +2902,7 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
 		 * creation query. It would be hard to refresh data or incrementally
 		 * maintain it if a source disappeared.
 		 */
-		if (isQueryUsingTempRelation(query))
+		if (isQueryUsingTempRelation(query) || isQueryUsingGlobalTempRelation(query))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("materialized views must not use temporary tables or views")));
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a03b33b53bd..7a50dd98052 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3435,17 +3435,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; }
@@ -11878,19 +11872,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 cb9e177b5e5..e333f427740 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -82,6 +82,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 isQueryUsingGlobalTempRelation_walker(Node *node, void *context);
 
 
 /*
@@ -3665,3 +3666,52 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/*
+ * Like function isQueryUsingTempRelation_walker
+ * return true if any relation underlying
+ * the query is a global temporary table.
+ */
+static bool
+isQueryUsingGlobalTempRelation_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 = table_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				table_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 isQueryUsingGlobalTempRelation_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  isQueryUsingGlobalTempRelation_walker,
+								  context);
+}
+
+/* Check if the query uses global temporary table */
+bool
+isQueryUsingGlobalTempRelation(Query *query)
+{
+	return isQueryUsingGlobalTempRelation_walker((Node *) query, NULL);
+}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 99efa26ce4a..08f36df50be 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -447,6 +447,13 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->options = seqoptions;
 
+	/*
+	 * If a sequence is bound to a global temporary table, then the sequence
+	 * must been "global temporary"
+	 */
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
 	 * to the list rather than append; in case a user supplied their own AS
@@ -3329,6 +3336,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		cxt.isforeign = false;
 	}
 	cxt.relation = stmt->relation;
+	/* Set the relpersistence to the context */
+	cxt.relation->relpersistence = RelationGetRelPersistence(rel);
 	cxt.rel = rel;
 	cxt.inhRelations = NIL;
 	cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 681ef91b81e..4ea4c5bfcc7 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2116,6 +2116,14 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/*
+			 * Aotuvacuum cannot vacuum the private data stored in each session
+			 * that belongs to global temporary table, so skip them.
+			 */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2182,7 +2190,7 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		if (RelpersistenceTsTemp(classForm->relpersistence))
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index f5459c68f89..39b82a0b389 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -38,6 +38,7 @@
 #include "access/xlogutils.h"
 #include "catalog/catalog.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "executor/instrument.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
@@ -2935,7 +2936,15 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
-	if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind))
+	/*
+	 * Returns 0 if this global temporary table is not initialized in this session.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(RelationGetRelid(relation)))
+	{
+		return 0;
+	}
+	else if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind))
 	{
 		/*
 		 * Not every table AM uses BLCKSZ wide fixed size blocks.
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index cd4ebe2fc5e..43384097d9b 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -23,6 +23,7 @@
 #include "access/syncscan.h"
 #include "access/twophase.h"
 #include "access/xlogrecovery.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -145,6 +146,7 @@ CalculateShmemSize(int *num_semaphores)
 	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
@@ -249,6 +251,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* For global temporary table shared hashtable */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 13d192ec2b4..c5fd02c9c7f 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -65,6 +65,7 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/guc.h"
 
 #define UINT32_ACCESS_ONCE(var)		 ((uint32)(*((volatile uint32 *)&(var))))
 
@@ -5175,3 +5176,66 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * Search all active session to get db level oldest frozenxid
+ * for global temporary table.
+ *
+ * Pids and Xids are used to store the session level oldest frozenxid if specified
+ */
+TransactionId
+gtt_get_oldest_frozenxids_in_current_database(List **pids, List **xids)
+{
+	ProcArrayStruct		*arrayP = NULL;
+	TransactionId		result = InvalidTransactionId;
+	int			index = 0;
+	int			i = 0;
+	uint8		flags = 0;
+
+	/* return 0 if feature is disabled */
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	/* Disable in standby node */
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	arrayP = procArray;
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		PGPROC	   *proc = &allProcs[pgprocno];
+		uint8		statusFlags = ProcGlobal->statusFlags[index];
+		TransactionId	gtt_frozenxid = InvalidTransactionId;
+
+		if (statusFlags & flags)
+			continue;
+
+		/* Fetch all session level frozenxid that is belonging to current database */
+		gtt_frozenxid = proc->gtt_frozenxid;
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = gtt_frozenxid;
+			else if (TransactionIdPrecedes(gtt_frozenxid, result))
+				result = gtt_frozenxid;
+
+			/* save backend pid and session level oldest relfrozenxid */
+			if (pids)
+				*pids = lappend_int(*pids, proc->pid);
+
+			if (xids)
+				*xids = lappend_oid(*xids, gtt_frozenxid);
+
+			i++;
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	return result;
+}
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 7b0dea4abec..ab0d2031e86 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -176,7 +176,9 @@ static const char *const BuiltinTrancheNames[] = {
 	/* LWTRANCHE_PARALLEL_APPEND: */
 	"ParallelAppend",
 	/* LWTRANCHE_PER_XACT_PREDICATE_LIST: */
-	"PerXactPredicateList"
+	"PerXactPredicateList",
+	/* LWTRANCHE_GLOBAL_TEMP_TABLE_CTL */
+	"GlobalTempTableControl"
 };
 
 StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 90283f8a9f5..d2e1e3e94e1 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -392,6 +392,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyProc->delayChkpt = false;
 	MyProc->statusFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 3a2f2e1f99d..9a6e7cee244 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -982,6 +982,13 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			/*
+			 * For global temporary table ,each backend has its own storage,
+			 * also only sees its own storage. Use Backendid to identify them.
+			 */
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 1fbb0b28c3b..637ff7f7e43 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -108,6 +108,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -5117,12 +5118,26 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								/* For global temporary table, get statistic data from localhash */
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -5365,15 +5380,28 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -6817,6 +6845,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;
@@ -6834,6 +6863,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6845,6 +6882,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;
@@ -6860,6 +6899,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* For global temporary table, get statistic data from localhash */
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7778,6 +7825,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))
@@ -7790,6 +7839,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -7802,6 +7860,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.
@@ -7821,6 +7881,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				/* For global temporary table, get statistic data from localhash */
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index feef9998634..ebaad33a698 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -3113,6 +3114,19 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		/* For global temporary table, get statistic data from localhash */
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index fccffce5729..1d2fdb3760e 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -65,6 +65,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/policy.h"
 #include "commands/publicationcmds.h"
 #include "commands/trigger.h"
@@ -1153,6 +1154,36 @@ retry:
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				BlockNumber     relpages = 0;
+				double          reltuples = 0;
+				BlockNumber     relallvisible = 0;
+				TransactionId	relfrozenxid = InvalidTransactionId;
+				MultiXactId 	relminmxid = InvalidMultiXactId;
+
+				relation->rd_backend = BackendIdForTempRelations();
+				relation->rd_islocaltemp = false;
+
+				/*
+				 * For global temporary table
+				 * get session level relstats from localhash
+				 * and set it to local relcache
+				 */
+				get_gtt_relstats(RelationGetRelid(relation),
+								 &relpages, &reltuples, &relallvisible,
+								 &relfrozenxid, &relminmxid);
+
+				relation->rd_rel->relpages = (int32)relpages;
+				relation->rd_rel->reltuples = (float4)reltuples;
+				relation->rd_rel->relallvisible = (int32)relallvisible;
+				if (TransactionIdIsNormal(relfrozenxid))
+					relation->rd_rel->relfrozenxid = relfrozenxid;
+
+				if (MultiXactIdIsValid(relminmxid))
+					relation->rd_rel->relminmxid = relminmxid;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -1213,6 +1244,15 @@ retry:
 	else
 		Assert(relation->rd_rel->relam == InvalidOid);
 
+	/*
+	 * For one global temporary table,
+	 * other session may created one index, that triggers relcache reload for this session.
+	 * If table already has data at this session, to avoid rebuilding index,
+	 * accept the structure of the index but set the local state to indisvalid.
+	 */
+	if (relation->rd_rel->relkind == RELKIND_INDEX)
+		gtt_correct_index_session_state(relation);
+
 	/* extract reloptions if any */
 	RelationParseRelOptions(relation, pg_class_tuple);
 
@@ -1336,7 +1376,23 @@ RelationInitPhysicalAddr(Relation relation)
 			heap_freetuple(phys_tuple);
 		}
 
-		relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		/*
+		 * For global temporary table,
+		 * the latest relfilenode is saved in localHash(see RelationSetNewRelfilenode()),
+		 * get it and put it to local relcache.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(relation))
+		{
+			Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+			if (OidIsValid(newrelnode) &&
+				newrelnode != relation->rd_rel->relfilenode)
+				relation->rd_node.relNode = newrelnode;
+			else
+				relation->rd_node.relNode = relation->rd_rel->relfilenode;
+		}
+		else
+			relation->rd_node.relNode = relation->rd_rel->relfilenode;
 	}
 	else
 	{
@@ -2290,6 +2346,14 @@ RelationReloadIndexInfo(Relation relation)
 							   HeapTupleHeaderGetXmin(tuple->t_data));
 
 		ReleaseSysCache(tuple);
+
+		/*
+		 * For one global temporary table,
+		 * other session may created one index, that triggers relcache reload for this session.
+		 * If table already has data at this session, to avoid rebuilding index,
+		 * accept the structure of the index but set the local state to indisvalid.
+		 */
+		gtt_correct_index_session_state(relation);
 	}
 
 	/* Okay, now it's valid again */
@@ -3570,6 +3634,10 @@ RelationBuildLocalRelation(const char *relname,
 			rel->rd_backend = BackendIdForTempRelations();
 			rel->rd_islocaltemp = true;
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			rel->rd_backend = BackendIdForTempRelations();
+			rel->rd_islocaltemp = false;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3683,6 +3751,13 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
 
+	/*
+	 * For global temporary table, storage information for each session is
+	 * maintained locally, not in pg_class.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+		return GlobalTempRelationSetNewRelfilenode(relation);
+
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
@@ -3726,7 +3801,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);
 	}
 	else
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index bf7ec0d4666..90fe0c6506e 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -45,6 +45,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "commands/prepare.h"
 #include "commands/tablespace.h"
@@ -157,6 +158,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temporary table feature.
+ * table schema are still saved in catalog.
+ *
+ * num > 0 means allows the database to manage multiple active tables at the same time.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2145,6 +2158,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("Sets the amount of time to wait before forcing a "
@@ -2716,6 +2738,16 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"vacuum_gtt_defer_check_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("The defer check age of GTT, used to check expired data after vacuum."),
+			NULL
+		},
+		&vacuum_gtt_defer_check_age,
+		10000, 0, 1000000,
+		NULL, NULL, NULL
+	},
+
 	/*
 	 * See also CheckRequiredParameterValues() if this parameter changes
 	 */
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 4a094bb38be..c7f97d2dcef 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -655,6 +655,10 @@
 					# autovacuum, -1 means use
 					# vacuum_cost_limit
 
+#max_active_global_temporary_table = 1000   # default active global temporary table
+                                            # -1 means disable gLobal temporary table
+
+#vacuum_gtt_defer_check_age = 10000         # global temporary table age check
 
 #------------------------------------------------------------------------------
 # CLIENT CONNECTION DEFAULTS
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index e69dcf8a484..2f3e43d0911 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2538,6 +2538,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
 		dopt->no_unlogged_table_data)
 		return;
 
+	/* Don't dump data in global temporary table/sequence */
+	if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		return;
+
 	/* Check that the data is not explicitly excluded */
 	if (simple_oid_list_member(&tabledata_exclude_oids,
 							   tbinfo->dobj.catId.oid))
@@ -14973,6 +14977,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
 		char	   *foreign = "";
+		char	   *table_type = NULL;
 
 		/*
 		 * Set reltypename, and collect any relkind-specific data that we
@@ -15048,9 +15053,15 @@ dumpTableSchema(Archive *fout, const 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);
 
@@ -15418,6 +15429,15 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 			}
 		}
 
+		/*
+		 * Transaction information for the global temporary table is not stored
+		 * in the pg_class.
+		 */
+		if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			Assert(tbinfo->frozenxid == 0);
+			Assert(tbinfo->minmxid == 0);
+		}
 		/*
 		 * In binary_upgrade mode, arrange to restore the old relfrozenxid and
 		 * relminmxid of all vacuumable relations.  (While vacuum.c processes
@@ -15425,7 +15445,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		 * of other relations; so this "if" lacks RELKIND_TOASTVALUE, and the
 		 * child toast table is handled below.)
 		 */
-		if (dopt->binary_upgrade &&
+		else if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
 			 tbinfo->relkind == RELKIND_MATVIEW))
 		{
@@ -16448,6 +16468,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	PQExpBuffer query = createPQExpBuffer();
 	PQExpBuffer delqry = createPQExpBuffer();
 	char	   *qseqname;
+	bool	   global_temp_seq = false;
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
@@ -16457,9 +16478,12 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
-						  "FROM pg_catalog.pg_sequence "
-						  "WHERE seqrelid = '%u'::oid",
+						  "seqcache, seqcycle, "
+						  "c.relpersistence "
+						  "FROM pg_catalog.pg_sequence s, "
+						  "pg_catalog.pg_class c "
+						  "WHERE seqrelid = '%u'::oid "
+						  "and s.seqrelid = c.oid",
 						  tbinfo->dobj.catId.oid);
 	}
 	else
@@ -16496,6 +16520,9 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
 
+	if (fout->remoteVersion >= 150000)
+		global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
 	if (strcmp(seqtype, "smallint") == 0)
@@ -16573,9 +16600,13 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	}
 	else
 	{
-		appendPQExpBuffer(query,
-						  "CREATE SEQUENCE %s\n",
-						  fmtQualifiedDumpable(tbinfo));
+		appendPQExpBuffer(query, "CREATE ");
+
+		if (global_temp_seq)
+			appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+		appendPQExpBuffer(query, "SEQUENCE %s\n",
+								  fmtQualifiedDumpable(tbinfo));
 
 		if (strcmp(seqtype, "bigint") != 0)
 			appendPQExpBuffer(query, "    AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 019bcb6c7b7..df30fa0dd7e 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -88,7 +88,7 @@ check_and_dump_old_cluster(bool live_check)
 		start_postmaster(&old_cluster, true);
 
 	/* Extract a list of databases and tables from the old cluster */
-	get_db_and_rel_infos(&old_cluster);
+	get_db_and_rel_infos(&old_cluster, true);
 
 	init_tablespaces();
 
@@ -174,7 +174,7 @@ check_and_dump_old_cluster(bool live_check)
 void
 check_new_cluster(void)
 {
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, false);
 
 	check_new_cluster_is_empty();
 	check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 69ef23119f5..14e0fc5ef75 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
 									  bool is_new_db);
 static void free_db_and_rel_infos(DbInfoArr *db_arr);
 static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_global_temp);
 static void free_rel_infos(RelInfoArr *rel_arr);
 static void print_db_infos(DbInfoArr *dbinfo);
 static void print_rel_infos(RelInfoArr *rel_arr);
@@ -269,9 +269,11 @@ report_unmatched_relation(const RelInfo *rel, const DbInfo *db, bool is_new_db)
  *
  * higher level routine to generate dbinfos for the database running
  * on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
  */
 void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_global_temp)
 {
 	int			dbnum;
 
@@ -281,7 +283,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
 	get_db_infos(cluster);
 
 	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
-		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+		get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_global_temp);
 
 	if (cluster == &old_cluster)
 		pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -365,7 +367,7 @@ get_db_infos(ClusterInfo *cluster)
  * This allows later processing to match up old and new databases efficiently.
  */
 static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_global_temp)
 {
 	PGconn	   *conn = connectToServer(cluster,
 									   dbinfo->db_name);
@@ -408,8 +410,17 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
 			 "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
 			 "         ON c.relnamespace = n.oid "
 			 "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-			 CppAsString2(RELKIND_MATVIEW) ") AND "
+			 CppAsString2(RELKIND_MATVIEW) ") AND ");
+
+	if (skip_global_temp)
+	{
+		/* exclude global temp tables */
+		snprintf(query + strlen(query), sizeof(query) - strlen(query),
+			"    relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND ");
+	}
+
 	/* exclude possible orphaned temp tables */
+	snprintf(query + strlen(query), sizeof(query) - strlen(query),
 			 "    ((n.nspname !~ '^pg_temp_' AND "
 			 "      n.nspname !~ '^pg_toast_temp_' AND "
 			 "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index ecb3e1f6474..5e33d9d8ca9 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -476,7 +476,7 @@ create_new_objects(void)
 		set_frozenxids(true);
 
 	/* update new_cluster info now that we have objects in the databases */
-	get_db_and_rel_infos(&new_cluster);
+	get_db_and_rel_infos(&new_cluster, true);
 }
 
 /*
@@ -714,7 +714,10 @@ set_frozenxids(bool minmxid_only)
 									  "UPDATE	pg_catalog.pg_class "
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
-									  "WHERE	relkind IN ("
+									  "WHERE "
+			/* exclude global temp tables */
+									  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+									  "relkind IN ("
 									  CppAsString2(RELKIND_RELATION) ", "
 									  CppAsString2(RELKIND_MATVIEW) ", "
 									  CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -725,7 +728,10 @@ set_frozenxids(bool minmxid_only)
 								  "UPDATE	pg_catalog.pg_class "
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
-								  "WHERE	relkind IN ("
+								  "WHERE "
+		/* exclude global temp tables */
+								  " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+								  "relkind IN ("
 								  CppAsString2(RELKIND_RELATION) ", "
 								  CppAsString2(RELKIND_MATVIEW) ", "
 								  CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index ca86c112924..7cb57a01b8f 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -384,7 +384,7 @@ void		check_loadable_libraries(void);
 FileNameMap *gen_db_file_maps(DbInfo *old_db,
 							  DbInfo *new_db, int *nmaps, const char *old_pgdata,
 							  const char *new_pgdata);
-void		get_db_and_rel_infos(ClusterInfo *cluster);
+void		get_db_and_rel_infos(ClusterInfo *cluster, bool skip_global_temp);
 
 /* option.c */
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index e3382933d98..1779787b56c 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3791,7 +3791,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		 * Show whether a relation is permanent, temporary, or unlogged.
 		 */
 		appendPQExpBuffer(&buf,
-						  ",\n  CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+						  ",\n  CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"",
+						  gettext_noop("session"),
 						  gettext_noop("permanent"),
 						  gettext_noop("temporary"),
 						  gettext_noop("unlogged"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 6957567264a..0c14beac935 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1181,6 +1181,8 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN TABLE", NULL, NULL, NULL},
 	{"FUNCTION", NULL, NULL, Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
+	{"GLOBAL", NULL, NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE
+																	* ... */
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"LARGE OBJECT", NULL, NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
@@ -2775,6 +2777,9 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
+	/* CREATE GLOBAL TEMP/TEMPORARY*/
+	else if (Matches("CREATE", "GLOBAL"))
+		COMPLETE_WITH("TEMP", "TEMPORARY");
 
 	/* CREATE FOREIGN TABLE */
 	else if (Matches("CREATE", "FOREIGN", "TABLE", MatchAny))
@@ -3019,6 +3024,8 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
+	else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY"))
+		COMPLETE_WITH("TABLE", "SEQUENCE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index c4757bda2d5..d99454c984f 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -86,9 +86,9 @@ extern Oid	heap_create_with_catalog(const char *relname,
 
 extern void heap_drop_with_catalog(Oid relid);
 
-extern void heap_truncate(List *relids);
+extern void heap_truncate(List *relids, bool is_global_temp);
 
-extern void heap_truncate_one_rel(Relation rel);
+extern void heap_truncate_one_rel(Relation rel, LOCKMODE lockmode);
 
 extern void heap_truncate_check_FKs(List *relations, bool tempTables);
 
@@ -161,5 +161,5 @@ extern void StorePartitionKey(Relation rel,
 extern void RemovePartitionKeyByRelId(Oid relid);
 extern void StorePartitionBound(Relation rel, Relation parent,
 								PartitionBoundSpec *bound);
-
+extern void CheckGlobalTempTableNotInUse(Relation rel, const char *stmt);
 #endif							/* HEAP_H */
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index a1d6e3b645f..441cd440493 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -158,7 +158,7 @@ extern void reindex_index(Oid indexId, bool skip_constraint_checks,
 #define REINDEX_REL_FORCE_INDEXES_UNLOGGED	0x08
 #define REINDEX_REL_FORCE_INDEXES_PERMANENT 0x10
 
-extern bool reindex_relation(Oid relid, int flags, ReindexParams *params);
+extern bool reindex_relation(Oid relid, int flags, ReindexParams *params, LOCKMODE lockmode);
 
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 304e8c18d52..e23cb49c010 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -172,6 +172,7 @@ DECLARE_INDEX(pg_class_tblspc_relfilenode_index, 3455, ClassTblspcRelfilenodeInd
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 7f1ee97f55c..67f6ea3ef62 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5735,6 +5735,40 @@
   proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_xact_function_self_time' },
 
+# For global temporary table
+{ oid => '9874',
+  descr => 'List local statistics for global temporary table',
+  proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+  proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+  proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+  prosrc => 'pg_get_gtt_statistics' },
+{ oid => '9875',
+  descr => 'List local relstats for global temporary table',
+  proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+  proargmodes => '{i,o,o,o,o,o,o}',
+  proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+  prosrc => 'pg_get_gtt_relstats' },
+{ oid => '9876',
+  descr => 'List attached pid for one global temporary table',
+  proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+  proallargtypes => '{oid,oid,int4}',
+  proargmodes => '{i,o,o}',
+  proargnames => '{relid,relid,pid}',
+  prosrc => 'pg_gtt_attached_pid' },
+{ oid => '9877',
+  descr => 'List those backends that have used global temporary table',
+  proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+  prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+  proallargtypes => '{int4,xid}',
+  proargmodes => '{o,o}',
+  proargnames => '{pid,relfrozenxid}',
+  prosrc => 'pg_list_gtt_relfrozenxids' },
+
 { oid => '3788',
   descr => 'statistics: timestamp of the current statistics snapshot',
   proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 9ffc7419131..ca5bcaa3f97 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,7 +22,7 @@
 /* GUC variables */
 extern int	wal_skip_threshold;
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationPreTruncate(Relation rel);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 00000000000..1b6d4ef2a8d
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,46 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern int		vacuum_gtt_defer_check_age;
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void gtt_update_relstats(Relation relation, BlockNumber relpages, double reltuples,
+									BlockNumber relallvisible, TransactionId relfrozenxid,
+									TransactionId relminmxid);
+extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+extern void gtt_index_force_enable(Relation index);
+extern void gtt_correct_index_session_state(Relation index);
+extern void gtt_init_storage(CmdType operation, Relation relation);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+extern void index_update_gtt_relstats(Relation rel, bool hasindex, double reltuples, bool isreindex);
+extern void vac_update_gtt_relstats(Relation relation, BlockNumber num_pages, double num_tuples,
+										BlockNumber num_all_visible_pages, bool hasindex, TransactionId frozenxid,
+										MultiXactId minmulti, bool in_outer_xact);
+extern void GlobalTempRelationSetNewRelfilenode(Relation relation);
+#endif							/* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index 9fecc41954e..c516851d09c 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -66,5 +66,6 @@ extern void seq_redo(XLogReaderState *rptr);
 extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
 extern const char *seq_identify(uint8 info);
 extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_sequence(Relation rel);
 
 #endif							/* SEQUENCE_H */
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 5d4037f26e8..28598b06c1e 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -86,7 +86,7 @@ extern void find_composite_type_dependencies(Oid typeOid,
 
 extern void check_of_type(HeapTuple typetuple);
 
-extern void register_on_commit_action(Oid relid, OnCommitAction action);
+extern void register_on_commit_action(Oid relid, OnCommitAction action, bool is_gloal_temp);
 extern void remove_on_commit_action(Oid relid);
 
 extern void PreCommit_on_commit_actions(void);
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 344399f6a8a..ae7628d692b 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -575,7 +575,7 @@ exec_rt_fetch(Index rti, EState *estate)
 
 extern Relation ExecGetRangeTableRelation(EState *estate, Index rti);
 extern void ExecInitResultRelation(EState *estate, ResultRelInfo *resultRelInfo,
-								   Index rti);
+								   Index rti, CmdType operation);
 
 extern int	executor_errposition(EState *estate, int location);
 
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 06dc27995ba..197e367f4e4 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -119,5 +119,6 @@ extern const NameData *attnumAttName(Relation rd, int attid);
 extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
+extern bool isQueryUsingGlobalTempRelation(Query *query);
 
 #endif							/* PARSE_RELATION_H */
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 124977cf7e3..9b64389b08a 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -189,6 +189,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_SHARED_TIDBITMAP,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_PER_XACT_PREDICATE_LIST,
+	LWTRANCHE_GLOBAL_TEMP_TABLE_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index a58888f9e90..1bbf31923c0 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -164,6 +164,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId 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 e03692053ee..d948c0383b7 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -94,4 +94,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern TransactionId gtt_get_oldest_frozenxids_in_current_database(List **pids, List **xids);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ea774968f07..264099cfc9b 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -288,6 +288,9 @@ extern int	tcp_user_timeout;
 extern bool trace_sort;
 #endif
 
+/* global temporary table */
+extern int	max_active_gtt;
+
 /*
  * Functions exported by guc.c
  */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 3b4ab65ae20..41d79809f5b 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -57,7 +57,7 @@ typedef struct RelationData
 	SMgrRelation rd_smgr;		/* cached file handle, or NULL */
 	int			rd_refcnt;		/* reference count */
 	BackendId	rd_backend;		/* owning backend id, if temporary relation */
-	bool		rd_islocaltemp; /* rel is a temp rel of this session */
+	bool		rd_islocaltemp;	/* rel is a temp rel of this session */
 	bool		rd_isnailed;	/* rel is nailed in cache */
 	bool		rd_isvalid;		/* relcache entry is valid */
 	bool		rd_indexvalid;	/* is rd_indexlist valid? (also rd_pkindex and
@@ -327,6 +327,7 @@ typedef struct StdRdOptions
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
 	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
+	bool		on_commit_delete_rows;	/* global temp table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -609,11 +610,13 @@ RelationGetSmgr(Relation rel)
  *		True if relation's pages are stored in local buffers.
  */
 #define RelationUsesLocalBuffers(relation) \
-	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RELATION_IS_LOCAL
- *		If a rel is either temp or newly created in the current transaction,
+ *		If a rel is either local temp or global temp relation
+ *		or newly created in the current transaction,
  *		it can be assumed to be accessible only to the current backend.
  *		This is typically used to decide that we can skip acquiring locks.
  *
@@ -621,6 +624,7 @@ RelationGetSmgr(Relation rel)
  */
 #define RELATION_IS_LOCAL(relation) \
 	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
 	 (relation)->rd_createSubid != InvalidSubTransactionId)
 
 /*
@@ -633,6 +637,30 @@ RelationGetSmgr(Relation rel)
 	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
 	 !(relation)->rd_islocaltemp)
 
+/*
+ * RELATION_IS_TEMP_ON_CURRENT_SESSION
+ *		Test a rel is either local temp relation of this session
+ *		or global temp relation.
+ */
+#define RELATION_IS_TEMP_ON_CURRENT_SESSION(relation) \
+	((relation)->rd_islocaltemp || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RELATION_IS_TEMP
+ *		Test a rel is local temporary relation or global temporary relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RelpersistenceTsTemp
+ *		Test a relpersistence is local temp relation or global temporary relation.
+ */
+#define RelpersistenceTsTemp(relpersistence) \
+	(relpersistence == RELPERSISTENCE_TEMP || \
+	 relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 
 /*
  * RelationIsScannable
@@ -678,6 +706,19 @@ RelationGetSmgr(Relation rel)
 	 (relation)->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&	\
 	 !IsCatalogRelation(relation))
 
+/* For global temporary table */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/* Get on commit clause value only for global temporary table */
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+/* Get relpersistence for relation */
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
-- 
2.32.0 (Apple Git-132)

0002-gtt-v67-doc.patchapplication/octet-stream; name=0002-gtt-v67-doc.patchDownload
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 473a0a4aeb..e510bde8ac 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -169,32 +169,67 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     <listitem>
      <para>
       If specified, the table is created as a temporary table.
-      Temporary tables are automatically dropped at the end of a
-      session, or optionally at the end of the current transaction
-      (see <literal>ON COMMIT</literal> below).  The default
-      search_path includes the temporary schema first and so identically
-      named existing permanent tables are not chosen for new plans
+      Optionally, <literal>GLOBAL</literal> or <literal>LOCAL</literal>
+      can be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
+      They represent two types of temporary tables supported by <productname>PostgreSQL</productname>:
+      global temporary table and local temporary table. Without specified
+      GLOBAL or LOCAL, a local temporary table is created by default.
+     </para>
+
+    <para>
+     Both types of temporary tables’ data are truncated at the
+     end of a session or optionally at the end of the current transaction.
+     (see <literal>ON COMMIT</literal> below). For global temporary table,
+     its schema is reserved and reused by future sessions or transactions.
+     For local temporary table, both its data and its schema are dropped.
+    </para>
+
+    <variablelist>
+     <varlistentry>
+      <term><literal>Global Temporary Table</literal></term>
+      <listitem>
+       <para>
+        Global temporary table are defined just once and automatically exist
+        (starting with empty contents) in every session that needs them.
+        The schema definition of temporary tables is persistent and shared among sessions.
+        However, the data in temporary tables are kept private to sessions themselves,
+        even though they use same name and same schema.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>Local Temporary Table</literal></term>
+     <listitem>
+     <para>
+      Local temporary table are automatically dropped at the end of a
+      session (include schema and data). Future sessions need to create
+      their own temporary tables when they are used.
+     </para>
+     <para>
+      The default search_path includes the temporary schema first and so
+      identically named existing permanent tables are not chosen for new plans
       while the temporary table exists, unless they are referenced
       with schema-qualified names. Any indexes created on a temporary
       table are automatically temporary as well.
      </para>
+     </listitem>
+     </varlistentry>
+    </variablelist>
 
-     <para>
-      The <link linkend="autovacuum">autovacuum daemon</link> cannot
-      access and therefore cannot vacuum or analyze temporary tables.
-      For this reason, appropriate vacuum and analyze operations should be
-      performed via session SQL commands.  For example, if a temporary
-      table is going to be used in complex queries, it is wise to run
-      <command>ANALYZE</command> on the temporary table after it is populated.
-     </para>
+    <para>
+     The <link linkend="autovacuum">autovacuum daemon</link> cannot
+     access and therefore cannot vacuum or analyze temporary tables.
+     For this reason, appropriate vacuum and analyze operations should be
+     performed via session SQL commands.  For example, if a temporary
+     table is going to be used in complex queries, it is wise to run
+     <command>ANALYZE</command> on the temporary table after it is populated.
+    </para>
+    <para>
+     The Temporary table resembles the SQL standard, but has some differences.
+     see <xref linkend="sql-createtable-compatibility"/> below.
+    </para>
 
-     <para>
-      Optionally, <literal>GLOBAL</literal> or <literal>LOCAL</literal>
-      can be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
-      This presently makes no difference in <productname>PostgreSQL</productname>
-      and is deprecated; see
-      <xref linkend="sql-createtable-compatibility"/> below.
-     </para>
     </listitem>
    </varlistentry>
 
@@ -2133,13 +2168,17 @@ CREATE TABLE cities_partdef
    <title>Temporary Tables</title>
 
    <para>
-    Although the syntax of <literal>CREATE TEMPORARY TABLE</literal>
-    resembles that of the SQL standard, the effect is not the same.  In the
-    standard,
-    temporary tables are defined just once and automatically exist (starting
-    with empty contents) in every session that needs them.
-    <productname>PostgreSQL</productname> instead
-    requires each session to issue its own <literal>CREATE TEMPORARY
+    Although the syntax of <literal>CREATE GLOBAL/LOCAL TEMPORARY TABLE</literal>
+    resembles that of the SQL standard, the effect is not the same.
+    The global temporary table follows the SQL standards while local temporary
+    table does not.
+   </para>
+
+   <para>
+    First, in the standard, both global and local temporary tables are defined just
+    once and automatically exist (starting with empty contents) in every session
+    that needs them. For local temporary tables, <productname>PostgreSQL</productname>
+    instead requires each session to issue its own <literal>CREATE LOCAL TEMPORARY
     TABLE</literal> command for each temporary table to be used.  This allows
     different sessions to use the same temporary table name for different
     purposes, whereas the standard's approach constrains all instances of a
@@ -2147,29 +2186,14 @@ CREATE TABLE cities_partdef
    </para>
 
    <para>
-    The standard's definition of the behavior of temporary tables is
-    widely ignored.  <productname>PostgreSQL</productname>'s behavior
-    on this point is similar to that of several other SQL databases.
-   </para>
-
-   <para>
-    The SQL standard also distinguishes between global and local temporary
+    Second, the SQL standard distinguishes between global and local temporary
     tables, where a local temporary table has a separate set of contents for
     each SQL module within each session, though its definition is still shared
-    across sessions.  Since <productname>PostgreSQL</productname> does not
+    across sessions. Since <productname>PostgreSQL</productname> does not
     support SQL modules, this distinction is not relevant in
     <productname>PostgreSQL</productname>.
    </para>
 
-   <para>
-    For compatibility's sake, <productname>PostgreSQL</productname> will
-    accept the <literal>GLOBAL</literal> and <literal>LOCAL</literal> keywords
-    in a temporary table declaration, but they currently have no effect.
-    Use of these keywords is discouraged, since future versions of
-    <productname>PostgreSQL</productname> might adopt a more
-    standard-compliant interpretation of their meaning.
-   </para>
-
    <para>
     The <literal>ON COMMIT</literal> clause for temporary tables
     also resembles the SQL standard, but has some differences.
@@ -2177,7 +2201,8 @@ CREATE TABLE cities_partdef
     default behavior is <literal>ON COMMIT DELETE ROWS</literal>.  However, the
     default behavior in <productname>PostgreSQL</productname> is
     <literal>ON COMMIT PRESERVE ROWS</literal>.  The <literal>ON COMMIT
-    DROP</literal> option does not exist in SQL.
+    DROP</literal> option does not exist in SQL and is not supported by
+    global temporary table.
    </para>
   </refsect2>
 
-- 
2.30.1 (Apple Git-130)

#352Andres Freund
andres@anarazel.de
In reply to: Wenjing Zeng (#351)
Re: [Proposal] Global temporary tables

Hi,

This is a huge thread. Realistically reviewers and committers can't reread
it. I think there needs to be more of a description of how this works included
in the patchset and *why* it works that way. The readme does a bit of that,
but not particularly well.

On 2022-02-25 14:26:47 +0800, Wenjing Zeng wrote:

+++ b/README.gtt.txt
@@ -0,0 +1,172 @@
+Global Temporary Table(GTT)
+=========================================
+
+Feature description
+-----------------------------------------
+
+Previously, temporary tables are defined once and automatically
+exist (starting with empty contents) in every session before using them.

I think for a README "previously" etc isn't good language - if it were
commited, it'd not be understandable anymore. It makes more sense for commit
messages etc.

+Main design ideas
+-----------------------------------------
+In general, GTT and LTT use the same storage and buffer design and
+implementation. The storage files for both types of temporary tables are named
+as t_backendid_relfilenode, and the local buffer is used to cache the data.

What does "named ast_backendid_relfilenode" mean?

+The schema of GTTs is shared among sessions while their data are not. We build
+a new mechanisms to manage those non-shared data and their statistics.
+Here is the summary of changes:
+
+1) CATALOG
+GTTs store session-specific data. The storage information of GTTs'data, their
+transaction information, and their statistics are not stored in the catalog.
+
+2) STORAGE INFO & STATISTICS INFO & TRANSACTION INFO
+In order to maintain durability and availability of GTTs'session-specific data,
+their storage information, statistics, and transaction information is managed
+in a local hash table tt_storage_local_hash.

"maintain durability"? Durable across what? In the context of databases it's
typically about crash safety, but that can't be the case here.

+3) DDL
+Currently, GTT supports almost all table'DDL except CLUSTER/VACUUM FULL.
+Part of the DDL behavior is limited by shared definitions and multiple copies of
+local data, and we added some structures to handle this.
+A shared hash table active_gtt_shared_hash is added to track the state of the
+GTT in a different session. This information is recorded in the hash table
+during the DDL execution of the GTT.
+The data stored in a GTT can only be modified or accessed by owning session.
+The statements that only modify data in a GTT do not need a high level of
+table locking. The operations making those changes include truncate GTT,
+reindex GTT, and lock GTT.

I think you need to introduce a bit more terminology for any of this to make
sense. Sometimes GTT means the global catalog entity, sometimes, like here, it
appears to mean the session specific contents of a GTT.

What state of a GTT in a nother session?

How do GTTs handle something like BEGIN; TRUNCATE some_gtt_table; ROLLBACK;?

+1.2 on commit clause
+LTT's status associated with on commit DELETE ROWS and on commit PRESERVE ROWS
+is not stored in catalog. Instead, GTTs need a bool value on_commit_delete_rows
+in reloptions which is shared among sessions.

Why?

+2.3 statistics info
+1) relpages reltuples relallvisible relfilenode

?

+3 DDL
+3.1. active_gtt_shared_hash
+This is the hash table created in shared memory to trace the GTT files initialized
+in each session. Each hash entry contains a bitmap that records the backendid of
+the initialized GTT file. With this hash table, we know which backend/session
+is using this GTT. Such information is used during GTT's DDL operations.

So there's a separate locking protocol for GTTs that doesn't use the normal
locking infrastructure? Why?

+3.7 CLUSTER GTT/VACUUM FULL GTT
+The current version does not support.

Why?

+4 MVCC commit log(clog) cleanup
+
+The GTT storage file contains transaction information. Queries for GTT data rely
+on transaction information such as clog. The transaction information required by
+each session may be completely different.

Why is transaction information different between sessions? Or does this just
mean that different transaction ids will be accessed?

0003-gtt-v67-implementation.patch
71 files changed, 3167 insertions(+), 195 deletions(-)

This needs to be broken into smaller chunks to be reviewable.

@@ -677,6 +678,14 @@ _bt_getrootheight(Relation rel)
{
Buffer metabuf;

+		/*
+		 * If a global temporary table storage file is not initialized in the
+		 * this session, its index does not have a root page, just returns 0.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
metad = _bt_getmeta(rel, metabuf);

Stuff like this seems not acceptable. Accesses would have to be prevented much
earlier. Otherwise each index method is going to need copies of this logic. I
also doubt that _bt_getrootheight() is the only place that'd need this.

static void
index_update_stats(Relation rel,
bool hasindex,
- double reltuples)
+ double reltuples,
+ bool isreindex)
{
Oid relid = RelationGetRelid(rel);
Relation pg_class;
@@ -2797,6 +2824,13 @@ index_update_stats(Relation rel,
Form_pg_class rd_rel;
bool dirty;

+	/*
+	 * Most of the global Temp table data is updated to the local hash, and reindex
+	 * does not refresh relcache, so call a separate function.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		return index_update_gtt_relstats(rel, hasindex, reltuples, isreindex);
+

So basically every single place in the code that does catalog accesses is
going to need a completely separate implementation for GTTs? That seems
unmaintainable.

+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  The body implementation of Global temparary table.
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *	  See src/backend/catalog/GTT_README for Global temparary table's
+ *	  requirements and design.
+ *
+ *-------------------------------------------------------------------------
+ */

I don't think that path to the readme is correct.

Greetings,

Andres Freund

#353Justin Pryzby
pryzby@telsasoft.com
In reply to: wenjing (#347)
3 attachment(s)
Re: [Proposal] Global temporary tables

I read through this.
Find attached some language fixes. You should be able to apply each "fix"
patch on top of your own local branch with git am, and then squish them
together. Let me know if you have trouble with that.

I think get_seqence_start_value() should be static. (Or otherwise, it should
be in lsyscache.c).

The include added to execPartition.c seems to be unused.

+#define RELATION_IS_TEMP_ON_CURRENT_SESSION(relation) \
+#define RELATION_IS_TEMP(relation) \
+#define RelpersistenceTsTemp(relpersistence) \
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)    \

=> These macros can evaluate their arguments multiple times.
You should add a comment to warn about that. And maybe avoid passing them a
function argument, like: RelpersistenceTsTemp(get_rel_persistence(rte->relid))

+list_all_backend_gtt_frozenxids should return TransactionId not int.
The function name should say "oldest" and not "all" ?

I think the GUC should have a longer name. max_active_gtt is too short for a
global var.

+#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

It's being initialized to MIN, but then the GUC machinery sets it to DEFAULT.
By convention, it should be initialized to default.

fout->remoteVersion >= 140000

=> should say 15

describe.c has gettext_noop("session"), which is a half-truth. The data is
per-session but the table definition is persistent..

You redirect stats from pg_class and pg_statistics to a local hash table.
This is pretty hairy :(
I guess you'd also need to handle pg_statistic_ext and ext_data.
pg_stats doesn't work, since the data isn't in pg_statistic - it'd need to look
at pg_get_gtt_statistics.

I wonder if there's a better way to do it, like updating pg_statistic but
forcing the changes to be rolled back when the session ends... But I think
that would make longrunning sessions behave badly, the same as "longrunning
transactions".

Have you looked at Gilles Darold's GTT extension ?

Attachments:

0002-f-0002-gtt-v64-doc.txttext/x-diff; charset=utf-8Download
From b89f3cc5c78e7b4c3e10ab39ef527b524d0d112d Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sat, 1 Jan 2022 17:02:30 -0600
Subject: [PATCH 02/11] f!0002-gtt-v64-doc.patch

---
 doc/src/sgml/ref/create_table.sgml | 42 ++++++++++++++++--------------
 1 file changed, 22 insertions(+), 20 deletions(-)

diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 408373323c..9cd5fc17f2 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -174,18 +174,18 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
      <para>
       If specified, the table is created as a temporary table.
       Optionally, <literal>GLOBAL</literal> or <literal>LOCAL</literal>
-      can be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
+      may be written before <literal>TEMPORARY</literal> or <literal>TEMP</literal>.
       They represent two types of temporary tables supported by <productname>PostgreSQL</productname>:
-      global temporary table and local temporary table. Without specified
-      GLOBAL or LOCAL, a local temporary table is created by default.
+      global temporary table and local temporary table. If neither
+      GLOBAL or LOCAL is specified, a local temporary table is created by default.
      </para>
 
     <para>
-     Both types of temporary tables’ data are truncated at the
+     Data of both types of temporary tables is truncated at the
      end of a session or optionally at the end of the current transaction.
-     (see <literal>ON COMMIT</literal> below). For global temporary table,
-     its schema is reserved and reused by future sessions or transactions.
-     For local temporary table, both its data and its schema are dropped.
+     (see <literal>ON COMMIT</literal> below). For a global temporary table,
+     its table definition is preserved and reused by future sessions or transactions.
+     For a local temporary table, both its data and its table definition are dropped.
     </para>
 
     <variablelist>
@@ -194,10 +194,10 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
       <listitem>
        <para>
         Global temporary table are defined just once and automatically exist
-        (starting with empty contents) in every session that needs them.
-        The schema definition of temporary tables is persistent and shared among sessions.
-        However, the data in temporary tables are kept private to sessions themselves,
-        even though they use same name and same schema.
+        (initially with empty contents) in every session.
+        The schema definition of a temporary table is persistent and common to all sessions.
+        However, the data in temporary tables is private to each sessions,
+        even though they share the same name and same table definition.
        </para>
       </listitem>
      </varlistentry>
@@ -206,15 +206,15 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
       <term><literal>Local Temporary Table</literal></term>
      <listitem>
      <para>
-      Local temporary table are automatically dropped at the end of a
-      session (include schema and data). Future sessions need to create
-      their own temporary tables when they are used.
+      Local temporary tables are automatically dropped at the end of a
+      session (include table definition and data). Future sessions need to create
+      their own temporary tables before using them.
      </para>
      <para>
-      The default search_path includes the temporary schema first and so
-      identically named existing permanent tables are not chosen for new plans
-      while the temporary table exists, unless they are referenced
-      with schema-qualified names. Any indexes created on a temporary
+      The default <varname>search_path</varname> includes the temporary schema first, and so
+      identically-named, permanent tables of the same name as a temporary table are not chosen for new plans
+      so long as the temporary table exists, unless the permanent table is referenced
+      with a schema-qualified name. Any indexes created on a temporary
       table are automatically temporary as well.
      </para>
      </listitem>
@@ -229,9 +229,11 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
      table is going to be used in complex queries, it is wise to run
      <command>ANALYZE</command> on the temporary table after it is populated.
     </para>
+
     <para>
-     The Temporary table resembles the SQL standard, but has some differences.
-     see <xref linkend="sql-createtable-compatibility"/> below.
+     The temporary table resembles the behavior defined in the SQL standard,
+     but has some differences.
+     See <xref linkend="sql-createtable-compatibility"/> below.
     </para>
 
     </listitem>
-- 
2.17.1

0004-f-0003-gtt-v64-implementation.txttext/x-diff; charset=us-asciiDownload
From 63d4a62043bd1d2af381c632553f85c89de4f36f Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sat, 1 Jan 2022 16:47:14 -0600
Subject: [PATCH 04/11] f!0003-gtt-v64-implementation.patch

---
 src/backend/catalog/heap.c          |  8 ++++----
 src/backend/catalog/index.c         | 24 +++++++++++-----------
 src/backend/catalog/storage_gtt.c   | 32 ++++++++++++++---------------
 src/backend/commands/analyze.c      |  4 ++--
 src/backend/commands/cluster.c      |  6 +++---
 src/backend/commands/sequence.c     |  6 +++---
 src/backend/commands/tablecmds.c    | 10 ++++-----
 src/backend/commands/vacuum.c       |  2 +-
 src/backend/commands/view.c         |  2 +-
 src/backend/parser/analyze.c        |  2 +-
 src/backend/parser/parse_utilcmd.c  |  2 +-
 src/backend/postmaster/autovacuum.c |  4 ++--
 src/backend/storage/ipc/procarray.c |  5 ++++-
 src/backend/utils/adt/dbsize.c      |  2 +-
 src/bin/pg_dump/pg_dump.c           |  2 +-
 src/include/catalog/pg_class.h      |  2 +-
 16 files changed, 58 insertions(+), 55 deletions(-)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 8e5b56f602..d24afa45c1 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2023,15 +2023,15 @@ heap_drop_with_catalog(Oid relid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
 	/*
-	 * Only when other sessions are not using this Global temporary table,
-	 * is it allowed to DROP it.
+	 * It is not allowed to DROP a Global temporary table when other
+	 * sessions are using it
 	 */
 	if (RELATION_IS_GLOBAL_TEMP(rel))
 	{
 		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
 			ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("cannot drop global temporary table %s when other backend attached it.",
+				 errmsg("cannot drop global temporary table %s being accessed by another backend",
 						RelationGetRelationName(rel))));
 	}
 
@@ -3466,7 +3466,7 @@ heap_truncate_one_rel(Relation rel)
 
 	/*
 	 * After the data is cleaned up on the GTT, the transaction information
-	 * for the data(stored in local hash table) is also need reset.
+	 * for the data(stored in local hash table) also needs to be reset.
 	 */
 	if (RELATION_IS_GLOBAL_TEMP(rel))
 		up_gtt_relstats(RelationGetRelid(rel), 0, 0, 0, RecentXmin, GetOldestMultiXactId());
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index be08ba376a..e2483426d2 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -741,11 +741,11 @@ index_create(Relation heapRelation,
 	/* For global temporary table only */
 	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
 	{
-		/* No support create index on global temporary table with concurrent mode */
+		/* No support for creating index on global temporary table with concurrent mode */
 		Assert(!concurrent);
 
 		/*
-		 * For the case that some backend is applied relcache message to create
+		 * For the case that some backend is applied relcache message to create XXX
 		 * an index on a global temporary table, if this table in the current
 		 * backend are not initialized, the creation of index storage on the
 		 * table are also skipped.
@@ -2201,18 +2201,18 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
 	/*
-	 * Allow to drop index on global temporary table when only current
-	 * backend use it.
+	 * Disallow dropping index on global temporary table if another
+	 * backend is using it.
 	 */
 	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation) &&
 		is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
 	{
-		ereport(ERROR,
+		ereport(ERROR, // XXX
 			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 			 errmsg("cannot drop index %s on global temporary table %s",
 					RelationGetRelationName(userIndexRelation), RelationGetRelationName(userHeapRelation)),
-					errdetail("Because the index is created on the global temporary table and other backend attached it."),
-					errhint("Please try detach all sessions using this temporary table and try again.")));
+					errdetail("The index is created on the global temporary table and other backend attached it."),
+					errhint("Detach other sessions using this temporary table and try again.")));
 	}
 
 	/*
@@ -2926,12 +2926,12 @@ index_update_stats(Relation rel,
 		/* For global temporary table */
 		if (is_gtt)
 		{
-			/* Update GTT'statistics into local relcache */
+			/* Update GTT's statistics into local relcache */
 			rel->rd_rel->relpages = (int32) relpages;
 			rel->rd_rel->reltuples = (float4) reltuples;
 			rel->rd_rel->relallvisible = (int32) relallvisible;
 
-			/* Update GTT'statistics into local hashtable */
+			/* Update GTT's statistics into local hashtable */
 			up_gtt_relstats(RelationGetRelid(rel), relpages, reltuples, relallvisible,
 							InvalidTransactionId, InvalidMultiXactId);
 		}
@@ -3066,7 +3066,7 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, progress_index, progress_vals);
 	}
 
-	/* For build index on global temporary table */
+	/* If building index on global temporary table */
 	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
 	{
 		/*
@@ -3075,7 +3075,7 @@ index_build(Relation heapRelation,
 		 */
 		if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
 		{
-			/* Before create init storage, fix the local Relcache first */
+			/* Before creating init storage, fix the local Relcache first */
 			force_enable_gtt_index(indexRelation);
 
 			Assert(gtt_storage_attached(RelationGetRelid(heapRelation)));
@@ -3651,7 +3651,7 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 		if (OidIsValid(params->tablespaceOid))
 			ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("The tablespace of global temporary table can not be changed")));
+				 errmsg("The tablespace of global temporary table cannot be changed")));
 
 		if (!gtt_storage_attached(indexId))
 		{
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
index d50e1fa9af..1660163e99 100644
--- a/src/backend/catalog/storage_gtt.c
+++ b/src/backend/catalog/storage_gtt.c
@@ -1,12 +1,12 @@
 /*-------------------------------------------------------------------------
  *
  * storage_gtt.c
- *	  The body implementation of Global Temparary table.
+ *	  The body implementation of Global Temporary table.
  *
  * IDENTIFICATION
  *	  src/backend/catalog/storage_gtt.c
  *
- *	  See src/backend/catalog/GTT_README for Global temparary table's
+ *	  See src/backend/catalog/GTT_README for Global temporary table's
  *	  requirements and design.
  *
  *-------------------------------------------------------------------------
@@ -197,7 +197,7 @@ active_gtt_shared_hash_size(void)
 }
 
 /*
- * Initialization shared hash table for GTT.
+ * Initialization of shared hash table for GTT.
  */
 void
 active_gtt_shared_hash_init(void)
@@ -277,7 +277,7 @@ gtt_storage_checkin(Oid relid)
 
 /*
  * Remove the GTT relid record from the shared hash table which means that current backend is
- * not use this GTT.
+ * not using this GTT.
  */
 static void
 gtt_storage_checkout(Oid relid, bool isCommit)
@@ -298,7 +298,7 @@ gtt_storage_checkout(Oid relid, bool isCommit)
 	{
 		LWLockRelease(&gtt_shared_ctl->lock);
 		if (isCommit)
-			elog(WARNING, "relid %u not exist in gtt shared hash when drop local storage", relid);
+			elog(WARNING, "relid %u doesn't exist in gtt shared hash when dropping local storage", relid);
 
 		return;
 	}
@@ -319,9 +319,9 @@ gtt_storage_checkout(Oid relid, bool isCommit)
 
 /*
  * Gets usage information for a GTT from shared hash table.
- * The information is in the form of bitmap.
- * Quickly copy the entire bitmap from shared memory and return it.
- * that to avoid holding locks for a long time.
+ * The information is in the form of a bitmap.
+ * Quickly copy the entire bitmap from shared memory and return it,
+ * to avoid holding locks for a long time.
  */
 static Bitmapset *
 copy_active_gtt_bitmap(Oid relid)
@@ -417,18 +417,18 @@ remember_gtt_storage_info(RelFileNode rnode, Relation rel)
 	if (max_active_gtt <= 0)
 		ereport(ERROR,
 			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-			 errmsg("Global temporary table feature is disable"),
+			 errmsg("Global temporary table feature is disabled"),
 			 errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
 
 	if (RecoveryInProgress())
-		elog(ERROR, "readonly mode not support access global temporary table");
+		elog(ERROR, "recovery mode does not support accessing global temporary table");
 
 	if (rel->rd_rel->relkind == RELKIND_INDEX &&
 		rel->rd_index &&
 		(!rel->rd_index->indisvalid ||
 		 !rel->rd_index->indisready ||
 		 !rel->rd_index->indislive))
-		 elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+		 elog(ERROR, "invalid gtt index %s not allowed to create storage", RelationGetRelationName(rel));
 
 	/* First time through: initialize the hash table */
 	if (gtt_storage_local_hash == NULL)
@@ -757,7 +757,7 @@ up_gtt_relstats(Oid relid,
 
 /*
  * Search GTT relstats(relpage/reltuple/relallvisible)
- * from local has.
+ * from local hash.
  */
 bool
 get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
@@ -852,7 +852,7 @@ up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
 	}
 	MemoryContextSwitchTo(oldcontext);
 
-	if (!found)
+	if (!found) // XXX: should be an ereport with a hint ?
 		elog(WARNING, "analyze can not update relid %u column %d statistics after add or drop column, try truncate table first", reloid, attnum);
 
 	return;
@@ -960,7 +960,7 @@ remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
 
 /*
  * Update of backend Level oldest relfrozenxid to MyProc.
- * This makes each backend's oldest RelFrozenxID globally visible.
+ * This makes each backend's oldest RelFrozenXID globally visible.
  */
 static void
 set_gtt_session_relfrozenxid(void)
@@ -1282,7 +1282,7 @@ pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
 }
 
 /*
- * In order to build the GTT index, force enable GTT'index.
+ * In order to build the GTT index, force enable GTT index.
  */
 void
 force_enable_gtt_index(Relation index)
@@ -1331,7 +1331,7 @@ gtt_fix_index_backend_state(Relation index)
 
 /*
  * During the SQL initialization of the executor (InitPlan)
- * Initialize storage of GTT GTT'indexes and build empty index.
+ * Initialize storage of GTT's indexes and build empty index.
  */
 void
 init_gtt_storage(CmdType operation, Relation relation)
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 72218b2c66..6dee51784f 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -188,8 +188,8 @@ analyze_rel(Oid relid, RangeVar *relation,
 	}
 
 	/*
-	 * Skip the global temporary table that did not initialize the storage
-	 * in this backend.
+	 * Skip global temporary tables for which storage was not initialized by
+	 * this backend.
 	 */
 	if (RELATION_IS_GLOBAL_TEMP(onerel) &&
 		!gtt_storage_attached(RelationGetRelid(onerel)))
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 535f537aae..ff6ae583f5 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -392,15 +392,15 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
 	}
 
 	/*
-	 * Skip the global temporary table that did not initialize the storage
-	 * in this backend.
+	 * Skip global temporary tables for which storage was not initialized by
+	 * this backend.
 	 */
 	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
 	{
 		if (gtt_storage_attached(RelationGetRelid(OldHeap)))
 			ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("not support cluster global temporary table")));
+				 errmsg("clustering is not supported on a global temporary table")));
 
 		relation_close(OldHeap, AccessExclusiveLock);
 		pgstat_progress_end_command();
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index af12dd2cdc..4d5aedf2c3 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -461,7 +461,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		if (is_other_backend_use_gtt(RelationGetRelid(seqrel)))
 			ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("cannot alter global temporary sequence %s when other backend attached it.",
+				 errmsg("cannot alter global temporary sequence %s being accessed by another backend",
 						RelationGetRelationName(seqrel))));
 	}
 
@@ -1194,7 +1194,7 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	*p_elm = elm;
 	*p_rel = seqrel;
 
-	/* Initializes the storage for sequence which the global temporary table belongs. */
+	/* Initializes the storage for sequence which belongs to a global temporary table. */
 	if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
 		!gtt_storage_attached(RelationGetRelid(seqrel)))
 	{
@@ -1999,7 +1999,7 @@ get_seqence_start_value(Oid seqid)
 }
 
 /*
- * Initialize sequence which global temporary table belongs.
+ * Initialize sequence which belongs to a global temporary table.
  */
 void
 gtt_init_seq(Relation rel)
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 7aa3beba2c..e4b67f8731 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2018,8 +2018,8 @@ ExecuteTruncateGuts(List *explicit_rels,
 		}
 
 		/*
-		 * Skip the global temporary table that is not initialized for storage
-		 * in current backend.
+		 * Skip global temporary tables for which the current backend has not
+		 * initialized storage.
 		 */
 		if (RELATION_IS_GLOBAL_TEMP(rel))
 		{
@@ -3365,7 +3365,7 @@ CheckRelationTableSpaceMove(Relation rel, Oid newTableSpaceId)
 	if (RELATION_IS_GLOBAL_TEMP(rel))
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("The tablespace of global temporary table can not be changed")));
+				 errmsg("The tablespace of global temporary table cannot be changed")));
 
 	return true;
 }
@@ -4136,13 +4136,13 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(context->relid, NoLock);
 
-	/* We allow to alter global temporary table only current backend use it */
+	/* We disallow altering a global temporary table if another backend is using it */
 	if (RELATION_IS_GLOBAL_TEMP(rel))
 	{
 		if (is_other_backend_use_gtt(RelationGetRelid(rel)))
 			ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("cannot alter global temporary table %s when other backend attached it.",
+				 errmsg("cannot alter global temporary table %s being accessed by another backend",
 						RelationGetRelationName(rel))));
 	}
 
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 47c734aa1c..3a48892feb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2140,7 +2140,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 	}
 
 	/*
-	 * Skip those global temporary table that are not initialized in
+	 * Skip global temporary tables that are not initialized in the
 	 * current backend.
 	 */
 	if (RELATION_IS_GLOBAL_TEMP(rel) &&
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index cd2bd57e38..79e4b0c9c7 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -543,7 +543,7 @@ DefineView(ViewStmt *stmt, const char *queryString,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("views cannot be unlogged because they do not have storage")));
 
-	/* Global temporary table are not sensible. */
+	/* Global temporary views are not sensible. */
 	if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		ereport(ERROR,
 				(errcode(ERRCODE_SYNTAX_ERROR),
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 75a0940b25..4b96429f30 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2912,7 +2912,7 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
 		if (is_query_using_gtt(query))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("materialized views must not use global temporary tables or views")));
+					 errmsg("materialized views must not use global temporary tables")));
 
 		/*
 		 * A materialized view would either need to save parameters for use in
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 45241b8b0f..c9bef46339 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -449,7 +449,7 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 
 	/*
 	 * If a sequence is bound to a global temporary table, then the sequence
-	 * must been "global temporary"
+	 * must be "global temporary"
 	 */
 	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 5601f15d18..fa8c391d40 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2119,8 +2119,8 @@ do_autovacuum(void)
 		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
 			/*
-			 * Aotuvacuum cannot vacuum the private data stored in each backend
-			 * that belongs to global temporary table, so skip them.
+			 * Autovacuum cannot vacuum the private data stored in each backend
+			 * that belongs to global temporary tables, so skip them.
 			 */
 			continue;
 		}
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index e9cea9b423..0b207dca16 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -5180,6 +5180,9 @@ KnownAssignedXidsReset(void)
 /*
  * search all active backend to get oldest frozenxid
  * for global temporary table.
+ * Return the oldest frozenxid, or InvalidTransactionId if none.
+ * If pids!=NULL, it's populated with qualifying backend pids.
+ * If xids!=NULL, it's populated with qualifying backend frozenxids.
  */
 int
 list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
@@ -5216,7 +5219,7 @@ list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
 	if (max_size > 0 && max_size < arrayP->numProcs)
 	{
 		LWLockRelease(ProcArrayLock);
-		elog(ERROR, "list_all_gtt_frozenxids require more array");
+		elog(ERROR, "list_all_gtt_frozenxids requires a longer array");
 	}
 
 	for (index = 0; index < arrayP->numProcs; index++)
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 9a6e7cee24..b86bc4fc69 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -984,7 +984,7 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 			break;
 		case RELPERSISTENCE_GLOBAL_TEMP:
 			/*
-			 * For global temporary table ,each backend has its own storage,
+			 * For global temporary table, each backend has its own storage,
 			 * also only sees its own storage. Use Backendid to identify them.
 			 */
 			backend = BackendIdForTempRelations();
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 3100ddc704..caab5dc36a 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15431,7 +15431,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 
 		/*
 		 * Transaction information for the global temporary table is not stored
-		 * in the pg_class.
+		 * in pg_class.
 		 */
 		if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index e23cb49c01..658e9be8de 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -169,10 +169,10 @@ DECLARE_INDEX(pg_class_tblspc_relfilenode_index, 3455, ClassTblspcRelfilenodeInd
 #define		  RELKIND_PARTITIONED_TABLE 'p' /* partitioned table */
 #define		  RELKIND_PARTITIONED_INDEX 'I' /* partitioned index */
 
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 #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'
-- 
2.17.1

0006-f-0004-gtt-v64-regress.txttext/x-diff; charset=us-asciiDownload
From 8311d1d40294865a419b9bd0950c3cd642744e28 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sat, 1 Jan 2022 16:46:54 -0600
Subject: [PATCH 06/11] f!0004-gtt-v64-regress.patch

---
 src/test/regress/expected/global_temporary_table.out | 12 ++++++------
 src/test/regress/sql/global_temporary_table.sql      |  4 ++--
 2 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/src/test/regress/expected/global_temporary_table.out b/src/test/regress/expected/global_temporary_table.out
index 2ada5fa6b9..0aeb53d46d 100644
--- a/src/test/regress/expected/global_temporary_table.out
+++ b/src/test/regress/expected/global_temporary_table.out
@@ -1,5 +1,5 @@
 --
--- GLobal emparary table test case 
+-- Global temporary table test case 
 --
 CREATE SCHEMA IF NOT EXISTS global_temporary_table;
 set search_path=global_temporary_table,sys;
@@ -153,16 +153,16 @@ ERROR:  Only support alter global temporary table in an empty context.
 HINT:  Please create a new connection and execute ALTER TABLE on the new connection.
 -- should fail
 cluster gtt_on_commit_default using gtt_on_commit_default_pkey;
-ERROR:  not support cluster global temporary table
+ERROR:  clustering is not supported on a global temporary table
 -- should fail
 create table gtt_on_commit_default(a int primary key, b text) on commit delete rows;
 ERROR:  ON COMMIT can only be used on temporary tables
 -- should fail
 REINDEX (TABLESPACE pg_default) TABLE gtt_test_createindex;
-ERROR:  The tablespace of global temporary table can not be changed
+ERROR:  The tablespace of global temporary table cannot be changed
 -- should fail
 REINDEX (TABLESPACE pg_default) INDEX gtt_idx_1;
-ERROR:  The tablespace of global temporary table can not be changed
+ERROR:  The tablespace of global temporary table cannot be changed
 -- should fail
 alter table gtt_on_commit_default set ( on_commit_delete_rows='true');
 ERROR:  table cannot add or modify on commit parameter by ALTER TABLE command.
@@ -184,7 +184,7 @@ create global temp table gtt4(a int primary key, b text) with(on_commit_delete_r
 ERROR:  could not create global temporary table with on commit and with clause at same time
 -- should fail
 CREATE MATERIALIZED VIEW mv_gtt_on_commit_default as select * from gtt_on_commit_default;
-ERROR:  materialized views must not use global temporary tables or views
+ERROR:  materialized views must not use global temporary tables
 -- test create table as select
 CREATE GLOBAL TEMPORARY TABLE test_create_table_as AS SELECT 1 AS a;
 -- test copy stmt
@@ -482,7 +482,7 @@ select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order
 (2 rows)
 
 -- get all object info in current schema
-select relname ,relkind, relpersistence, reloptions from pg_class c, pg_namespace n where c.relnamespace = n.oid and n.nspname = 'global_temporary_table' order by relkind,relpersistence,relname;
+select relname, relkind, relpersistence, reloptions from pg_class c, pg_namespace n where c.relnamespace = n.oid and n.nspname = 'global_temporary_table' order by relkind,relpersistence,relname;
                      relname                     | relkind | relpersistence |          reloptions           
 -------------------------------------------------+---------+----------------+-------------------------------
  global_temp_partition_01_2021_id_seq            | S       | g              | {on_commit_delete_rows=false}
diff --git a/src/test/regress/sql/global_temporary_table.sql b/src/test/regress/sql/global_temporary_table.sql
index 51cd9bbbc0..e47c6aa3cf 100644
--- a/src/test/regress/sql/global_temporary_table.sql
+++ b/src/test/regress/sql/global_temporary_table.sql
@@ -1,5 +1,5 @@
 --
--- GLobal emparary table test case 
+-- Global temporary table test case
 --
 
 CREATE SCHEMA IF NOT EXISTS global_temporary_table;
@@ -306,7 +306,7 @@ select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_rel
 select * from pg_gtt_stats where tablename = 'temp_table_test_systemview' order by tablename;
 
 -- get all object info in current schema
-select relname ,relkind, relpersistence, reloptions from pg_class c, pg_namespace n where c.relnamespace = n.oid and n.nspname = 'global_temporary_table' order by relkind,relpersistence,relname;
+select relname, relkind, relpersistence, reloptions from pg_class c, pg_namespace n where c.relnamespace = n.oid and n.nspname = 'global_temporary_table' order by relkind,relpersistence,relname;
 
 reset search_path;
 drop schema global_temporary_table cascade;
-- 
2.17.1

#354Pavel Stehule
pavel.stehule@gmail.com
In reply to: Justin Pryzby (#353)
Re: [Proposal] Global temporary tables

Hi

You redirect stats from pg_class and pg_statistics to a local hash table.

This is pretty hairy :(
I guess you'd also need to handle pg_statistic_ext and ext_data.
pg_stats doesn't work, since the data isn't in pg_statistic - it'd need to
look
at pg_get_gtt_statistics.

Without this, the GTT will be terribly slow like current temporary tables
with a lot of problems with bloating of pg_class, pg_attribute and
pg_depend tables.

Regards

Pavel

#355Andres Freund
andres@anarazel.de
In reply to: Pavel Stehule (#354)
Re: [Proposal] Global temporary tables

Hi,

On 2022-02-27 04:17:52 +0100, Pavel Stehule wrote:

You redirect stats from pg_class and pg_statistics to a local hash table.
This is pretty hairy :(

As is I think the patch is architecturally completely unacceptable. Having
code everywhere to redirect to manually written in-memory catalog table code
isn't maintainable.

I guess you'd also need to handle pg_statistic_ext and ext_data.
pg_stats doesn't work, since the data isn't in pg_statistic - it'd need to
look
at pg_get_gtt_statistics.

Without this, the GTT will be terribly slow like current temporary tables
with a lot of problems with bloating of pg_class, pg_attribute and
pg_depend tables.

I think it's not a great idea to solve multiple complicated problems at
once...

Greetings,

Andres Freund

#356Pavel Stehule
pavel.stehule@gmail.com
In reply to: Andres Freund (#355)
Re: [Proposal] Global temporary tables

ne 27. 2. 2022 v 5:13 odesílatel Andres Freund <andres@anarazel.de> napsal:

Hi,

On 2022-02-27 04:17:52 +0100, Pavel Stehule wrote:

You redirect stats from pg_class and pg_statistics to a local hash

table.

This is pretty hairy :(

As is I think the patch is architecturally completely unacceptable. Having
code everywhere to redirect to manually written in-memory catalog table
code
isn't maintainable.

I guess you'd also need to handle pg_statistic_ext and ext_data.
pg_stats doesn't work, since the data isn't in pg_statistic - it'd

need to

look
at pg_get_gtt_statistics.

Without this, the GTT will be terribly slow like current temporary tables
with a lot of problems with bloating of pg_class, pg_attribute and
pg_depend tables.

I think it's not a great idea to solve multiple complicated problems at
once...

I thought about this issue for a very long time, and I didn't find any
better (without more significant rewriting of pg storage). In a lot of
projects, that I know, the temporary tables are strictly prohibited due
possible devastating impact to system catalog bloat. It is a serious
problem. So any implementation of GTT should solve the questions: a) how to
reduce catalog bloating, b) how to allow session related statistics for
GTT. I agree so implementation of GTT like template based LTT (local
temporary tables) can be very simple (it is possible by extension), but
with the same unhappy performance impacts.

I don't say so current design should be accepted without any discussions
and without changes. Maybe GTT based on LTT can be better than nothing
(what we have now), and can be good enough for a lot of projects where the
load is not too high (and almost all projects have low load).
Unfortunately,it can be a trap for a lot of projects in future, so there
should be discussion and proposed solutions for fix of related issues. The
performance of GTT should be fixable, so any discussion about this topic
should have part about protections against catalog bloat and about cost
related to frequent catalog updates.

But anyway, I invite (and probably not just me) any discussion on how to
implement this feature, how to solve performance issues, and how to divide
implementation into smaller steps. I am sure so fast GTT implementation
can be used for fast implementation of LTT too, and maybe with all other
temporary objects

Regards

Pavel

Show quoted text

Greetings,

Andres Freund

#357Wenjing Zeng
wjzeng2012@gmail.com
In reply to: Andres Freund (#352)
Re: [Proposal] Global temporary tables

2022年2月25日 15:45,Andres Freund <andres@anarazel.de> 写道:

Hi,

This is a huge thread. Realistically reviewers and committers can't reread
it. I think there needs to be more of a description of how this works included
in the patchset and *why* it works that way. The readme does a bit of that,
but not particularly well.

Thank you for your review of the design and code.
I'm always trying to improve it. If you are confused or need clarification on something, please point it out.

On 2022-02-25 14:26:47 +0800, Wenjing Zeng wrote:

+++ b/README.gtt.txt
@@ -0,0 +1,172 @@
+Global Temporary Table(GTT)
+=========================================
+
+Feature description
+-----------------------------------------
+
+Previously, temporary tables are defined once and automatically
+exist (starting with empty contents) in every session before using them.

I think for a README "previously" etc isn't good language - if it were
commited, it'd not be understandable anymore. It makes more sense for commit
messages etc.

Thanks for pointing it out. I will adjust the description.

+Main design ideas
+-----------------------------------------
+In general, GTT and LTT use the same storage and buffer design and
+implementation. The storage files for both types of temporary tables are named
+as t_backendid_relfilenode, and the local buffer is used to cache the data.

What does "named ast_backendid_relfilenode" mean?

This is the storage file naming format for describing temporary tables.
It starts with 't', followed by backendid and relfilenode, connected by an underscore.
File naming rules are the same as LTT.
The data in the file is no different from regular tables and LTT.

+The schema of GTTs is shared among sessions while their data are not. We build
+a new mechanisms to manage those non-shared data and their statistics.
+Here is the summary of changes:
+
+1) CATALOG
+GTTs store session-specific data. The storage information of GTTs'data, their
+transaction information, and their statistics are not stored in the catalog.
+
+2) STORAGE INFO & STATISTICS INFO & TRANSACTION INFO
+In order to maintain durability and availability of GTTs'session-specific data,
+their storage information, statistics, and transaction information is managed
+in a local hash table tt_storage_local_hash.

"maintain durability"? Durable across what? In the context of databases it's
typically about crash safety, but that can't be the case here.

It means that the transaction information(relfrozenxid/relminmxid) storage information(relfilenode)
and statistics(relpages) of GTT, which are maintained in hashtable , not pg_class.
This is to allow GTT to store its own local data in different sessions and to avoid frequent catalog changes.

+3) DDL
+Currently, GTT supports almost all table'DDL except CLUSTER/VACUUM FULL.
+Part of the DDL behavior is limited by shared definitions and multiple copies of
+local data, and we added some structures to handle this.
+A shared hash table active_gtt_shared_hash is added to track the state of the
+GTT in a different session. This information is recorded in the hash table
+during the DDL execution of the GTT.
+The data stored in a GTT can only be modified or accessed by owning session.
+The statements that only modify data in a GTT do not need a high level of
+table locking. The operations making those changes include truncate GTT,
+reindex GTT, and lock GTT.

I think you need to introduce a bit more terminology for any of this to make
sense. Sometimes GTT means the global catalog entity, sometimes, like here, it
appears to mean the session specific contents of a GTT.

What state of a GTT in a nother session?

How do GTTs handle something like BEGIN; TRUNCATE some_gtt_table; ROLLBACK;?

GTT behaves exactly like a regular table.
Specifically, the latest relfilenode for the current session is stored in the hashtable and may change it.
If the transaction rolls back, the old relfilenode is enabled again, just as it is in pg_class.

+1.2 on commit clause
+LTT's status associated with on commit DELETE ROWS and on commit PRESERVE ROWS
+is not stored in catalog. Instead, GTTs need a bool value on_commit_delete_rows
+in reloptions which is shared among sessions.

Why?

The LTT is always created and used in the current session. The on commit clause property
does not need to be shared with other sessions. This is why LTT does not record the on commit clause
in the catalog.
However, GTT's table definitions are shared between sessions, including the on commit clause,
so it needs to be saved in the catalog.

+2.3 statistics info
+1) relpages reltuples relallvisible relfilenode

?

It was mentioned above.

+3 DDL
+3.1. active_gtt_shared_hash
+This is the hash table created in shared memory to trace the GTT files initialized
+in each session. Each hash entry contains a bitmap that records the backendid of
+the initialized GTT file. With this hash table, we know which backend/session
+is using this GTT. Such information is used during GTT's DDL operations.

So there's a separate locking protocol for GTTs that doesn't use the normal
locking infrastructure? Why?

+3.7 CLUSTER GTT/VACUUM FULL GTT
+The current version does not support.

Why?

Currently, GTT cannot reuse clusters for regular table processes. I choose not to support it for now.
Also, I can't think of any scenario that would require clustering for temporary tables, which
is another reason why not support cluster first.

+4 MVCC commit log(clog) cleanup
+
+The GTT storage file contains transaction information. Queries for GTT data rely
+on transaction information such as clog. The transaction information required by
+each session may be completely different.

Why is transaction information different between sessions? Or does this just
mean that different transaction ids will be accessed?

It has the same meaning as pg_class.relfrozenxid.
For the same GTT, the first transaction to write data in each session is different and
the data is independent of each other. They have a separate frozenxid.
The vacuum clog process needs to consider it.

0003-gtt-v67-implementation.patch
71 files changed, 3167 insertions(+), 195 deletions(-)

This needs to be broken into smaller chunks to be reviewable.

@@ -677,6 +678,14 @@ _bt_getrootheight(Relation rel)
{
Buffer metabuf;

+		/*
+		 * If a global temporary table storage file is not initialized in the
+		 * this session, its index does not have a root page, just returns 0.
+		 */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			!gtt_storage_attached(RelationGetRelid(rel)))
+			return 0;
+
metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
metad = _bt_getmeta(rel, metabuf);

Stuff like this seems not acceptable. Accesses would have to be prevented much
earlier. Otherwise each index method is going to need copies of this logic. I
also doubt that _bt_getrootheight() is the only place that'd need this.

You are right, this is done to solve the empty GTT being queried. I don't need it anymore,
so I'll get rid of it.

static void
index_update_stats(Relation rel,
bool hasindex,
- double reltuples)
+ double reltuples,
+ bool isreindex)
{
Oid relid = RelationGetRelid(rel);
Relation pg_class;
@@ -2797,6 +2824,13 @@ index_update_stats(Relation rel,
Form_pg_class rd_rel;
bool dirty;

+	/*
+	 * Most of the global Temp table data is updated to the local hash, and reindex
+	 * does not refresh relcache, so call a separate function.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		return index_update_gtt_relstats(rel, hasindex, reltuples, isreindex);
+

So basically every single place in the code that does catalog accesses is
going to need a completely separate implementation for GTTs? That seems
unmaintainable.

create Index on GTT and VACUUM GTT do this.
Some info of the table (relhasIndex) need to be updated to pg_class,
while others not (relpages…).
Would you prefer to extend it on the original function?

+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  The body implementation of Global temparary table.
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *	  See src/backend/catalog/GTT_README for Global temparary table's
+ *	  requirements and design.
+ *
+ *-------------------------------------------------------------------------
+ */

I don't think that path to the readme is correct.

I tried to reorganize it.

Regards, Wenjing.

Show quoted text

Greetings,

Andres Freund

#358Wenjing Zeng
wjzeng2012@gmail.com
In reply to: Justin Pryzby (#353)
Re: [Proposal] Global temporary tables

2022年2月27日 08:21,Justin Pryzby <pryzby@telsasoft.com> 写道:

I read through this.
Find attached some language fixes. You should be able to apply each "fix"
patch on top of your own local branch with git am, and then squish them
together. Let me know if you have trouble with that.

I think get_seqence_start_value() should be static. (Or otherwise, it should
be in lsyscache.c).

The include added to execPartition.c seems to be unused.

+#define RELATION_IS_TEMP_ON_CURRENT_SESSION(relation) \
+#define RELATION_IS_TEMP(relation) \
+#define RelpersistenceTsTemp(relpersistence) \
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)    \

=> These macros can evaluate their arguments multiple times.
You should add a comment to warn about that. And maybe avoid passing them a
function argument, like: RelpersistenceTsTemp(get_rel_persistence(rte->relid))

+list_all_backend_gtt_frozenxids should return TransactionId not int.
The function name should say "oldest" and not "all" ?

I think the GUC should have a longer name. max_active_gtt is too short for a
global var.

+#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

It's being initialized to MIN, but then the GUC machinery sets it to DEFAULT.
By convention, it should be initialized to default.

fout->remoteVersion >= 140000

=> should say 15

describe.c has gettext_noop("session"), which is a half-truth. The data is
per-session but the table definition is persistent..

Thanks for your advice, I will try to merge this part of the code.

You redirect stats from pg_class and pg_statistics to a local hash table.
This is pretty hairy :(
I guess you'd also need to handle pg_statistic_ext and ext_data.
pg_stats doesn't work, since the data isn't in pg_statistic - it'd need to look
at pg_get_gtt_statistics.

I wonder if there's a better way to do it, like updating pg_statistic but
forcing the changes to be rolled back when the session ends... But I think
that would make longrunning sessions behave badly, the same as "longrunning
transactions".

There are three pieces of data related to session-level GTT data that need to be managed
1 session-level storage info like relfilenode
2 session-level like relfrozenxid
3 session-level stats like relpages or column stats

I think the 1 and 2 are necessary, but not for stats.
In the previous email, It has been suggested that GTT statistics not be processed.
This means that GTT statistics are not recorded in the localhash or catalog.
In my observation, very few users require an accurate query plan for temporary tables to
perform manual analyze.
Of course, doing this will also avoid catalog bloat and performance problems.

Have you looked at Gilles Darold's GTT extension ?

If you are referring to https://github.com/darold/pgtt <https://github.com/darold/pgtt&gt; , yes.
It is smart to use unlogged table as a template and then use LTT to read and write data.
For this implementation, I want to point out two things:
1 For the first insert of GTT in each session, create table or create index is implicitly executed.
2 The catalog bloat caused by LTT still exist.

Regards, Wenjing.

Show quoted text

<0002-f-0002-gtt-v64-doc.txt><0004-f-0003-gtt-v64-implementation.txt><0006-f-0004-gtt-v64-regress.txt>

#359Wenjing Zeng
wjzeng2012@gmail.com
In reply to: Andres Freund (#355)
Re: [Proposal] Global temporary tables

2022年2月27日 12:13,Andres Freund <andres@anarazel.de> 写道:

Hi,

On 2022-02-27 04:17:52 +0100, Pavel Stehule wrote:

You redirect stats from pg_class and pg_statistics to a local hash table.
This is pretty hairy :(

As is I think the patch is architecturally completely unacceptable. Having
code everywhere to redirect to manually written in-memory catalog table code
isn't maintainable.

I guess you'd also need to handle pg_statistic_ext and ext_data.
pg_stats doesn't work, since the data isn't in pg_statistic - it'd need to
look
at pg_get_gtt_statistics.

Without this, the GTT will be terribly slow like current temporary tables
with a lot of problems with bloating of pg_class, pg_attribute and
pg_depend tables.

I think it's not a great idea to solve multiple complicated problems at
once...

I'm trying to break down the entire implementation into multiple sub-patches.

Regards, Wenjing.

Show quoted text

Greetings,

Andres Freund

#360Adam Brusselback
adambrusselback@gmail.com
In reply to: Wenjing Zeng (#359)
Re: [Proposal] Global temporary tables

In my observation, very few users require an accurate query plan for

temporary tables to
perform manual analyze.

Absolutely not true in my observations or personal experience. It's one of
the main reasons I have needed to use (local) temporary tables rather than
just materializing a CTE when decomposing queries that are too complex for
Postgres to handle.

I wish I could use GTT to avoid the catalog bloat in those instances, but
that will only be possible if the query plans are accurate.

#361Pavel Stehule
pavel.stehule@gmail.com
In reply to: Adam Brusselback (#360)
Re: [Proposal] Global temporary tables

st 2. 3. 2022 v 19:02 odesílatel Adam Brusselback <adambrusselback@gmail.com>
napsal:

In my observation, very few users require an accurate query plan for

temporary tables to
perform manual analyze.

Absolutely not true in my observations or personal experience. It's one of
the main reasons I have needed to use (local) temporary tables rather than
just materializing a CTE when decomposing queries that are too complex for
Postgres to handle.

I wish I could use GTT to avoid the catalog bloat in those instances, but
that will only be possible if the query plans are accurate.

This strongly depends on usage. Very common patterns from MSSQL don't need
statistics. But on second thought, sometimes, the query should be divided
and temp tables are used for storing some middle results. In this case, you
cannot exist without statistics. In the first case, the temp tables can be
replaced by arrays. In the second case, the temp tables are not replaceable.

Regards

Pavel

#362Andres Freund
andres@anarazel.de
In reply to: Pavel Stehule (#356)
Re: [Proposal] Global temporary tables

Hi,

On 2022-02-27 06:09:54 +0100, Pavel Stehule wrote:

ne 27. 2. 2022 v 5:13 odes�latel Andres Freund <andres@anarazel.de> napsal:

On 2022-02-27 04:17:52 +0100, Pavel Stehule wrote:

Without this, the GTT will be terribly slow like current temporary tables
with a lot of problems with bloating of pg_class, pg_attribute and
pg_depend tables.

I think it's not a great idea to solve multiple complicated problems at
once...

I thought about this issue for a very long time, and I didn't find any
better (without more significant rewriting of pg storage). In a lot of
projects, that I know, the temporary tables are strictly prohibited due
possible devastating impact to system catalog bloat. It is a serious
problem. So any implementation of GTT should solve the questions: a) how to
reduce catalog bloating, b) how to allow session related statistics for
GTT. I agree so implementation of GTT like template based LTT (local
temporary tables) can be very simple (it is possible by extension), but
with the same unhappy performance impacts.

I don't say so current design should be accepted without any discussions
and without changes. Maybe GTT based on LTT can be better than nothing
(what we have now), and can be good enough for a lot of projects where the
load is not too high (and almost all projects have low load).

I think there's just no way that it can be merged with anything close to the
current design - it's unmaintainable. The need for the feature doesn't change
that.

That's not to say it's impossible to come up with a workable design. But it's
definitely not easy. If I were to work on this - which I am not planning to -
I'd try to solve the problems of "LTT" first, with an eye towards using the
infrastructure for GTT.

I think you'd basically have to come up with a generic design for partitioning
catalog tables into local / non-local storage, without needing explicit code
for each catalog. That could also be used to store the default catalog
contents separately from user defined ones (e.g. pg_proc is pretty large).

Greetings,

Andres Freund

#363Pavel Stehule
pavel.stehule@gmail.com
In reply to: Andres Freund (#362)
Re: [Proposal] Global temporary tables

Hi

I think you'd basically have to come up with a generic design for
partitioning
catalog tables into local / non-local storage, without needing explicit
code
for each catalog. That could also be used to store the default catalog
contents separately from user defined ones (e.g. pg_proc is pretty large).

There is still a risk of bloating in local storage, but, mainly, you
probably have to modify a lot of lines because the system cache doesn't
support partitioning.

Regards

Pavel

Show quoted text

Greetings,

Andres Freund

#364Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#362)
Re: [Proposal] Global temporary tables

On Wed, Mar 2, 2022 at 4:18 PM Andres Freund <andres@anarazel.de> wrote:

I think there's just no way that it can be merged with anything close to the
current design - it's unmaintainable. The need for the feature doesn't change
that.

I don't know whether the design is right or wrong, but I agree that a
bad design isn't OK just because we need the feature. I'm not entirely
convinced that the change to _bt_getrootheight() is a red flag,
although I agree that there is a need to explain and justify why
similar changes aren't needed in other places. But I think overall
this patch is just too big and too unpolished to be seriously
considered. It clearly needs to be broken down into incremental
patches that are not just separated by topic but potentially
independently committable, with proposed commit messages for each.

And, like, there's a long history on this thread of people pointing
out particular crash bugs and particular problems with code comments
or whatever and I guess those are getting fixed as they are reported,
but I do not have the feeling that the overall code quality is
terribly high, because people just keep finding more stuff. Like look
at this:

+ uint8 flags = 0;
+
+ /* return 0 if feature is disabled */
+ if (max_active_gtt <= 0)
+ return InvalidTransactionId;
+
+ /* Disable in standby node */
+ if (RecoveryInProgress())
+ return InvalidTransactionId;
+
+ flags |= PROC_IS_AUTOVACUUM;
+ flags |= PROC_IN_LOGICAL_DECODING;
+
+ LWLockAcquire(ProcArrayLock, LW_SHARED);
+ arrayP = procArray;
+ for (index = 0; index < arrayP->numProcs; index++)
+ {
+ int pgprocno = arrayP->pgprocnos[index];
+ PGPROC    *proc = &allProcs[pgprocno];
+ uint8 statusFlags = ProcGlobal->statusFlags[index];
+ TransactionId gtt_frozenxid = InvalidTransactionId;
+
+ if (statusFlags & flags)
+ continue;

This looks like code someone wrote, modified multiple times as they
found problems, and never cleaned up. 'flags' gets set to 0, and then
unconditionally gets two bits xor'd in, and then we test it against
statusFlags. Probably there shouldn't be a local variable at all, and
if there is, the value should be set properly from the start instead
of constructed incrementally as we go along. And there should be
comments. Why is it OK to return InvalidTransactionId in standby mode?
Why is it OK to pass that flags value? And, if we look at this
function a little further down, is it really OK to hold ProcArrayLock
across an operation that could perform multiple memory allocation
operations? I bet it's not, unless calls are very infrequent in
practice.

I'm not asking for this particular part of the code to be cleaned up.
I'm asking for the whole patch to be cleaned up. Like, nobody who is a
committer is going to have enough time to go through the patch
function by function and point out issues on this level of detail in
every place where they occur. Worse, discussing all of those issues is
just a distraction from the real task of figuring out whether the
design needs adjustment. Because the patch is one massive code drop,
and with not-really-that-clean code and not-that-great comments, it's
almost impossible to review. I don't plan to try unless the quality
improves a lot. I'm not saying it's the worst code ever written, but I
think it's kind of at a level of "well, it seems to work for me," and
the standard around here is higher than that. It's not the job of the
community or of individual committers to prove that problems exist in
this patch and therefore it shouldn't be committed. It's the job of
the author to prove that there aren't and it should be. And I don't
think we're close to that at all.

--
Robert Haas
EDB: http://www.enterprisedb.com

#365Greg Stark
stark@mit.edu
In reply to: Robert Haas (#364)
Re: [Proposal] Global temporary tables

It doesn't look like this is going to get committed this release
cycle. I understand more feedback could be valuable, especially on the
overall design, but as this is the last commitfest of the release we
should focus on other patches for now and spend that time in the next
release cycle.

I'm going to bump this one now as Waiting on Author for the design
documentation Robert asks for and probably a plan for how to separate
that design into multiple separable features as Andres suggested.

I'm still hopeful we get to advance this early in 16 because I think
everyone agrees the feature would be great.

#366Robert Haas
robertmhaas@gmail.com
In reply to: Greg Stark (#365)
Re: [Proposal] Global temporary tables

On Thu, Mar 3, 2022 at 3:29 PM Greg Stark <stark@mit.edu> wrote:

I'm still hopeful we get to advance this early in 16 because I think
everyone agrees the feature would be great.

I'm not saying this patch can't make progress, but I think the chances
of this being ready to commit any time in the v16 release cycle, let
alone at the beginning, are low. This patch set has been around since
2019, and here Andres and I are saying it's not even really reviewable
in the shape that it's in. I have done some review of it previously,
BTW, but eventually I gave up because it just didn't seem like we were
making any progress. And then a long time after that people were still
finding many server crashes with relatively simple test cases.

I agree that the feature is desirable, but I think getting there is
going to require a huge amount of effort that may amount to a total
rewrite of the patch.

--
Robert Haas
EDB: http://www.enterprisedb.com

#367Andres Freund
andres@anarazel.de
In reply to: Robert Haas (#366)
Re: [Proposal] Global temporary tables

Hi,

On 2022-03-03 16:07:37 -0500, Robert Haas wrote:

On Thu, Mar 3, 2022 at 3:29 PM Greg Stark <stark@mit.edu> wrote:

I'm still hopeful we get to advance this early in 16 because I think
everyone agrees the feature would be great.

I'm not saying this patch can't make progress, but I think the chances
of this being ready to commit any time in the v16 release cycle, let
alone at the beginning, are low. This patch set has been around since
2019, and here Andres and I are saying it's not even really reviewable
in the shape that it's in. I have done some review of it previously,
BTW, but eventually I gave up because it just didn't seem like we were
making any progress. And then a long time after that people were still
finding many server crashes with relatively simple test cases.

I agree that the feature is desirable, but I think getting there is
going to require a huge amount of effort that may amount to a total
rewrite of the patch.

Agreed. I think this needs very fundamental design work, and the patch itself
isn't worth reviewing until that's tackled.

Greetings,

Andres Freund

#368Jacob Champion
jchampion@timescale.com
In reply to: Andres Freund (#367)
Re: [Proposal] Global temporary tables

On 3/3/22 13:20, Andres Freund wrote:

On 2022-03-03 16:07:37 -0500, Robert Haas wrote:

I agree that the feature is desirable, but I think getting there is
going to require a huge amount of effort that may amount to a total
rewrite of the patch.

Agreed. I think this needs very fundamental design work, and the patch itself
isn't worth reviewing until that's tackled.

Given two opinions that the patch can't be effectively reviewed as-is, I
will mark this RwF for this commitfest. Anyone up for shepherding the
design conversations, going forward?

--Jacob